你的假设是正确的。使用innerHTML 进行替换意味着浏览器必须丢弃 div 中的所有节点并从您提供的 HTML 字符串创建新节点,这意味着之前存在的选择的节点不再存在。这是非常低效的,尤其是在每次按键时都这样做。您需要改用 DOM 方法来执行此操作。
我建议在进行任何替换之前等待一段时间的键盘不活动,而不是在每次按键后都这样做。
最后,这需要在哪些浏览器中工作?
更新
我认为这是一个有趣的问题,所以我写了一个完整的解决方案。我也改变了使用innerHTML 的想法。复杂的一点是您必须使用完全不同的方法从所有其他浏览器中保存和恢复 IE 中的选择。我还将它更改为使用contenteditable 而不是designMode。这排除了 Firefox 2,但它简化了一些事情,所以我希望没问题。
<script type="text/javascript">
function getBoundary(el, textNodes, charIndex) {
var charsSoFar = 0, textNodeLength;
// Walk text nodes
for (var i = 0, len = textNodes.length; i < len; ++i) {
textNodeLength = textNodes[i].data.length;
charsSoFar += textNodeLength;
if (charsSoFar >= charIndex) {
return {
node: textNodes[i],
offset: charIndex + textNodeLength - charsSoFar
};
}
}
throw new Error("Boundary not found");
}
function highlightVars() {
var myDiv = document.getElementById('myDiv');
var selectedRange, range, divText;
var selectionStartPos, selectionLength;
var hasRanges = !!(window.getSelection
&& document.createRange);
var hasTextRanges = !!(document.selection
&& document.body.createTextRange);
// Get the selection text position within the div
if (hasRanges) {
selectedRange = window.getSelection().getRangeAt(0);
range = document.createRange();
range.selectNodeContents(myDiv);
divText = range.toString();
range.setEnd(selectedRange.startContainer, selectedRange.startOffset);
selectionStartPos = range.toString().length;
selectionLength = selectedRange.toString().length;
} else if (hasTextRanges) {
selectedRange = document.selection.createRange();
range = document.body.createTextRange();
range.moveToElementText(myDiv);
divText = range.text;
range.setEndPoint("EndToStart", selectedRange);
selectionStartPos = range.text.length;
selectionLength = selectedRange.text.length;
}
// Substitute in existing text with vars highlighted
myDiv.innerHTML = divText.replace(/(@[a-z0-9_]+)/gi,
'<strong>$1</strong>').replace(/ $/, "\u00a0");
// Restore selection
if (hasRanges) {
var textNodes = [];
for (var n = myDiv.firstChild; n; n = n.nextSibling) {
textNodes.push( (n.nodeType == 1) ? n.firstChild : n );
}
var selectionStartBoundary = getBoundary(myDiv, textNodes,
selectionStartPos);
var selectionEndBoundary = getBoundary(myDiv, textNodes,
selectionStartPos + selectionLength);
range.setStart(selectionStartBoundary.node,
selectionStartBoundary.offset);
range.setEnd(selectionEndBoundary.node,
selectionEndBoundary.offset);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (hasTextRanges) {
range.moveToElementText(myDiv);
range.moveStart("Character", selectionStartPos);
range.collapse();
range.moveEnd("Character", selectionLength);
range.select();
}
}
var keyTimer;
function myKeyDown() {
if (keyTimer) {
window.clearTimeout(keyTimer);
}
keyTimer = window.setTimeout(function() {
keyTimer = null;
highlightVars();
}, 1000);
}
function init() {
var myDiv = document.getElementById("myDiv");
myDiv.onkeydown = myKeyDown;
}
window.onload = init;
</script>
<body>
<div id="myDiv" contenteditable="true">This is my variable
name: @varname. If I type here things go wrong...</div>
</body>