Rumah > Soal Jawab > teks badan
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粉3930309172023-11-09 18:34:46
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粉0601123962023-11-09 14:42:18
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 < 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; }