在文本区域上调用焦点会中断 document.getSelection()

Calling focus on a textarea breaks document.getSelection()

我目前正在尝试为我的 Svelte 应用程序构建一个增强的文本输入组件,使用户可以轻松输入化学物质 formulas/equations,但是在尝试创建将光标移动到单击文本输入时的正确位置,我 运行 遇到了问题。

我当前的 HTML 布局类似于以下内容:

<div class="input-field" contenteditable>
    <span class="left"> /* text left of the cursor goes here */  </span>
    <span class="caret"></span>
    <span class="right"> /* text right of the cursor goes here */ </span>
</div>
<textarea class="input-textarea"></textarea>

我正在使用文本区域监视 inputkeydown 事件,并使用来自这些事件的信息将正确的 text/special 字符插入 input-field div。 div 是 contenteditable,因为我希望能够看到用户试图将他们的插入符号放在何处,将真正的插入符号焦点转移到文本区域,并将我的模拟插入符号移动到适当的位置。

在将焦点转移到文本区域之前尝试从 input-field div 检索光标位置时出现此问题。如果任何地方存在任何代码试图随时将焦点转移到文本区域,那么 getSelection() 返回的结果将被破坏。如果 textarea.focus() 是同步完成的,使用 promises 异步完成的,使用 setTimeout 异步完成的,甚至放置在完全不同时间运行的不同函数中(由来自用户的不同事件触发),就会发生这种情况。删除此 focus 调用会使 getSelection 结果再次正确。 textarea 和 input 元素都会出现此问题。

getSelection 返回的结果以两种方式之一损坏:

  1. anchor/focus/base/extent 元素都是 body 元素或 #svelte 元素(div 包裹页面中的所有内容)。这不是结果应该是什么,它应该是 .left.right。偏移量也不正确。
  2. getSelection直接返回的结果是正确的,但是访问结果的任何属性都没有给出正确的值。这个很奇怪,我无法想象为什么代码行之间的值会不同,或者取决于甚至还没有执行的代码行。下面是 selectionselection.anchorNode 相继记录时的输出图像。

这种行为很奇怪,我很难找到解决这些巨大不一致的方法。任何帮助解释正在发生的事情的帮助将不胜感激。下面是我为演示此错误而制作的测试页面。

<!DOCTYPE html>
<html>
    <head>
        <script>
            function handleFocus() {
                let textarea = document.getElementById('textarea')
                console.dir(document.getSelection())
                setTimeout(() => {
                    textarea.focus()
                })
            }
        </script>
        <style>
            
        </style>
    </head>
    <body>
        <div contenteditable tabindex="1" onfocus="handleFocus()">This is some sample text, click on me</div>
        <textarea id="textarea" tabindex="1"></textarea>
    </body>
</html>

由于 getSelection 仅在事件发生后才是正确的,因此您必须不在处理程序中而是在事后调用 getSelection。 您可以通过在 setTimeout 中调用它来从处理程序中执行此操作,因为这会将它放到事件循环的最后。 这将是 svelte 的有效解决方案:

<script>
    import {tick} from 'svelte'
    let textarea;
    let div;
    
    function handleFocus() {
        setTimeout(() => {
            const focusOffset = window.getSelection().focusOffset
            console.log(focusOffset)
            textarea.focus()
        }, 0)
  }
</script>

<div bind:this={div} contenteditable on:focus="{handleFocus}">This is some sample text, click on me</div>
<textarea bind:this={textarea} class="input-textarea"></textarea>