键盘输入分类
间接输出
输出的键间接落入可输出DOM元素,为间接输出。
E.g.英文输出。
间接输出
输出的键值不会间接落入可输出DOM元素,有一个两头态,为间接输出。
E.g.中文输出。
辨别中英文输出
因为任何输出都会触发input
,而输出中文的时候才触发compositionstart
和compositionend
,能够以此来辨别中英文输出。
e.keyCode
在中英文下不同的体现
e.keyCode
在英文模式下输出,能获取正确的键值;e.keyCode
在中文模式下输出,键入任何值都输入229
;
Windows
将所有未辨认的设施输出都设置为VK_PROCESSKEY 229
,浏览器的 event.keyCode
复用了这一标准,因而在中文输出过程中,无论按下什么按键,返回的event.keyCode
永远是229
。
输出的事件监听
因为任何输出都会触发input
,而输出中文的时候才触发compositionstart
和compositionend
,能够以此来辨别中英文输出。
监听input
事件时,输出值时,e.data
有值,删除值时,e.data === undefined
,能够以此判断输出、删除。
compositionstart
、compositionupdate
和compositionend
只能通过window.addEventListener('')
监听,on*
监听有效。
自定义编辑器
保障输出的复杂度与灵活性,个别选用一般标签而非文本域做输出容器。
一般标签可编辑
contenteditable
标签属性
属性值如下:
contenteditable=""contenteditable="events"contenteditable="caret"// 纯文本输出:// 换行不会生成<div>,应用'\n'或inputType === 'insertLineBreak'判断// 复制黏贴不会带有格局contenteditable="plaintext-only" contenteditable="true" // 换行会生成<div>包裹contenteditable="false"
user-modify
CSS属性
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 // 只能够输出纯文本
两种形式比照
contenteditable
和user-modify
的旧版本浏览器支持性差contenteditable
是归属于W3C规范,全浏览器反对;而user-modify
浏览器有本人的实现,非标准,应用须要追加浏览器前缀-webkit-
、-moz-
...;contenteditable
和user-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
之前
- node为文本节点
- 通过
console.log(range)
即可查看选中的文本;静默调用toString()
办法返回内容; - 通过
range.startContainer
、range.startOffset
查看以后范畴的终点归属元素及偏移量 - 通过
range.endContainer
、range.endOffset
查看以后范畴的起点归属元素及偏移量 - 通过
range.insertNode(node)
,在范畴的起始处将node插入文档 - 通过
range.extractContents()
、range.deleteContents
从文档中删除范畴内容 - 通过
range.surroundContents(node)
,自定义元素节点包裹抉择的范畴,抉择的范畴若有元素节点,元素节点必须闭合 - 通过
selection.empty()
能够清空抉择
应用selection.addRange(range)
增加范畴时,如果抉择已存在,则首先应用selection.removeAllRanges()
将其清空。而后增加范畴。否则,除Firefox
外的所有浏览器都将疏忽新范畴。 其中,通过range.setStart
、range.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
- 中英文长度限度