[英]javascript : focusOffset with html tags
我有一个 contenteditable div 如下(| = 光标位置):
<div id="mydiv" contenteditable="true">lorem ipsum <spanclass="highlight">indol|or sit</span> amet consectetur <span class='tag'>adipiscing</span> elit</div>
我想获取当前光标位置,包括 html 标签。 我的代码:
var offset = document.getSelection().focusOffset;
Offset 返回 5(来自最后一个标签的全文),但我需要它来处理 html 标签。 预期的返回值为 40。该代码必须适用于所有最近浏览器。 (我还检查了这个: window.getSelection() offset with HTML tags?但它没有回答我的问题)。 有任何想法吗 ?
编辑:这是一个旧答案,不适用于 OP 要求节点具有相同文本的要求。 但如果你没有这个要求,它会更干净、更轻便。
这是您可以使用并且适用于所有主要浏览器的一个选项:
document.getSelection().anchorOffset
)document.getSelection().anchorNode.data
)indexOf()
在#mydiv
获取该文本的偏移量对于您的特定情况,代码如下所示:
var offset = document.getSelection().anchorOffset;
var text = document.getSelection().anchorNode.data;
var textOffset = $("#mydiv").html().indexOf( text );
offsetCaret = textOffset + offset;
您可以在此 JSFiddle 上看到一个工作演示(查看控制台以查看结果)。
还有一个更通用的函数版本(允许将div
作为参数传递,因此它可以与不同的contenteditable
一起使用)在另一个 JSFiddle 上:
function getCaretHTMLOffset(obj) {
var offset = document.getSelection().anchorOffset;
var text = document.getSelection().anchorNode.data;
var textOffset = obj.innerHTML.indexOf( text );
return textOffset + offset;
}
关于这个答案
另一种方法是在 DOM 中添加一个临时标记并计算与该标记的偏移量。 该算法在感兴趣的div
的内部序列化( innerHTML
)中查找标记(其outerHTML
)的 HTML 序列化。 重复文本不是这个解决方案的问题。
为此,标记的序列化在其 div 中必须是唯一的。 您无法控制用户在字段中输入的内容,但您可以控制放入 DOM 的内容,因此这应该不难实现。 在我的示例中,标记是静态唯一的:通过选择一个不太可能提前引起冲突的类名。 也可以通过检查 DOM 并更改类直到它是唯一的来动态执行此操作。
我有一个小提琴(源自阿尔瓦罗蒙托罗自己的小提琴)。 主要部分是:
function getOffset() {
if ($("." + unique).length)
throw new Error("marker present in document; or the unique class is not unique");
// We could also use rangy.getSelection() but there's no reason here to do this.
var sel = document.getSelection();
if (!sel.rangeCount)
return; // No ranges.
if (!sel.isCollapsed)
return; // We work only with collapsed selections.
if (sel.rangeCount > 1)
throw new Error("can't handle multiple ranges");
var range = sel.getRangeAt(0);
var saved = rangy.serializeSelection();
// See comment below.
$mydiv[0].normalize();
range.insertNode($marker[0]);
var offset = $mydiv.html().indexOf($marker[0].outerHTML);
$marker.remove();
// Normalizing before and after ensures that the DOM is in the same shape before
// and after the insertion and removal of the marker.
$mydiv[0].normalize();
rangy.deserializeSelection(saved);
return offset;
}
如您所见,代码必须补偿在 DOM 中添加和删除标记,因为这会导致当前选择丢失:
Rangy用于保存选择并在之后恢复它。 请注意,保存和恢复可以用比 Rangy 更轻的东西来完成,但我不想用细节加载答案。 如果您决定使用 Rangy 执行此任务,请阅读文档,因为可以优化序列化和反序列化。
为了让 Rangy 工作,DOM 必须在保存前后处于完全相同的状态。 这就是在添加标记之前和删除标记之后调用normalize()
原因。 这样做是将紧邻的文本节点合并为一个文本节点。 问题是向 DOM 添加标记会导致文本节点被分成两个新的文本节点。 这会导致选择丢失,如果不通过规范化撤消,将导致 Rangy 无法恢复选择。 同样,比调用normalize
更轻松的方法可以解决问题,但我不想用细节加载答案。
注意:此解决方案甚至适用于具有重复文本的节点,但它仅将 html 实体(例如:
)检测为一个字符。
我想出了一个基于处理节点的完全不同的解决方案。 它不像旧答案那么干净(请参阅其他答案),但即使存在具有相同文本的节点(OP 要求),它也能正常工作。
这是它如何工作的描述:
代码是这样的:
function getCaretOffset(contentEditableDiv) {
// read the node in which the caret is and store it in a stack
var aux = document.getSelection().anchorNode;
var stack = [ aux ];
// add the parents to the stack until we get to the content editable div
while ($(aux).parent()[0] != contentEditableDiv) { aux = $(aux).parent()[0]; stack.push(aux); }
// traverse the contents of the editable div until we reach the one with the caret
var offset = 0;
var currObj = contentEditableDiv;
var children = $(currObj).contents();
while (stack.length) {
// add the lengths of the previous "siblings" to the offset
for (var x = 0; x < children.length; x++) {
if (children[x] == stack[stack.length-1]) {
// if the node is not a text node, then add the size of the opening tag
if (children[x].nodeType != 3) { offset += $(children[x])[0].outerHTML.indexOf(">") + 1; }
break;
} else {
if (children[x].nodeType == 3) {
// if it's a text node, add it's size to the offset
offset += children[x].length;
} else {
// if it's a tag node, add it's size + the size of the tags
offset += $(children[x])[0].outerHTML.length;
}
}
}
// move to a more inner container
currObj = stack.pop();
children = $(currObj).contents();
}
// finally add the offset within the last node
offset += document.getSelection().anchorOffset;
return offset;
}
你可以在这个 JSFiddle 上看到一个工作演示。
关于这个答案:
这样的 html 实体
仅计为一个字符。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.