Maison > Questions et réponses > le corps du texte
J'ai un div qui fait office d'éditeur WYSIWYG. Il agit comme une zone de texte mais y restitue la syntaxe Markdown pour afficher les modifications en direct.
Problème : Lors de la saisie de lettres, la position du curseur est réinitialisée au début du 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
Les éditeurs Markdown comme QuillJS semblent être capables de modifier les éléments enfants sans modifier l'élément parent. Cela évite le problème, mais je sais maintenant comment recréer cette logique avec cette configuration.
Question : Comment empêcher la réinitialisation de la position du curseur lors de la frappe ?
Mise à jour : J'ai réussi à envoyer la position du curseur à la fin du div sur chaque entrée. Cependant, cela réinitialise essentiellement la position. https://codepen.io/ADAMJR/pen/KKvGNbY
P粉3930309172023-11-09 18:34:46
La plupart des éditeurs de texte enrichi conservent leur propre état interne, le mettent à jour en fonction des événements clés et restituent une couche visuelle personnalisée. Par exemple :
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粉0601123962023-11-09 14:42:18
Vous devez d'abord obtenir la position du curseur, puis traiter et définir le contenu. Restaurez ensuite la position du curseur.
Restaurer la position du curseur est la partie la plus délicate lorsqu'il y a des éléments imbriqués. De plus, vous créerez de nouveaux éléments 和
à chaque fois et les anciens seront supprimés.
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 < parent.childNodes.length && !stat.done; i++) { currentNode = parent.childNodes[i]; if (currentNode === node) { stat.pos += offset; stat.done = true; return stat; } else getCursorPosition(currentNode, node, offset, stat); } } return stat; } //find the child node and relative position and set it on range function setCursorPosition(parent, range, stat) { if (stat.done) return range; if (parent.childNodes.length == 0) { if (parent.textContent.length >= stat.pos) { range.setStart(parent, stat.pos); stat.done = true; } else { stat.pos = stat.pos - parent.textContent.length; } } else { for (let i = 0; i < parent.childNodes.length && !stat.done; i++) { currentNode = parent.childNodes[i]; setCursorPosition(currentNode, range, stat); } } return range; }
.editor { height: 100px; width: 400px; border: 1px solid #888; padding: 0.5rem; white-space: pre; } em, strong{ font-size: 1.3rem; }