По какой-то причине мне нужно использовать contenteditable div вместо обычного ввода текста для ввода текста. (для некоторой библиотеки javascript) Это работает нормально, пока я не обнаружил, что когда я устанавливаю contenteditable div с помощью display: inline-block, он фокусируется на 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;
}

JSFiddle для отображения проблемы

Есть ли способ (и CSS, или javascript, оба приемлемы), чтобы браузер фокусировался на div только при нажатии вместо того, чтобы щелкнуть по той же строке?

Постскриптум Я заметил, что есть похожие проблемы ( ссылка на другой связанный пост <) / a>), но ситуация немного отличается, и предоставленное решение не работает для меня.

33
cytsunny 18 Дек 2015 в 13:59

4 ответа

Лучший ответ

Объяснение (если вам все равно, перейдите к временным решениям ниже)

Когда вы нажимаете редактируемый элемент, браузер размещает курсор (он же точка вставки) в ближайшем текстовый узел, который находится внутри элемента, по которому щелкнули, в той же строке, что и ваш щелчок. Текстовый узел может находиться либо непосредственно в элементе, по которому щелкнули, либо в одном из его дочерних элементов. Вы можете убедиться в этом, запустив приведенный ниже фрагмент кода и щелкнув по большой синей рамке.

.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, а внутренние оранжевые / желтые прямоугольники являются вложенными дочерними элементами. Обратите внимание, что если вы щелкнете рядом (но не в) с одним из дочерних элементов, курсор окажется внутри него, даже если вы щелкнули снаружи. Это не ошибка. Поскольку элемент, по которому вы щелкнули (синее поле), является редактируемым, а дочерний элемент является частью его содержимого, имеет смысл поместить курсор в дочерний элемент, если именно там находится ближайший текстовый узел.

Проблема заключается в том, что браузеры Webkit (Chrome, Safari, Opera) демонстрируют такое же поведение, когда contenteditable установлен на дочернем, а не на родительском. В этом случае браузер не должен даже искать ближайший текстовый узел, поскольку элемент, на который вы фактически нажали, не редактируется. Но 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), что заставляет меня думать, что ответ @GOTO 0 Правильно подразумевать выделение текста - по крайней мере, поскольку он, по-видимому, регулируется той же логикой, которая контролирует размещение точки вставки. Многократный щелчок за пределами встроенного элемента выделяет текст внутри него, но не для блочных элементов. Вероятно, не случайно, что вы также не получаете точку вставки, когда нажимаете за пределами блока. Первый обходной путь ниже использует это исключение.


Обходной путь 1 (вложенный div)

Поскольку блоки не подвержены этой ошибке, я думаю, что лучшее решение - это вставить div в inline-блок и сделать его редактируемым. Внутренние блоки уже ведут себя как блоки внутри, поэтому 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 в inline-блоки, это решение позволит это сделать. Он работает, окружая встроенные блоки невидимыми символами (в частности, пробелы нулевой ширины) которые защищают их от внешних кликов. (в ответе GOTO 0 используется тот же принцип, но при последней проверке у меня все еще были некоторые проблемы).

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 может работать в качестве крайней меры (вдохновлено этим ответом) , Он устанавливает 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>
30
DoctorDestructo 23 Июл 2018 в 18:50

Если нет необходимости использовать display: inline-block, я бы порекомендовал использовать float. Вот пример.

Исходя из вашего примера, новый CSS будет:

div.text-input {
  display: block;
  background-color: black;
  color: white;
  width: 300px;
  float: left;
  margin-right: 10px;
}
div.unrelated {
  clear: both;
}
1
Inacio Schweller 22 Дек 2015 в 06:12

Как насчет небольшой 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(); «Хитрость заключается в удалении всех диапазонов после вызова размытия»

0
Community 23 Май 2017 в 12:02

Отключить выделение текста в контейнере ... должно исправить это.

Например:

* {
   -ms-user-select: none; /* IE 10+ */
   -moz-user-select: -moz-none;
   -khtml-user-select: none;
   -webkit-user-select: none;
   user-select: none;
}
0
Flash Thunder 23 Дек 2015 в 16:00