Behalten Sie die Caret-Position im HTML-Inhalt bei, wenn sich der innere HTML-Code ändert
P粉668804228
P粉668804228 2023-11-08 22:38:23
0
2
815

Ich habe ein Div, das als WYSIWYG-Editor fungiert. Es fungiert als Textfeld, rendert jedoch die Markdown-Syntax darin, um Live-Änderungen anzuzeigen.

Problem: Beim Eingeben von Buchstaben wird die Einfügemarke auf den Anfang des Div zurückgesetzt.


const editor = document.querySelector('div');
editor.innerHTML = parse('**dlob**  *cilati*');

editor.addEventLis tener('input', () => {
  editor.innerHTML = parse(editor.innerText);
});

function parse(text) {
  return text
    .replace(/**(.*)**/gm, '**<strong></strong>**')     // bold
    .replace(/*(.*)*/gm, '*<em></em>*');                  // italic
}
div {
  height: 100vh;
  width: 100vw;
}
<div contenteditable />


Codepen: https://codepen.io/ADAMJR/pen/MWvPebK

Markdown-Editoren wie QuillJS scheinen in der Lage zu sein, untergeordnete Elemente zu bearbeiten, ohne das übergeordnete Element zu bearbeiten. Dadurch wird das Problem vermieden, aber ich bin mir jetzt sicher, wie ich diese Logik mit diesem Setup wiederherstellen kann.

Frage: Wie kann verhindert werden, dass die Einfügemarke beim Tippen zurückgesetzt wird?

Update: Ich habe es geschafft, die Caret-Position bei jeder Eingabe an das Ende des Div zu senden. Dies führt jedoch immer noch zu einer grundsätzlichen Neuausrichtung der Position. https://codepen.io/ADAMJR/pen/KKvGNbY


P粉668804228
P粉668804228

Antworte allen(2)
P粉393030917

大多数富文本编辑器的做法是保持自己的内部状态,在按键事件上更新它并渲染自定义可视层。例如这样:

const $editor = document.querySelector('.editor');
const state = {
 cursorPosition: 0,
 contents: 'hello world'.split(''),
 isFocused: false,
};


const $cursor = document.createElement('span');
$cursor.classList.add('cursor');
$cursor.innerText = '᠎'; // Mongolian vowel separator

const renderEditor = () => {
  const $contents = state.contents
    .map(char => {
      const $span = document.createElement('span');
      $span.innerText = char;
      return $span;
    });
  
  $contents.splice(state.cursorPosition, 0, $cursor);
  
  $editor.innerHTML = '';
  $contents.forEach(el => $editor.append(el));
}

document.addEventListener('click', (ev) => {
  if (ev.target === $editor) {
    $editor.classList.add('focus');
    state.isFocused = true;
  } else {
    $editor.classList.remove('focus');
    state.isFocused = false;
  }
});

document.addEventListener('keydown', (ev) => {
  if (!state.isFocused) return;
  
  switch(ev.key) {
    case 'ArrowRight':
      state.cursorPosition = Math.min(
        state.contents.length, 
        state.cursorPosition + 1
      );
      renderEditor();
      return;
    case 'ArrowLeft':
      state.cursorPosition = Math.max(
        0, 
        state.cursorPosition - 1
      );
      renderEditor();
      return;
    case 'Backspace':
      if (state.cursorPosition === 0) return;
      delete state.contents[state.cursorPosition-1];
      state.contents = state.contents.filter(Boolean);
      state.cursorPosition = Math.max(
        0, 
        state.cursorPosition - 1
      );
      renderEditor();
      return;
    default:
      // This is very naive
      if (ev.key.length > 1) return;
      state.contents.splice(state.cursorPosition, 0, ev.key);
      state.cursorPosition += 1;
      renderEditor();
      return;
  }  
});

renderEditor();
.editor {
  position: relative;
  min-height: 100px;
  max-height: max-content;
  width: 100%;
  border: black 1px solid;
}

.editor.focus {
  border-color: blue;
}

.editor.focus .cursor {
  position: absolute;
  border: black solid 1px;
  border-top: 0;
  border-bottom: 0;
  animation-name: blink;
  animation-duration: 1s;
  animation-iteration-count: infinite;
}

@keyframes blink {
  from {opacity: 0;}
  50% {opacity: 1;}
  to {opacity: 0;}
}
P粉060112396

需要先获取光标的位置,然后对内容进行处理和设置。然后恢复光标位置。

当存在嵌套元素时,恢复光标位置是一个棘手的部分。此外,您每次都会创建新的 元素,旧的元素将被丢弃。

const editor = document.querySelector(".editor");
editor.innerHTML = parse(
  "For **bold** two stars.\nFor *italic* one star. Some more **bold**."
);

editor.addEventListener("input", () => {
  //get current cursor position
  const sel = window.getSelection();
  const node = sel.focusNode;
  const offset = sel.focusOffset;
  const pos = getCursorPosition(editor, node, offset, { pos: 0, done: false });
  if (offset === 0) pos.pos += 0.5;

  editor.innerHTML = parse(editor.innerText);

  // restore the position
  sel.removeAllRanges();
  const range = setCursorPosition(editor, document.createRange(), {
    pos: pos.pos,
    done: false,
  });
  range.collapse(true);
  sel.addRange(range);
});

function parse(text) {
  //use (.*?) lazy quantifiers to match content inside
  return (
    text
      .replace(/\*{2}(.*?)\*{2}/gm, "**$1**") // bold
      .replace(/(?$1*") // italic
      // handle special characters
      .replace(/\n/gm, "
") .replace(/\t/gm, " ") ); } // get the cursor position from .editor start function getCursorPosition(parent, node, offset, stat) { if (stat.done) return stat; let currentNode = null; if (parent.childNodes.length == 0) { stat.pos += parent.textContent.length; } else { for (let i = 0; i = stat.pos) { range.setStart(parent, stat.pos); stat.done = true; } else { stat.pos = stat.pos - parent.textContent.length; } } else { for (let i = 0; i
.editor {
  height: 100px;
  width: 400px;
  border: 1px solid #888;
  padding: 0.5rem;
  white-space: pre;
}

em, strong{
  font-size: 1.3rem;
}
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage