Резюме реализации div+contenteditable поля публикации форматированного текста

JavaScript HTML DOM

Визуализации и реализованные функции

Схема эффекта реализации выглядит следующим образом, основными функциями являются:

  • Вставка выражений
  • Выберите текст после вставки темы
  • Адаптивная высота текстового поля
  • Отправьте сообщение, получите текстовое содержимое в поле отправки
  • Реализация заполнителя
  • Панель подсчета для ввода текста

кодовый адреспорталКодVue

источник вдохновения

«Должно быть, кто-то сделал то, что вы хотите сделать, и вы не должны быть первым, кто столкнулся с этой проблемой» — это предложение справедливо для 80% (28 раз) людей, и я получил от него много пользы.

Мой первый эталонный случай — это поле выпуска разговора в пространстве qq && поле отправки сообщения webqq, Выгоды от него следующие:

  • можно использоватьdiv+contenteditableРеализовать окно отправки сообщений
  • Использовать в Chromebuttonтег для выделения @users, используйте в FirefoximgТеги для выделения @users (отлично) Использование различных тегов здесь очень специфично, учитывая совместимость с браузерами.
  • Вставив тему, выберите текст, чтобы улучшить взаимодействие с пользователем. Здесь также есть ссылка на него.Блог учителя Чжан Синьсюй
    Второй эталонный случай — это коробка динамического выпуска Наггетсов, а урожай выглядит следующим образом.
  • В правом нижнем углу показано количество слов, которые еще можно ввести.
  • placeholderреализация

Некоторые детали реализации

contenteditable

Я читаю статью г-на Чжан СиньсюяПеревод — 28 функций, хитростей и приемов HTML5, которые вы должны знатьПри первом контакте с ним можно пройтиdiv+contenteditableзаменятьtextareaРеализуйте поле редактирования. прочитай позжеDiv имитирует текстовое поле textarea, чтобы легко адаптироваться к высотеОткройте для себя силу этого свойства. Кроме того, если вы хотите, чтобы элемент на веб-странице был выделен, сначала вам понадобится HTML-тег, которому можно назначить CSS для его выбора, а затем измените тег, чтобы выполнить эту задачу.textarea, инлайнить теги крайне сложно, можно сказать, что нельзя, ноdivРазличные встроенные теги являются обычным явлением, и истории успеха Nuggets и пространства QQ не нужно упоминать.

оcontenteditableСвойства атрибута можно посмотреть по двум ссылкам выше, одним словом, тег с этим атрибутом и вложенные в него теги будут установлены как редактируемые атрибуты.

Реализация заполнителя

картинаinputа такжеtextareaЭти теги идут сplaceholderсвойства, ноdivНет, для этого нужно смоделировать JS и CSS. через два слояdivДля достижения во внешнем слое, отслеживая, есть ли текст в поле редактирования, чтобы выбрать, отображать лиplaceholder,placeholderРеализовано с помощью псевдоэлементов и абсолютного позиционирования, плавающего над полем редактирования без стандартного потока документов.

    <div
      class="edit-panel"
      :class="{'show-placeholder' : showPlaceholder}"
      :placeholder="placeholder"
    >
      <div contenteditable="true" ref="editor" class="editor"></div>
      <span class="count" :class="{'font-red':textCount < 0}">{{ textCount }}</span>
    </div>
.edit-panel {
  position: relative;
  width: 100%;
  height: auto;
  font-size: 14px;
  line-height: 20px;
  border: 1px solid;
}
.show-placeholder::before {
  content: attr(placeholder);
  position: absolute;
  top: 4px;
  left: 8px;
  color: #555;
  pointer-events: none;
}

количество панелей

надplaceholderРеализовать то же намерение

.edit-panel .count {
  position: absolute;
  color: #555;
  right: 1rem;
  bottom: 0.5rem;
  user-select: none;
  pointer-events: none;
}

Адаптивная высота текстового поля

Только здесь нужно установитьmin-heightа такжеmax-heightпросто хорошо

