javascript 在可编辑的 div 静止图像之外单击是否会聚焦它?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/34354085/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 17:36:36  来源:igfitidea点击:

Clicking outside a contenteditable div stills give focus to it?

javascripthtmlcss

提问by cytsunny

For some reason I need to use contenteditable div instead of normal text input for inputting text. (for some javascript library) It works fine until I found that when I set the contenteditable div using display: inline-block, it gives focus to the div even if I click outside the div!

出于某种原因,我需要使用 contenteditable div 而不是普通的文本输入来输入文本。(对于某些 javascript 库)它工作正常,直到我发现当我使用 设置 contenteditable div 时display: inline-block,即使我在 div 外部单击,它也会将焦点放在 div 上!

I need it to be giving focus to the div only when the user clicks right onto the div but not around it. Currently, I found that when the user clicks elsewhere and then click at position that is the same row as the div, it gives focus to it.

我需要它只在用户点击 div 而不是它周围时才将焦点放在 div 上。目前,我发现当用户点击其他地方,然后点击与 div 相同的行的位置时,它会给它焦点。

A simple example to show the problem:

一个简单的例子来说明问题:

HTML:

HTML:

<div class="outside">
    <div class="text-input" contenteditable="true">
        Input 1
    </div>
    <div class="text-input" contenteditable="true">
        Input 2
    </div>
    <div class="unrelated">This is some unrelated content<br>
      This is some more unrelated content
      This is just some space to shows that clicking here doesn't mess with the contenteditable div
      but clicking the side mess with it.
    </div>
</div>

CSS:

CSS:

div.outside {
  margin: 30px;
}
div.text-input {
  display:inline-block;
  background-color: black;
  color: white;
  width: 300px;
}

The JSFiddle for displaying the problem

用于显示问题的 JSFiddle

Is there a way (CSS or javascript are both acceptable) to make the browser only give focus to div when it is clicked instead of clicking the same row?

有没有办法(CSS 或 javascript 都可以接受)让浏览器只在点击 div 而不是点击同一行时才将焦点放在 div 上?

P.S. I noticed that there are similar problem (link to other related post), but the situation is a bit different and the solution provided is not working for me.

PS 我注意到有类似的问题(链接到其他相关帖子),但情况有点不同,提供的解决方案对我不起作用。

采纳答案by DoctorDestructo

