点击一个可疑的div剧照外面的焦点?

时间:2015-12-18 10:59:47

标签: javascript html css

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

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

显示问题的简单示例:

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:

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

The JSFiddle for displaying the problem

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

P.S。我注意到有类似的问题(link to other related post),但情况有点不同,所提供的解决方案对我不起作用。

5 个答案:

答案 0 :(得分:28)

解释(如果您不在乎,请跳至下方的解决方法

当您点击editable元素时,浏览器会将光标(也称为insertion point)放置在点击元素内最近的text node上,与您点击的同一行。文本节点可以直接位于被点击的元素内,也可以位于其子元素之一中。您可以通过运行下面的代码段并在大蓝框中单击来验证这一点。

.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!

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

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

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

......当他们应该这样做时:

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

块元素(例如div)似乎没有受到bug的影响,这使得我认为@GOTO 0's answer在暗示文本选择方面是正确的 - 至少在它看起来是由相同的控制插入点放置的逻辑。在内联元素外部进行多次单击会突出显示其中的文本,但对于块元素则不然。当你在一个块外面点击时,你也没有得到一个插入点,这可能并非巧合。下面的第一个解决方法是使用此异常。

解决方法1(嵌套div)

由于块不受bug的影响,我认为最好的解决方案是在内联块中嵌入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>

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

如果必须将contenteditable属性放在内联块上,此解决方案将允许它。它的工作原理是使用不可见字符(特别是zero-width spaces)包围内联块,以防止外部点击。 (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>

解决方法3(javascript)

如果你绝对无法改变你的标记,那么这个基于JavaScript的解决方案可以作为最后的手段(灵感来自this answer)。单击内联块时将contentEditable设置为true,失去焦点时将其设置为false。

(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>

答案 1 :(得分:9)

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

如果不检查代码,很难说出发生了什么,但我们至少可以怀疑问题在于触发浏览器中文本选择的一些错误机制。 类比而言,如果div不符合要求,则在最后一个字符后点击相同的文本行会触发从行尾开始的文本选择。

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

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

  • 为什么非空?:因为文本选择时会忽略空元素。
  • 为什么零宽度?:因为我们不想看到它......
  • 为什么看不见?:因为我们不希望将内容复制到剪贴板,例如 Ctrl + A Ctrl + 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>

即使在正常情况下,通常最好将相邻的内联块元素保存在单独的容器中,而不是放在块元素旁边(如不相关的div),以防止出现意外的布局效果兄弟元素的变化。

答案 2 :(得分:1)

如果不需要使用display: inline-block,我建议使用float。 Here is the example

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

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

答案 3 :(得分:0)

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

例如:

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

答案 4 :(得分:0)

一个小jQuery怎么样?

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

如果是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"