【发布时间】:2012-05-11 20:02:36
【问题描述】:
我研究了所有 Rangy Q&A 好几天,但无法适应这种情况。
我有以下内容可编辑
<div id="area" style="width:100%;height:2em;"
contentEditable="true";
onkeyup="formatText();"
></div>
调用一个函数,每次用户输入内容时,它都会解析内容并格式化特定的标记。
function formatText() {
var el = document.getElementById('area');
var savedSel = saveSelection(el); // calls Rangy function
var tokenColor;
// removes html tags before passing the expression to the parser
var userInput = document.getElementById('area').innerHTML.replace(/(<([^>]+)>)/g,"").replace(/&/g, "").replace(/>/g, ">").replace(/</g, "<").replace(/<span[^>]*>+<\/span>/, "");
var i, newHTML=[];
tokenType=[]; // [NUMBER,+,(,NUMBER,..]
tokenArray=[]; // [3,+,(5,...]
var resultOutput = parse(userInput); // parser also fills tokenType and tokenArray
for (i=0; i<tokenArray.length-1; i++){
newHTML += "<span style='color: " + tokenColor + " '>" + tokenArray[i] + "</span>";
} // newHTML looks like <span style='color: red'>3</span><span style='color: black'>+</span> etc.
el.innerHTML = newHTML; // replaces content of <div> with formatted text
restoreSelection(el, savedSel); // calls Rangy function to restore cursor position
}
我使用了作者在本论坛其他帖子中提出的以下基于 Rangy 的函数:
function saveSelection(containerEl) {
var charIndex = 0, start = 0, end = 0, foundStart = false, stop = {};
var sel = rangy.getSelection(), range;
function traverseTextNodes(node, range) {
if (node.nodeType == 3) {
if (!foundStart && node == range.startContainer) {
start = charIndex + range.startOffset;
foundStart = true;
}
if (foundStart && node == range.endContainer) {
end = charIndex + range.endOffset;
throw stop;
}
charIndex += node.length;
} else {
for (var i = 0, len = node.childNodes.length; i < len; ++i) {
traverseTextNodes(node.childNodes[i], range);
}
}
}
if (sel.rangeCount) {
try {
traverseTextNodes(containerEl, sel.getRangeAt(0));
} catch (ex) {
if (ex != stop) {
throw ex;
}
}
}
return {
start: start,
end: end
};
}
function restoreSelection(containerEl, savedSel) {
var charIndex = 0, range = rangy.createRange(), foundStart = false, stop = {};
range.collapseToPoint(containerEl, 0);
function traverseTextNodes(node) {
if (node.nodeType == 3) {
var nextCharIndex = charIndex + node.length;
if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
range.setStart(node, savedSel.start - charIndex);
foundStart = true;
}
if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
range.setEnd(node, savedSel.end - charIndex);
throw stop;
}
charIndex = nextCharIndex;
}
else {
for (var i = 0, len = node.childNodes.length; i < len; ++i) {
traverseTextNodes(node.childNodes[i]);
}
}
}
try {
traverseTextNodes(containerEl);
} catch (ex) {
if (ex == stop) {
rangy.getSelection().setSingleRange(range);
} else {
throw ex;
}
}
}
在我尝试删除一个字符之前,一切正常。此时光标会跳到div的开头。
知道为什么会发生这种情况吗?
非常感谢。
【问题讨论】:
-
您销毁并用innerHTML替换了div的全部内容,因此被选中的节点不再存在。您应该手动删除不需要的节点,而不是使用 innerHTML,而让其他节点保持原样。
-
@GGG:第二个代码块对选择进行基于字符索引的保存和恢复,所以只要可见文本在
innerHTML替换后保持不变,它应该可以工作。 -
@TimDown 我现在明白你的意思了,我之前只是略过那部分。我仍然对这项工作持怀疑态度。一些正则表达式看起来会创建损坏的标记。
'<span></span>'.replace(/(<([^>]+)>)/, '') // "</span>" -
@GGG:我将以下链接用于正则表达式pagecolumn.com/tool/all_about_html_tags.htm。您能否建议一个更好的选择来删除所有没有内容的跨度标签?这也应该删除像 这样的空跨度和带有隐藏字符的跨度。如果 javascript 用特殊的隐藏字符替换 DEL,可能在按下删除时会创建一些奇怪的跨度?
标签: javascript dom contenteditable rangy