javascript - HTML contenteditable:当内部 HTML 更改时保持插入符号位置
问题描述
我有一个充当所见即所得编辑器的 div。这充当文本框,但在其中呈现 markdown 语法,以显示实时更改。
问题:当输入一个字母时,插入符号的位置被重置为 div 的开头。
const editor = document.querySelector('div');
editor.innerHTML = parse('**dlob** *cilati*');
editor.addEventListener('input', () => {
editor.innerHTML = parse(editor.innerText);
});
function parse(text) {
return text
.replace(/\*\*(.*)\*\*/gm, '**<strong>$1</strong>**') // bold
.replace(/\*(.*)\*/gm, '*<em>$1</em>*'); // italic
}
div {
height: 100vh;
width: 100vw;
}
<div contenteditable />
代码笔:https : //codepen.io/ADAMJR/pen/MWvPebK
像 QuillJS 这样的 Markdown 编辑器似乎在不编辑父元素的情况下编辑子元素。这避免了问题,但我现在确定如何使用此设置重新创建该逻辑。
问题:如何让插入符号位置在打字时不重置?
更新:我已经设法在每个输入上将插入符号位置发送到 div 的末尾。然而,这仍然基本上重置了位置。https://codepen.io/ADAMJR/pen/KKvGNbY
解决方案
您需要先获取光标的位置,然后处理并设置内容。然后恢复光标位置。
当存在嵌套元素时,恢复光标位置是一个棘手的部分。此外,您每次都在创建新元素<strong>
,<em>
旧元素被丢弃。
const editor = document.querySelector('.editor');
editor.innerHTML = parse('For **bold** two stars. For *italic* one star. Some more **bold**.');
editor.addEventListener('input', () => {
//get current cursor position
let sel = window.getSelection();
let node = sel.focusNode;
let offset = sel.focusOffset;
let pos = getCursorPosition(editor, node, offset, {pos: 0, done: false});
//console.log('Position: ', pos);
editor.innerHTML = parse(editor.innerText);
// restore the position
sel.removeAllRanges();
let 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, '**<strong>$1</strong>**') // bold
.replace(/(?<!\*)\*(?!\*)(.*?)(?<!\*)\*(?!\*)/gm, '*<em>$1</em>*'); // italic
}
// 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 (var 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 (var i = 0; i < parent.childNodes.length && !stat.done; i++) {
currentNode = parent.childNodes[i];
setCursorPosition(currentNode, range, stat);
}
}
return range;
}
.editor {
height: 100px;
width: 300px;
border: 1px solid #888;
padding: 0.5rem;
}
em,strong{
font-size: 1.3rem;
}
<div class='editor' contenteditable />
API window.getSelection返回节点和相对于它的位置。每次创建全新元素时,我们都无法使用旧节点对象恢复位置。因此,为了保持简单并拥有更多控制权,我们将获得相对于.editor
usinggetCursorPosition
函数的位置。并且,在我们设置了 innerHTML 内容之后,我们使用setCursorPosition
.
这两个函数都适用于嵌套元素。
此外,改进了正则表达式:使用 (.*?) 惰性量词和前瞻和后视,以实现更好的匹配。你可以找到更好的表达方式。
笔记:
- 我已经在 Windows 10 上的 Chrome 97 上测试了代码。
- 在演示中使用递归解决方案
getCursorPosition
并setCursorPosition
使其保持简单。
推荐阅读
- model - Committee Methods (ensemble learners) Best Model
- kubernetes - Magento CLI 设置,指定的实体类型无效:catalog_category
- javascript - JS Promise - 返回一些结果而不解决承诺
- css - 类名倒序
- ios - dyld:未加载库:@rpath/GoogleInteractiveMediaAds.framework/GoogleInteractiveMediaAds 自定义框架集成中的错误
- linux - 如何使用 Bash 收缩/收缩一组值
- python - 如何将 django 1.11 移植到 django 2.2 和 python 2.7 到 3.5?
- powerbi - 总和基于值是否在不同的表中可用
- amazon-web-services - 如何使用氡的 JSON 结果在 Athena 中创建表
- linux-kernel - Linux设备树:如何解析包含单元列表的属性