前言
最近在弄個人的網站,偶然間發現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,繼續出發,繼續尋找正確的方向,最后終於完成了想要的功能。
有想法就去實現它!!!成功了,能收獲喜悅;不成功,也能在途中拓寬自己的知識面。還有,最好有個地方將其記錄並分享出來,不管是成功或失敗。成功的經驗,或許能幫助到他人;失敗的記錄,或許能獲得他人的幫助,然后再去完成它。