繁体   English   中英

javascript:带有html标签的focusOffset

[英]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 要求节点具有相同文本的要求。 但如果你没有这个要求,它会更干净、更轻便。

这是您可以使用并且适用于所有主要浏览器的一个选项:

  1. 获取插入符号在其节点内的偏移量( document.getSelection().anchorOffset
  2. 获取插入符号所在节点的文本( document.getSelection().anchorNode.data
  3. 使用indexOf()#mydiv获取该文本的偏移量
  4. 将 1 和 3 中获得的值相加,以获得 div 中插入符号的偏移量。

对于您的特定情况,代码如下所示:

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;

}

关于这个答案

  • 它将按要求在所有最近的浏览器中工作(在 Chrome 42、Firefox 37 和 Explorer 11 上测试)。
  • 它短小精悍,不需要任何外部库(甚至不需要 jQuery)
  • 问题:如果具有相同文本的不同节点,它可能会返回第一次出现的偏移量而不是插入符号的实际位置。

另一种方法是在 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 中添加和删除标记,因为这会导致当前选择丢失:

  1. Rangy用于保存选择并在之后恢复它。 请注意,保存和恢复可以用比 Rangy 更轻的东西来完成,但我不想用细节加载答案。 如果您决定使用 Rangy 执行此任务,请阅读文档,因为可以优化序列化和反序列化。

  2. 为了让 Rangy 工作,DOM 必须在保存前后处于完全相同的状态。 这就是在添加标记之前和删除标记之后调用normalize()原因。 这样做是将紧邻的文本节点合并为一个文本节点。 问题是向 DOM 添加标记会导致文本节点被分成两个新的文本节点。 这会导致选择丢失,如果不通过规范化撤消,将导致 Rangy 无法恢复选择。 同样,比调用normalize更轻松的方法可以解决问题,但我不想用细节加载答案。

注意:此解决方案甚至适用于具有重复文本的节点,但它仅将 html 实体(例如: &nbsp; )检测为一个字符。

我想出了一个基于处理节点的完全不同的解决方案。 它不像旧答案那么干净(请参阅其他答案),但即使存在具有相同文本的节点(OP 要求),它也能正常工作。

这是它如何工作的描述:

  1. 使用插入符号所在节点的所有父元素创建一个堆栈。
  2. 当堆栈不为空时,遍历包含元素的节点(最初是内容可编辑的 div)。
  3. 如果节点与堆栈顶部的节点不同,则将其大小添加到偏移量中。
  4. 如果节点与栈顶节点相同:从栈中弹出,转步骤2。

代码是这样的:

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 上看到一个工作演示


关于这个答案:

  • 它适用于所有主要浏览器。
  • 它很轻,不需要外部库(除了 jQuery)
  • 它有一个问题:像&nbsp;这样的 html 实体&nbsp; 仅计为一个字符。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM