div+contenteditable 實現富文本發佈框的小結

效果圖和實現的功能

實現的效果圖以下,主要實現的功能有css

  • 表情的插入
  • 插入話題以後部分文字選中
  • 文本框高度自適應
  • 發送消息,獲取發送框中的純文本內容
  • placeholder的實現
  • 輸入文字的計數面板

代碼地址傳送門 代碼實現Vuehtml

靈感來源

「你想作的必定有人作了,你必定不是第一個遇到這個問題的人」——這句話對80%(二八分佈)的人是有效的,我也從中獲益很多。html5

個人第一份參考案例是qq空間的說說發佈框&&webqq的消息發送框,從中的收穫有如下幾點node

  • 可使用div+contenteditable實現消息發送框
  • 在Chrome中使用button標籤來高亮被@的用戶,在Firefox中使用img標籤來高亮被@的用戶(絕妙)這裏不一樣標籤的使用很講究,考慮了瀏覽器兼容性。
  • 插入話題以後部分文字選中,提升用戶體驗,這裏還參考了張鑫旭老師的博客_新浪微博插入話題後部分文字選中的js實現
    第二份參考案例是掘金的動態發佈框,收穫以下
  • 右下角顯示還可以輸入的字數
  • placeholder的實現

實現的一些細節

contenteditable

我在拜讀張鑫旭老師的文章翻譯-你必須知道的28個HTML5特徵、竅門和技術的時候第一次接觸到能夠經過div+contenteditable替代textarea實現一個編輯框。以後再閱讀了div模擬textarea文本域輕鬆實現高度自適應發現了這個屬性的強大之處。 另外想要網頁上的元素可以高亮,首先你須要一個能夠被賦予CSS的HTML標籤選中它,而後去改變這個標籤纔可以完成這個任務,傳統的發佈框使用textarea,內嵌標籤極其困難,能夠說是不行,可是div不一樣,內嵌標籤是屢見不鮮,掘金和qq空間的成功案例就不用多說。git

關於contenteditable屬性的特性能夠去上面的兩個連接中查閱,總之是會把設置了這個屬性的標籤和裏面的子標籤都設置爲可編輯屬性。github

palceholder的實現

inputtextarea這些標籤自帶placeholder屬性,可是div沒有,要實現就須要經過JS和CSS來模擬。 經過兩層div實現,在外層經過監聽編輯框中是否存在文字來選擇是否展現placeholderplaceholder經過僞元素和絕對定位實現,脫離標準文檔流浮於編輯框之上web

<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-heightmax-height就行app

插入表情

插入表情的時候不能想固然的使用dom操做插入一個img標籤,這裏對比一下掘金實現的功能和qq空間實現的功能。我發現掘金插入表情的時候輸入框會閃爍一下,而qq空間的不會。我合理的猜測掘金是經過dom操做來插入表情的,而後是記錄了插入以前的range對象,在插入以後還原range這樣就不會丟失光標位置,range對象是用來控制和獲取當前光標選取的內容的。詳細參考MDN——Range。qq空間則是經過其餘的方式來插入表情,相似於其餘的富文本編輯器,在插入的時候不會閃爍一下。我這裏去探索了一下,使用下面的這個方法插入,經過建立一個dom片斷,而後用range對象插入到編輯框中,這樣也不會丟失光標。dom

這裏須要注意,只要操做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對象中四個重要的屬性startContainerstartOffsetendContainerendOffset。在不一樣狀況下指代的意思是不同的,我這裏就是輕描淡寫的提一下,我理解的不是很透徹就不誤導你們了。

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屬性表現出來的問題
  • 統一插入文本的樣式,不然輸入文字的時候會沿用前面的樣式
  • @用戶高亮顯示的瀏覽器兼容問題

感謝

我能完成這個功能要感謝@炒飯君的幫助。實習期間給力很大的幫助。

相關文章
相關標籤/搜索