Kekalkan kedudukan karet dalam HTML boleh diedit kandungan apabila HTML dalaman berubah
P粉668804228
P粉668804228 2023-11-08 22:38:23
0
2
801

Saya mempunyai div yang bertindak sebagai editor WYSIWYG. Ia bertindak sebagai kotak teks tetapi memaparkan sintaks Markdown di dalamnya untuk menunjukkan perubahan secara langsung.

Masalah: Apabila menaip huruf, kedudukan karet ditetapkan semula ke permulaan div.


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

Penyunting penurunan harga seperti QuillJS nampaknya boleh mengedit elemen anak tanpa mengedit elemen induk. Ini mengelakkan masalah, tetapi saya kini pasti bagaimana untuk mencipta semula logik itu dengan persediaan ini.

Soalan: Bagaimana untuk mengelakkan kedudukan karet daripada ditetapkan semula semasa menaip?

Kemas kini: Saya telah berjaya menghantar kedudukan karet ke hujung div pada setiap input. Walau bagaimanapun, ini masih pada asasnya menetapkan semula kedudukan. https://codepen.io/ADAMJR/pen/KKvGNbY


P粉668804228
P粉668804228

membalas semua(2)
P粉393030917

Apa yang dilakukan oleh kebanyakan editor teks yang kaya ialah mengekalkan keadaan dalaman mereka sendiri, mengemas kininya pada acara utama dan menghasilkan lapisan visual tersuai. Contohnya:

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

Anda perlu mendapatkan kedudukan kursor terlebih dahulu, dan kemudian memproses dan menetapkan kandungan. Kemudian pulihkan kedudukan kursor.

Memulihkan kedudukan kursor adalah bahagian yang sukar apabila terdapat elemen bersarang. Tambahan pula, anda akan mencipta elemen baharu setiap kali dan yang lama akan dibuang.

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, "****") // bold
      .replace(/(?*") // 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;
}
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan