键盘输入分类

间接输出

输出的键间接落入可输出DOM元素,为间接输出。

E.g.英文输出。

间接输出

输出的键值不会间接落入可输出DOM元素,有一个两头态,为间接输出。

E.g.中文输出。

辨别中英文输出

因为任何输出都会触发input,而输出中文的时候才触发compositionstartcompositionend,能够以此来辨别中英文输出。

e.keyCode在中英文下不同的体现

e.keyCode在英文模式下输出,能获取正确的键值;
e.keyCode在中文模式下输出,键入任何值都输入229

Windows将所有未辨认的设施输出都设置为VK_PROCESSKEY 229,浏览器的 event.keyCode复用了这一标准,因而在中文输出过程中,无论按下什么按键,返回的event.keyCode永远是229

输出的事件监听

因为任何输出都会触发input,而输出中文的时候才触发compositionstartcompositionend,能够以此来辨别中英文输出。

监听input事件时,输出值时,e.data有值,删除值时,e.data === undefined,能够以此判断输出、删除。

compositionstartcompositionupdatecompositionend只能通过window.addEventListener('')监听,on*监听有效。

自定义编辑器

保障输出的复杂度与灵活性,个别选用一般标签而非文本域做输出容器。

一般标签可编辑

contenteditable标签属性

属性值如下:

contenteditable=""contenteditable="events"contenteditable="caret"// 纯文本输出:// 换行不会生成<div>,应用'\n'或inputType === 'insertLineBreak'判断// 复制黏贴不会带有格局contenteditable="plaintext-only" contenteditable="true" // 换行会生成<div>包裹contenteditable="false"

user-modifyCSS属性

user-modify能够在挪动端应用,以及,只须要兼顾webkit内容的桌面网页我的项目。
-webkit-user-modify: read-only;  // 一般元素的默认状态-webkit-user-modify: read-write; //能够输出富文本-webkit-user-modify: write-only;-webkit-user-modify: read-write-plaintext-only // 只能够输出纯文本

两种形式比照

  • contenteditableuser-modify的旧版本浏览器支持性差
  • contenteditable是归属于W3C规范,全浏览器反对;而user-modify浏览器有本人的实现,非标准,应用须要追加浏览器前缀-webkit--moz-...;
  • contenteditableuser-modify都能够实现对拷贝的富文本过滤格局;

插入内容 && 敞开抉择范畴

基本知识

两个概念抉择selection范畴range
const selection = getSelection()const range = selection.getRangeAt(0) // 获取光标位的选中范畴const range1 = new Range()  // 自定义选中范畴,应用时须要selection.addRange(range1)
  • selection是治理range的汇合,除了Firfox中rangeCount > 1,其它浏览器的实现,selection最多只有一个range
  • range是文本抉择范畴的终点和起点
range文本抉择规定
  • 通过range.setStart(node, offset)range.setEnd(node, offset)设置范畴,依据node节点的类型nodeType不同分属不同的状况

    • node为文本节点nodeType === 3,偏移量offset为文本中的地位。
    • node为元素节点nodeType === 1,偏移量offset为指定元素子节点node.childNodes的地位
    • 其中范畴终点、起点的node容许不同节点
    • 其中范畴终点、起点的定位位于偏移量offset之前
  • 通过console.log(range)即可查看选中的文本;静默调用toString()办法返回内容;
  • 通过range.startContainerrange.startOffset查看以后范畴的终点归属元素及偏移量
  • 通过range.endContainerrange.endOffset查看以后范畴的起点归属元素及偏移量
  • 通过range.insertNode(node),在范畴的起始处将node插入文档
  • 通过range.extractContents()range.deleteContents从文档中删除范畴内容
  • 通过range.surroundContents(node),自定义元素节点包裹抉择的范畴,抉择的范畴若有元素节点,元素节点必须闭合
  • 通过selection.empty()能够清空抉择

应用selection.addRange(range)增加范畴时,如果抉择已存在,则首先应用selection.removeAllRanges()将其清空。而后增加范畴。否则,除Firefox外的所有浏览器都将疏忽新范畴。 其中,通过range.setStartrange.setEnd调整范畴的状况,不用思考清空selection

Selection类型

selection.type
  • None: 以后没有抉择。
  • Caret: 选区已折叠(即 光标在字符之间,并未处于选中状态)。
  • Range: 抉择的是一个范畴。

设置光标地位为某元素后

// 形式1. 只反对Android、PCrange.setStart(baseNode, 1)range.setEnd(baseNode, 1)range.collapse()// 形式2. 只反对Android、PCrange.setEndAfter(baseNode)selection.collapseToEnd()// 形式3. selection.setPosition(node, offset)// 形式4. 反对IOS,仅用于通过range.extractContents()提取的documentFrag文本selection.removeAllRanges()selection.addRange(range)range.setStart(cloneNode, cloneNode.endOffset)range.setEnd(cloneNode, cloneNode.endOffset)selection.collapseToEnd()// 形式4. 反对全平台,IOS不可用于通过range.extractContents()提取的documentFrag文本selection.extend(baseNode, 1)selection.collapseToEnd()// 光标定位文本后:通过range.extractContents()提取的documentFrag文本try {  // 安卓端  selection.extend(cloneNode, 1)  selection.collapseToEnd()} catch (e) {  // Iphone端  selection.removeAllRanges()  selection.addRange(range)  range.setStart(cloneNode, cloneNode.endOffset)  range.setEnd(cloneNode, cloneNode.endOffset)  selection.collapseToEnd()}

代码示例

    const { selection, range } = this.lastSelection    this.editableEle.focus()    const textNode = range.startContainer    range.setStart(textNode, range.endOffset)    range.setEnd(textNode, range.endOffset)    const spanNode1 = document.createTextNode(' ')    const spanNode2 = document.createElement('span')    spanNode2.className = 'tag'    spanNode2.innerHTML = '#'    let frag = document.createDocumentFragment(), lastNode = spanNode2    frag.appendChild(spanNode1)    frag.appendChild(spanNode2)    range.insertNode(frag)    // IPhone下有时候会报错,采纳下方代码代替    selection.extend(lastNode, 1)    selection.collapseToEnd()
IOS下,在标签后紧跟着增加节点,selection.extend(node, 1)敞开范畴报错解决方案
whetherEndTag (prefixer) {   if (prefixer === ' ') {      const selection = getSelection()      const range = selection.getRangeAt(0)      const node = range.startContainer      if (node.parentNode && node.parentNode.className === 'tag') {        range.setStart(node, range.endOffset - 1)        range.setEnd(node, range.endOffset)        const cloneNode = range.extractContents()        range.setStartAfter(node.parentNode, selection.endOffset)        range.setEndAfter(node.parentNode, selection.endOffset)        range.collapse(true)        range.insertNode(cloneNode)        // 安卓、IOS不兼容        try {          // 安卓端          selection.extend(cloneNode, 1)          selection.collapseToEnd()        } catch (e) {          // Iphone端          selection.removeAllRanges()          selection.addRange(range)          range.setStart(cloneNode, cloneNode.endOffset)          range.setEnd(cloneNode, cloneNode.endOffset)          selection.collapseToEnd()        }      }    }}

长度限度

间接输出
  // 间接输出  onKeyupListener (e) {    this.check_charcount(e)  },  onKeydownListener (e) {    this.check_charcount(e)  },  check_charcount (e, max = 100) {    if(e.which != 8 && this.editableEle.textContent.length > max) {      e.preventDefault()    }  }
间距输出
  // 中英文,在input、compositionEnd事件中调用  ...  data () {      return {          CNEnd: true      }  }  ...  compositionstart (e) {    this.CNEnd = false    },  compositionend (e) {      this.CNEnd = true      this.limitInput(e)  }  ...    limitInput(event) {        let _words = this.editableEle.textContent        let _this = this.editableEle        if (this.CNEnd) {          let num = _words.length              if (num >= 100) {            num = 100            if (_this.spillOver) {              event.target.innerText = this.fullContent            } else {              event.target.innerText = _words.substring(0, 100)              _this.spillOver = true              this.fullContent = _words.substring(0, 100)            }            Toast('100以内')          } else {            _this.spillOver = false            this.fullContent = ''          }          const sel = window.getSelection()          let range = document.createRange()          range.selectNodeContents(this.editableEle)          range.collapse(false)          sel.removeAllRanges()          sel.addRange(range)        } else if (this.fullContent) {          // 指标对象:超过100字时候的中文输入法          // 原由:尽管不会输出胜利,然而输出过程中字母仍然会浮现在输入框内          // 弊病:谷歌浏览器输入法的界面偶然会闪现          event.target.innerText = this.fullContent          this.CNEnd = true        }     }

不了解的问题解决方案

node.nextSibling.nodeType === 3

当获取元素节点的兄弟文本节点node.nextSibling时,元素节点必须要有文本内容,否则一堆世界未解之谜。

删除有款式的文本时(常见于插入回车),浏览器会主动生成<font>追加款式

  clearFontTag () {    // 当删除时,浏览器主动增加font标签加款式    const fontTag = this.editableEle.querySelector('font')    if (fontTag) {      const newNode = document.createTextNode(fontTag.textContent)      this.editableEle.replaceChild(newNode, fontTag)      const { selection } = this.lastSelection      selection.extend(newNode, 1)      selection.collapseToEnd()      this.cacheCursorPos()    }  }

在带有款式的标签后回车,下一行浏览器主动带款式

  • 应用contenteditable="plaintext-only"创立可编辑区时,在带有款式的标签后回车,换行后仍在标签内
  • 应用contenteditable="true"创立可编辑区时,,在带有款式的标签后回车,浏览器主动在新行增加款式标签。
解决方案
  clearNewlineSideEffect () {    const { range } = this.lastSelection    const node = range.startContainer    const baseNode = isAndroid ? node.parentNode : node    if (baseNode.nodeType === 1 && baseNode.className === 'tag' && !/^#/.test(baseNode.textContent)) {      const frag = document.createDocumentFragment()      const textNode = document.createTextNode(baseNode.textContent)      const brNode = document.createElement('br')      frag.appendChild(textNode)      frag.appendChild(brNode)      baseNode.parentNode.replaceChild(frag, baseNode)    }  },

知识点补充

selection文本抉择

selection也能够实现range局部性能的范畴抉择
  • selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset)等同于对设置range.setStart(anchorNode, anchorOffset)range.setEnd(focusNode, focusOffset)
  • 通过selection.collapse(node, offset)等同于对同一node设置range.setStart(node, 0)range.setEnd(node, offset)
  • 通过selection.setPosition(node, offset)等同于对同一node设置同一偏移量range.setStart(node, offset)range.setEnd(node, offset)range.setStart(node, offset)range.collapse(true)
  • 通过selection.deleteFromDocument()从文档中删除所抉择的内容

参考文档

  • selection-range
  • contenteditable+user-modify
  • 中英文长度限度