Explanation(if you don't care, skip to the Workaroundsbelow)

说明(如果您不在乎,请跳到下面的解决方法

When you click in an editableelement, the browser places a cursor (a.k.a. insertion point) in the nearest text nodethat is within the clicked element, on the same line as your click. The text node may be either directly within the clicked element, or in one of its child elements. You can verify this by running the code snippet below and clicking around in the large blue box.

当您单击可编辑元素时,浏览器会将光标(又名插入点)放置在所单击元素内最近的文本节点中,与您的单击位于同一行。文本节点可以直接位于单击的元素中,也可以位于其子元素之一中。您可以通过运行下面的代码片段并单击蓝色大框来验证这一点。

.container {width: auto; padding: 20px; background: cornflowerblue;}
.container * {margin: 4px; padding: 4px;}
div {width: 50%; background: gold;}
span {background: orange;}
span > span {background: gold;}
span > span > span {background: yellow;}
<div class="container" contenteditable>
  text in an editable element
  <div>
    text in a nested div
  </div>
  <span><span><span>text in a deeply nested span</span></span></span></div>
Notice that you can get an insertion point by clicking above the first line or below the last. This is because the "hitbox" of these lines extends to the top and bottom of the container, respectively. Some of the other answers don't account for this!

The blue box is a <div>with the contenteditableattribute, and the inner orange/yellow boxes are nested child elements. Notice that if you click near (but not in) one of the child elements, the cursor ends up inside it, even though you clicked outside. This is not a bug. Since the element you clicked on (the blue box) is editable and the child element is part of its content, it makes sense to place the cursor in the child element if that's where the nearest text node happens to be.

蓝色框是<div>带有contenteditable属性的,里面的橙色/黄色框是嵌套的子元素。请注意,如果您在子元素之一附近(而不是在其中)单击,即使您在外部单击,光标也会在其中结束。这不是错误。由于您单击的元素(蓝色框)是可编辑的,并且子元素是其内容的一部分,因此如果子元素恰好是最近的文本节点所在的位置,则将光标放在子元素中是有意义的。

The problem is that Webkit browsers (Chrome, Safari, Opera) exhibit this same behavior when contenteditableis set on the child instead of the parent. The browser shouldn't even bother looking for the nearest text node in this case since the element you actually clicked on isn't editable. But Webkit does, and if that text node happens to be in the editable child, you get a blinking cursor. I'd consider that a bug; Webkit browsers are doing this:

问题是 Webkit 浏览器(Chrome、Safari、Opera)contenteditable在子级而不是父级上设置时表现出相同的行为。在这种情况下,浏览器甚至不应该费心寻找最近的文本节点,因为您实际单击的元素不可编辑。但 Webkit 确实如此,如果该文本节点恰好位于可编辑子节点中,则会出现一个闪烁的光标。我认为这是一个错误;Webkit 浏览器正在这样做:

on click:
  find nearest text node within clicked element;
  if text node is editable:
    add insertion point;

...when they should be doing this:

...什么时候他们应该这样做:

on click:
  if clicked element is editable:
    find nearest text node within clicked element;
    add insertion point;

Block elements (such as divs) don't seem to be affected by the bug, which makes me think @GOTO 0's answeris correct in implicating text selection-- at least insofar as it seems to be governed by the same logic that controls insertion point placement. Multi-clicking outside an inline element highlights the text within it, but not so for block elements. It's probably no coincidence that you also don't get an insertion point when you click outside a block. The first workaround below makes use of this exception.

块元素(例如 div)似乎不受该错误的影响,这让我认为@GOTO 0 的答案在暗示文本选择方面是正确的——至少在它似乎受控制插入的相同逻辑控制的情况下点放置。在内联元素外多次单击会突出显示其中的文本,但对于块元素则不然。当您在块外单击时也没有获得插入点,这可能并非巧合。下面的第一个解决方法利用了这个异常。



Workaround 1 (nested div)

解决方法 1(嵌套 div)

Since blocks aren't affected by the bug, I think the best solution is to nest a div in the inline-block and make it editable instead. Inline-blocks already behave like blocks internally, so the div should have no effect on its behavior.

由于块不受该错误的影响,我认为最好的解决方案是在 inline-block 中嵌套一个 div 并使其可编辑。内联块已经在内部表现得像块,所以 div 应该对其行为没有影响。

div.outside {
  margin: 30px;
}
div.text-input {
  display:inline-block;
  background-color: black;
  color: white;
  width: 300px;
}
<div class="outside">
    <div class="text-input">
      <div contenteditable>
        Input 1
      </div>
    </div>
    <div class="text-input">
      <div contenteditable>
        Input 2
      </div>
    </div>
    <div class="unrelated">This is some unrelated content<br>
      This is some more unrelated content
      This is just some space to shows that clicking here doesn't mess with the contenteditable div
      but clicking the side mess with it.
    </div>
</div>



Workaround 2 (invisible characters)

解决方法 2(不可见字符)

If you must put the contenteditableattribute on the inline-blocks, this solution will allow it. It works by surrounding the inline-blocks with invisible characters (specifically, zero-width spaces) which shield them from external clicks. (GOTO 0's answeruses the same principle, but it still had some problems last I checked).

如果您必须将contenteditable属性放在 inline-blocks 上,此解决方案将允许它。它的工作原理是用不可见字符(特别是零宽度空格)包围内联块,以保护它们免受外部点击。(GOTO 0's answer使用相同的原理,但上次我检查它仍然存在一些问题)。

div.outside {
  margin: 30px;
}
div.text-input {
  display:inline-block;
  background-color: black;
  color: white;
  width: 300px;
  white-space: normal;
}
.input-container {white-space: nowrap;}
<div class="outside">
  <span class="input-container">&#8203;<div class="text-input" contenteditable>
    Input 1
  </div>&#8203;</span>
  <span class="input-container">&#8203;<div class="text-input" contenteditable>
    Input 2
  </div>&#8203;</span>
  <div class="unrelated">This is some unrelated content<br>
      This is some more unrelated content
      This is just some space to shows that clicking here doesn't mess with the contenteditable div
      but clicking the side mess with it.
  </div>
</div>



Workaround 3 (javascript)

解决方法 3 (javascript)

If you absolutely can't change your markup, then this JavaScript-based solution could work as a last resort (inspired by this answer). It sets contentEditableto true when the inline-blocks are clicked, and false when they lose focus.

如果您绝对不能更改您的标记,那么这个基于 JavaScript 的解决方案可以作为最后的手段(受此答案的启发)。contentEditable当内嵌块被点击时它设置为真,当它们失去焦点时设置为假。

(function() {
  var inputs = document.querySelectorAll('.text-input');
  for(var i = inputs.length; i--;) {
    inputs[i].addEventListener('click', function(e) {
      e.target.contentEditable = true;
      e.target.focus();
    });
    inputs[i].addEventListener('blur', function(e) {
      e.target.contentEditable = false;
    });
  }
})();
div.outside {
  margin: 30px;
}
div.text-input {
  display:inline-block;
  background-color: black;
  color: white;
  width: 300px;
}
<div class="outside">
    <div class="text-input">
      Input 1
    </div>
    <div class="text-input">
      Input 2
    </div>
    <div class="unrelated">This is some unrelated content<br>
      This is some more unrelated content
      This is just some space to shows that clicking here doesn't mess with the contenteditable div
      but clicking the side mess with it.
    </div>
</div>

回答by GOTO 0

I was able to reproduce this behavior only in Chrome and Safari, suggesting that this may be a Webkit related issue.

我只能在 Chrome 和 Safari 中重现此行为,这表明这可能是与 Webkit 相关的问题。

It's hard to tell what's going on without inspecting the code but we can at least suspect that the problem lies in some faulty mechanism that triggers text selection in the browser. For analogy, if the divs were not contenteditable, clicking in the same line of text after the last character would trigger a text selection starting at the end of the line.

如果不检查代码就很难知道发生了什么,但我们至少可以怀疑问题出在某些触发浏览器中文本选择的错误机制中。打个比方,如果divs 不可编辑,则在最后一个字符之后的同一行文本中单击将触发从该行末尾开始的文本选择。

The workaround is to wrap the contenteditable divs into a container element and style the container with -webkit-user-select: noneto make it unselectable.

解决方法是将 contenteditable 包装div到一个容器元素中,并用-webkit-user-select: none它来设置容器的样式以使其不可选择。

As Alex Char points out in a comment, this will not prevent a mouse click outside the container to trigger a selection at the start of the text inside it, since there is no static text between the first contenteditable divand the (selectable) ancestor container around it. There are likely more elegant solutions, but a way to overcome this problem is to insert an invisible, nonempty span of text of zero width just before the first contenteditable divto capture the unwanted text selection.

正如 Alex Char 在评论中指出的那样,这不会阻止在容器外单击鼠标以在其中的文本开头触发选择,因为第一个 contenteditablediv和周围的(可选择的)祖先容器之间没有静态文本它。可能有更优雅的解决方案,但克服这个问题的一种方法是在第一个 contenteditable 之前插入一个不可见的、非空的零宽度文本范围,div以捕获不需要的文本选择。

  • Why non empty?: Because empty elements are ignored upon text selection.
  • Why zero width?: Because we don't want to see it...
  • Why invisible?: Because we don't want the content to be copied to the clipboard with, say Ctrl+A, Ctrl+C.
  • 为什么非空?: 因为在选择文本时会忽略空元素。
  • 为什么是零宽度?: 因为不想看...
  • 为什么看不见?:因为我们不希望将内容复制到剪贴板,例如Ctrl+ACtrl+C

div.outside {
  margin: 30px;
}
div.text-input {
  display:inline-block;
  background-color: black;
  color: white;
  width: 300px;
}
div.text-input-container {
  -webkit-user-select: none;
}
.invisible {
  visibility: hidden;
}
<div class="outside">
    <div class="text-input-container">
        <span class="invisible">&#8203;</span><div class="text-input" contenteditable="true">
            Input 1
        </div>
        <div class="text-input" contenteditable="true">
            Input 2
        </div>
    </div>
    <div class="unrelated">This is some unrelated content<br>
      This is some more unrelated content
      This is just some space to shows that clicking here doesn't mess with the contenteditable div
      but clicking the side mess with it.
    </div>
</div>

Even in normal circumstances it is generally a good idea to keep adjacent inline-block elements in a separate container rather than next to a block element (like the unrelated div) to prevent unexpected layout effects in case the order of the sibling elements changes.

即使在正常情况下,将相邻的 inline-block 元素保存在单独的容器中,而不是在块元素旁边(如 unrelated div)通常也是一个好主意,以防止在兄弟元素的顺序发生变化时出现意外的布局效果。

回答by Inacio Schweller

If it's not needed to use display: inline-block, I would recommend using float. Here is the example.

如果不需要使用display: inline-block,我建议使用浮动。这是示例

Based on your example, the new CSS would be:

根据您的示例,新的 CSS 将是:

div.text-input {
  display: block;
  background-color: black;
  color: white;
  width: 300px;
  float: left;
  margin-right: 10px;
}
div.unrelated {
  clear: both;
}

回答by Flash Thunder

Disable text selection in container... should fix that.

禁用容器中的文本选择...应该可以解决这个问题。

For example:

例如:

* {
   -ms-user-select: none; /* IE 10+ */
   -moz-user-select: -moz-none;
   -khtml-user-select: none;
   -webkit-user-select: none;
   user-select: none;
}

回答by JBG

How about a little jQuery?

来点 jQuery 怎么样?

$(".outside").click(function(e){
    $(e.target).siblings(".text-input").blur();
    window.getSelection().removeAllRanges();
});

And if IRL you need to account for clicks on contenteditable=truesiblings' children:

如果 IRL 您需要考虑对contenteditable=true兄弟姐妹的孩子的点击:

$(".outside").click(function(e){
    if ($(e.target).siblings(".text-input").length != 0){
        $(e.target).siblings(".text-input").blur();
        window.getSelection().removeAllRanges();
    } 
    else {
        $(e.target).parentsUntil(".outside").last().siblings(".text-input").blur();
        window.getSelection().removeAllRanges();
    }
});

window.getSelection().removeAllRanges();"The trick is to remove all ranges after calling blur"

window.getSelection().removeAllRanges();“诀窍是在调用模糊后删除所有范围”