вставить смайлик

При вставке выражения нельзя считать само собой разумеющимся, что можно использовать операцию dom для вставки выражения.imgLabel, вот сравнение функций, реализованных Nuggets, и функций, реализованных пространством QQ. Я обнаружил, что поле ввода будет мигать, когда самородки вставят смайлик, а пробел QQ — нет. Мое разумное предположение состоит в том, что Nuggets вставляет выражения через операции dom, а затем записывает перед вставкой.rangeобъект, восстановленный после вставкиrangeТаким образом, вы не потеряете позицию курсора,rangeОбъекты используются для управления и получения текущего выбора курсора. Подробная ссылкаМДН — Диапазон. Пространство QQ вставляет выражения другими способами, аналогичными другим редакторам форматированного текста, и не мерцает при вставке. Я пошел исследовать здесь, используя следующий метод для вставки, создавdom片段, затем используйтеrangeОбъект вставляется в поле редактирования так, чтобы курсор не терялся.

Здесь следует отметить, что если с DOM неправильно манипулировать, позиция курсора будет смещена.Хороший пользовательский опыт заключается в том, что позиция курсора остается неизменной.

function insertHtmlAtCaret (html) {
  var sel, range, frag
  if (window.getSelection) {
    sel = window.getSelection()
    if (sel.getRangeAt && sel.rangeCount) {
      range = sel.getRangeAt(0)
      range.deleteContents()
      var el = document.createElement('div')
      el.innerHTML = html
      frag = document.createDocumentFragment()
      var node
      var lastNode
      while ((node = el.firstChild)) {
        lastNode = frag.appendChild(node)
      }
      range.insertNode(frag)
      if (lastNode) {
        range = range.cloneRange()
        range.setStartAfter(lastNode)
        range.collapse(true)
        sel.removeAllRanges()
        sel.addRange(range)
      }
    }
  }
}

Выберите текст после вставки темы

тут надо пониматьrangeЧетыре важных свойства объектовstartContainer,startOffset,endContainer,endOffset.在不同情况下指代的意思是不一样的,我这里就是轻描淡写的提一下,我理解的不是很透彻就不误导大家了。

addTopic (event) {
      this.$refs.editor.focus()
      insertHtmlAtCaret('#')
      insertHtmlAtCaret('请输入一个话题')
      insertHtmlAtCaret('#')
      var range = window.getSelection().getRangeAt(0)
      console.log(range)
      range.selectNodeContents(range.startContainer.childNodes[range.startOffset - 2])
    }

Получить обычный текстовый контент

Использовать напрямуюtextContentНет, ты не можешь получить этоimgСодержимое ярлыка, оно будет использоваться после добавленияbuttonилиinput[type=button]Чтобы реализовать некоторые функции выделения, вам необходимо определить метод для самостоятельного получения текстового содержимого. Я реализовал это относительно просто

function getDomValue (elem) {
  var res = ''
  Array.from(elem.childNodes).forEach((child) => {
    if (child.nodeName === '#text') {
      res += child.nodeValue
    } else if (child.nodeName === 'BR') {
      res += '\n'
    } else if (child.nodeName === 'BUTTON') {
      res += getDomValue(child)
    } else if (child.nodeName === 'IMG') {
      res += child.alt
    } else if (child.nodeName === 'DIV') {
      res += '\n' + getDomValue(child)
    }
  })
  return res
}

Заметки и перспективы

  • Поле редактирования расширенного текста должно учитывать множество проблем XSS, фильтрацию меток при назначении и вставке и т. д.
  • проблемы с совместимостью браузера,rangeоперации с объектами разные,contenteditableПроблемы со свойствами
  • Унифицируйте стиль вставляемого текста, иначе при вводе текста будет использоваться предыдущий стиль
  • Проблемы совместимости браузера, отмеченные @user

благодарный

Я могу выполнить эту функцию, спасибо@Жареный рис июньс помощь. Отличная помощь во время стажировки.