前言
最近在弄个人的网站,偶然间发现DIV可以设置编辑模式,之前设计的方案在此功能上需要限制输入的长度。网上搜索了一波,综合搜索的结果,考虑使用的监听事件有:keydown 、textInput 、input。因为可能输入中文,所以也要监听compositionstart和compositionend两个事件。
在试用过程中,发现:
- Keydown是监控到键盘的输入出发,调用顺序在compositionstart和compositionend之间,导致无法对中文的输入进行实时的监控。
- textInput和keydown一样,对中文一样无力,而且无法对粘贴进行监控。
- Input能对所有value改变事件进行监控,但是不支持撤销功能。
不用想,肯定选择范围最宽的'Input'了,没有撤销?那就实现一个。
思路及实现
撤销在文本框的直接感觉回到操作之前的文本(Ps:这里只考虑字符串的增加操作)。那将上一步添加的数据删除,已达到撤销相同的效果不就可以了。那么首先要得到上一步操作时光标位置的初始位置以及光标的偏移量,然后对操作后的字符串中添加的字串移除不就可以了。 顺着这个思路得到下面的代码。
- 1 var content = document.getElementById('add-content')
- 2 //注册中文的输入事件,
- 3 var isCN = false;
- 4 content.addEventListener('compositionstart',function(){
- 5 isCN = true;
- 6 });
- 7 content.addEventListener('compositionend',function(){
- 8 isCN = false;
- 9 })
- 10 //注册文本输入事件,获取光标的起止偏移数据,如果是非中文以及超出长度的输入,则撤销本次操作
- 11 var txtAnchorOffset, txtFocusOffset;
- 12 content.addEventListener("textInput",function(event){
- 13 var _sel = document.getSelection();
- 14 txtAnchorOffset = _sel.anchorOffset;
- 15 txtFocusOffset = _sel.focusOffset;
- 16 //必须加上isCN的判断,否则获取不到正确的光标数据
- 17 if(!isCN && this.textContent.length >= noteMax){
- 18 event.preventDefault();
- 19 }
- 20 });
- 21
- 22 //注册输入事件,对输入的数据进行
- 23 content.addEventListener("input",function(event){
- 24 setTimeout(function(){
- 25 if(!isCN){
- 26 var _this = content;
- 27 if(_this.textContent.length > noteMax){
- 28 var data = _this.textContent;
- 29 oldDate = data.slice(0, txtAnchorOffset) + data.slice(txtFocusOffset, data.length);
- 30 //再次截取最大长度字符串,防止溢出
- 31 _this.textContent = oldDate.slice(0, noteMax);
- 32 }
- 33 }
- 34 }, 0);
- 35 });
Emmmm,的确能移除了,但是光标却回到了字符串的开始位置。这可不行,这样不就成了阉割版的了吗? 继续查询网上移动文本域光标的实现方法,move()、setSelectionRange()在运行的时候都提示” xx is not a funtion”,网上查询后,好像说是只支持input和textarea标签。
后面在MDN官方文档上看到这个函数:
Selection.collapse(); 参数:parentNode光标落在的目标节点 offset 落在节点的偏移量。
有了函数,那就来开干!!
- 1 //注册输入事件,对输入的数据进行
- 2 content.addEventListener("input",function(event){
- 3 setTimeout(function(){
- 4 if(!isCN){
- 5 var _this = content;
- 6 if(_this.textContent.length > noteMax){
- 7 var data = _this.textContent;
- 8 oldDate = data.slice(0, txtAnchorOffset) + data.slice(txtFocusOffset, data.length);
- 9 //再次截取最大长度字符串,防止溢出
- 10 _this.textContent = oldDate.slice(0, noteMax);
- 11 //光标移动到起始偏移位置
- 12 document.getSelection().collapse(_this.firstChild, txtAnchorOffset);
- 13 }
- 14 }
- 15 }, 0);
- 16 });
注意:parentNode参数选择文本节点。
开始的时候,我传的值是_this,提示“Failed to execute 'collapse' on 'Selection': There is no child at offset X.”,后面,在debugger中发现文本域是div对象的子节点,有想法就去尝试。改成_this.firstChild后,目标达成。
到此基本上已经完成功能的设计,然后在加上移除粘贴内容就行了。完成的代码如下:
html
- <div id="add-content" contenteditable="true" ></div>
Javascript
- 1 var content = document.getElementById('add-content')
- 2 //注册中文的输入事件,
- 3 var isCN = false;
- 4 content.addEventListener('compositionstart',function(){
- 5 isCN = true;
- 6 //撤销预输入内容,必须否则会替代末尾字符
- 7 if(this.textContent.length >= noteMax){
- 8 event.preventDefault();
- 9 }
- 10 });
- 11 content.addEventListener('compositionend',function(){
- 12 isCN = false;
- 13 })
- 14 //注册文本输入事件,获取光标的起止偏移数据,如果是非中文以及超出长度的输入,则撤销本次操作
- 15 var txtAnchorOffset, txtFocusOffset;
- 16 content.addEventListener("textInput",function(event){
- 17 var _sel = document.getSelection();
- 18 txtAnchorOffset = _sel.anchorOffset;
- 19 txtFocusOffset = _sel.focusOffset;
- 20 //必须加上isCN的判断,否则获取不到正确的光标数据
- 21 if(!isCN && this.textContent.length >= noteMax){
- 22 event.preventDefault();
- 23 }
- 24 });
- 25 //注册粘贴事件,获取粘贴数据的长度
- 26 var pastedLength;
- 27 content.addEventListener("paste",function(event){
- 28 if(!event.clipboardData) return;
- 29 pastedLength = event.clipboardData.getData('Text').length;
- 30 });
- 31
- 32 //注册输入事件,对输入的数据进行
- 33 content.addEventListener("input",function(event){
- 34 setTimeout(function(){
- 35 if(!isCN){
- 36 var _this = content;
- 37 if(_this.textContent.length > noteMax){
- 38 var data = _this.textContent;
- 39 if(pastedLength > 1){
- 40 oldDate = data.slice(0, txtAnchorOffset) + data.slice(txtFocusOffset+pastedLength, data.length);
- 41 //粘贴字符串长度置为0,以免影响到下一次判断。
- 42 pastedLength = 0;
- 43 } else {
- 44 oldDate = data.slice(0, txtAnchorOffset) + data.slice(txtFocusOffset, data.length);
- 45 }
- 46 //再次截取最大长度字符串,防止溢出
- 47 _this.textContent = oldDate.slice(0, noteMax);
- 48 //光标移动到起始偏移位置
- 49 document.getSelection().collapse(_this.firstChild, txtAnchorOffset);47 }
- 50 }
- 51 }, 0);
- 52 });
总结
在这次的编码中,其实试过很多个方法,尤其是光标那一部分,失败了很多次。然后继续搜索寻找方法。最后还是在官方文档里面找到突破点,网上很多经验都是别人消化并根据自己的理解写出来的,官方文档却是原滋原味的,所以一定要以官方文档为参照,不要迷失方向。 中间其实曾放弃了一次,将div改成textarea光标一下子就能正确偏移了。但是想了一下,还是重新改成div,继续出发,继续寻找正确的方向,最后终于完成了想要的功能。
有想法就去实现它!!!成功了,能收获喜悦;不成功,也能在途中拓宽自己的知识面。还有,最好有个地方将其记录并分享出来,不管是成功或失败。成功的经验,或许能帮助到他人;失败的记录,或许能获得他人的帮助,然后再去完成它。