双击 contenteditable 元素进行聚焦(看起来不那么容易)

Doubleclick contenteditable element to focus (not as easy at it seems)

提问人:Lukas 提问时间:11/10/2023 更新时间:11/11/2023 访问量:61

问:

我一直在努力寻找解决这个问题的方法,所以我现在要在这里问。

从一个普通的 contenteditable div 元素开始,如下所示:

<div contenteditable>Edit this text</div>

如果您单击文本,您将能够使用光标在您单击的位置进行编辑。

现在,我不希望文本在第一次单击时是可编辑的,并且我不希望在元素未聚焦时与文本进行交互。我可以通过禁用 div、使用 CSS 或覆盖元素打开 contenteditable 来实现这一点。

我现在希望该元素在双击时再次可编辑,其行为与您正常单击该元素所期望的行为完全相同。让我们使用 JavaScript 和 EventListener 来实现 or 为此。onmousedownondblclick

示例 1

<div id="wrapper">
    <div id="overlay"></div>
    <div id="editable" contenteditable>Edit this text</div>
</div>
overlay.ondblclick = () => {
    overlay.style.display = 'none';
    editable.focus();
};

// use onblur to restore the overlay 

这有效,但光标将位于元素的开头。如果不使用 ,则必须第三次单击以聚焦元素。focus()

示例 2

<div id="editable" contenteditable="false">Edit this text</div>
editable.ondblclick = () => {
    editable.setAttribute('contenteditable', true);
};

// use onblur to set contenteditable to false again
// additional CSS will prevent being able to interact with the text if not focused

这是可行的,但您将选择您要单击的单词,而不是将文本光标放在那里。

这是针对私人项目的,可访问性、兼容性等无关紧要。

javascript html css dom-events contenteditable

评论

0赞 Mark Schultheiss 11/10/2023
也许使用 HTML、CSS 和 JavaScript 创建一个功能片段来运行并清楚地说明您的一个目标,即“我正在尝试为这个问题找到解决方案”,而当前代码无法满足该目标。

答:

0赞 slugcat-dev 11/11/2023 #1

经过几个小时的研究,我得出了这个解决方案:

const editable = document.getElementById('editable');

editable.ondblclick = (event) => {
  if (document.activeElement === event.target) return;

  editable.setAttribute('contenteditable', true);
  selectRange(getMouseEventCaretRange(event));
}

function getMouseEventCaretRange(event) {
    if (event.rangeOffset !== undefined) {
        // The new fancy Firefox-only way that doesn't even have documentation
        let range = document.createRange();

        range.setStart(event.rangeParent, event.rangeOffset);
        range.collapse(true);

        return range;
    } else if (document.caretPositionFromPoint) {
        // The 'official' way (only used by Firefox) that doesn't even position the cursor correctly
        // VSC keeps telling me the method doesn't exist
        let pos = document.caretPositionFromPoint(event.clientX, event.clientY);
        let range = document.createRange();

        range.setStart(pos.offsetNode, pos.offsetX);
        range.collapse(true);

        return range;
    } else if (document.caretRangeFromPoint) {
        // The 'deprecated WebKit-proprietary fallback method' (used by every mf browser out there)
        return document.caretRangeFromPoint(event.clientX, event.clientY);
    }
}

function selectRange(range) {
    const sel = window.getSelection();

    sel.removeAllRanges();
    sel.addRange(range);
}
* {
  font-family: monospace;
}

#editable {
  font-size: 1.5rem;
 }

#editable[contenteditable="false"] {
  user-select: none;
  cursor: pointer;
}

#editable[contenteditable="false"]::selection {
  background-color: transparent;
 }
<div id="editable" contenteditable="false" onblur="this.setAttribute('contenteditable', false);">Double click to edit this text</div>
<ul>
  <li>Caret is placed where clicked, not at the start</li>
  <li>Double clicked word is not highlighted</li>
  <li>Text can not be interacted with if the element isn't focused</li>
</ul>

它很长,很复杂,但它有效。在我看来,这方面的 API 完全搞砸了。我希望这能在未来帮助到某人。

0赞 JPC 11/11/2023 #2

如果您愿意在双击之前允许指针事件,用光标隐藏该功能,则无需任何复杂操作,只需在添加 contenteditable 时阻止双击操作,同时确保它处于焦点中,并且原始单击将失败。

document.addEventListener('mousedown', (event) => {
  if (event.detail > 1) { // double-clicked
    const e = event.target;
    if (e.id === "content" && !['true', 'plaintext-only'].includes(e.contentEditable)) {
      event.preventDefault();
      e.style.cursor = 'auto';
      e.contentEditable = true;
      e.focus();
    }
  }
}, false);
#content {
  cursor: default;
}
<div id="content">Edit this text</div>