
https://github.com/kohunglee/areaEditor?
areaEditor.js 演示 :
https://www.ccgxk.com/areaEditor.html
快速使用
只需這樣即可:
| <script src="https://cdn.jsdelivr.net/gh/kohunglee/areaeditor/src/areaeditor.2.0.x.min.js" integrity="sha256-sP3tIYbNNHejSjhs3X0SBLULz54YEbR3g1dSJMvpCME=" crossorigin="anonymous"></script> |
| <script> |
| var editor = new AreaEditor('textarea', {indentType : { type: 'space', count: 4 }}); |
| </script> |
這樣,您的網頁上所有的 <textarea>
就都可以縮進了。當然,{indentType : { type: 'space', count: 4 }}
是控制縮進的類型,space
是空格縮進,目前是 4 個空格。如果寫成 {indentType : { type: 'tab', count: 1 }}
,則是一個 '\t'
TAB 制表符。
(不寫這個配置,直接 new AreaEditor('textarea')
也可以,默認是 4 個空格 )
后續也可以動態進行更改。比如:
| editor.indentType.type = 'tab'; |
當然,如果是特定元素,也可以使用選擇器:
| var editor = new AreaEditor('textarea'); |
| var editor = new AreaEditor('.code-editor'); |
| var editor = new AreaEditor('#code-editor'); |
縮進功能
最開始,我找了很多資料,發現貌似沒人有針對 <textarea>
的直接優化。代碼編輯器是很多,大部分都是另辟一大堆 <div>
模擬新編輯框了,或者直接在大輪子 Code Mirror 基礎上進一步改進。
其實,有時我們需要的并不多。比如,這些代碼編輯器有高亮的功能,能五顏六色顯示關鍵詞,其實這個屬于「消費升級」的非剛需功能了,真正在編輯時的剛需是 代碼縮進!
| 代碼縮進 > 自動縮進 >> 高亮提示 > 智能補全 > 括號補全 > 糾錯提示 > 語法高亮 |
我們肯定很討厭在 <textarea>
寫代碼時,一按 TAB 鍵,然后光標跑外星的那種感覺。其實我們完全可以使用 js 來改良這個。
(可以試試在 https://www.ccgxk.com/cellhtmleditor.html 這個實時編輯頁面進行調試下面的這些代碼 ~)
| <textarea id="editor" placeholder="演示 tab 鍵縮進"></textarea> |
| <script> |
| editor.addEventListener('keydown', function(e){ |
| var start = e.target.selectionStart; |
| var end = e.target.selectionEnd; |
| var value = e.target.value; |
|
|
| if (e.key === 'Tab') { |
| e.preventDefault(); |
| e.target.value = value.substring(0, start) + '\t' + value.substring(end); |
| e.target.selectionStart = e.target.selectionEnd = start + 1; |
| } |
| }); |
| </script> |
首先,我們阻止了 tab 的默認事件(跳到下一個表單元素)e.preventDefault();
,然后我們調用 textarea
元素的 .value
api 重新為其填充內容。
然后是 e.target.selectionStart = e.target.selectionEnd = ...
讓我們的光標位置放到應該到達的地方。
.selectionStart
、 .selectionEnd
、 .value
這三個 API 現在實現這樣一個小功能,然后還是這三個 API ,漸漸貫徹了整個代碼。一切從這里開始。
縮進類型
目前,縮進格式并不統一,主流的縮進是 4 個空格,所以,就得需要能切換。
(可以試試在 https://www.ccgxk.com/cellhtmleditor.html 這個實時編輯頁面進行調試下面的這些代碼 ~)
| <textarea id="editor" placeholder="演示縮進類型"></textarea> |
| <script> |
| editor.addEventListener('keydown', function(e){ |
| var type = 'space'; |
| var indentCount = 4; |
| var tabChar = (type === 'tab') ? '\t' : Array(indentCount + 1).join(' '); |
|
|
| var start = e.target.selectionStart; |
| var end = e.target.selectionEnd; |
| var value = e.target.value; |
|
|
| if (e.key === 'Tab') { |
| e.preventDefault(); |
| e.target.value = value.substring(0, start) + tabChar + value.substring(end); |
| e.target.selectionStart = e.target.selectionEnd = start + indentCount; |
| } |
| }); |
| </script> |
代碼里 Array(indentCount + 1).join(' ')
是一種技巧,可以返回 n 個空格,比如 Array(5 + 1).join(' ')
就是 5 個空格,Array(3 + 1).join(' ')
就等于 3 個空格。
根據這個原理,代碼的收縮也實現了。不過,還是有很多坑,習慣上的坑:
增加縮進很簡單,減少縮進沒想到這么復雜,計算邏輯還挺抽象,花了好幾個小時潤色,才算完美解決,下面是最終的代碼:
| |
| if (e.key === 'Tab') { |
| e.preventDefault(); |
| if (start === end) { |
| e.target.value = value.substring(0, start) + this.tabChar + value.substring(end); |
| e.target.selectionStart = e.target.selectionEnd = start + this.tabLength; |
| return; |
| } else { |
| var contentArr = value.split('\n'); |
| var contentArrOriginal = value.split('\n'); |
| var startLine = (value.substring(0, start).match(/\n/g) || []).length; |
| var endLine = (value.substring(0, end).match(/\n/g) || []).length; |
| if (event.shiftKey) { |
| for (var _i = startLine; _i <= endLine; _i++) { |
| contentArr[_i] = this._removeLeadingSpaces(contentArr[_i], this.tabLength); |
| } |
| e.target.value = contentArr.join('\n'); |
| var lengthDiff = contentArrOriginal[startLine].length - |
| contentArrOriginal[startLine].trimStart().length; |
| var moveLength = Math.min(this.tabLength, lengthDiff); |
|
|
| |
| var limitLineNum = this._arrSum(contentArr, startLine); |
|
|
| |
| var startPoint = limitLineNum > start - moveLength - startLine ? limitLineNum + startLine : start - moveLength; |
|
|
| e.target.selectionStart = lengthDiff > 0 ? startPoint : start; |
| e.target.selectionEnd = end - (contentArrOriginal.join('\n').length - e.target.value.length); |
| } else { |
| for (var _i = startLine; _i <= endLine; _i++) { |
| contentArr[_i] = this.tabChar + contentArr[_i]; |
| } |
| e.target.value = contentArr.join('\n'); |
| e.target.selectionStart = start + this.tabLength; |
| e.target.selectionEnd = end + this.tabLength * (startLine === endLine ? 1 : endLine - startLine + 1); |
| } |
| } |
| } |
自動補全括號
但,只有這種縮進,肯定還不夠,我們還需要那種寫下括號后,再回車,產生的那種自動換行和縮進的效果。

按下回車:

順便把自動括號也實現。
于是哐哐一頓實現,計算還簡單,但逐漸發現一個問題。
這是程序 1.0 時候的一個案例:
| <textarea placeholder="演示 enter 鍵 bug"></textarea> |
| <script src="https://cdn.jsdelivr.net/gh/kohunglee/areaeditor/src/areaeditor.1.0.x.min.js"></script> |
| <script> |
| var editor = new AreaEditor('textarea'); |
| </script> |
當我們用中文輸入法輸入時,一回車(按照習慣,應該只會輸入 jtxql 這六個字符),會產生這樣的效果:


顯然使用 addEventListener('keydown', ... )
是不行的。
于是又加入了 addEventListener('input', ... )
。keydown
和 input
是不一樣的。前者是在鍵盤按下的一瞬間觸發,在字符輸入前(也因此可以阻止字符輸入),而后者 input
是在字符輸入后探測你按下了哪個按鍵。
犯難
這就犯了難,我到底是使用哪個來監測 enter
鍵的按下。如果是前者,那我避免不了這個 bug,如果是后者,那畫面會跳動一下,很詭異。
最后我還是使用后者,只不過我不是監測的按鈕,而是字符 if(lastChar === '\n'){ ... }
,然后重新生成縮進內容,里面多來一個換行符即可。
自動補全
自動補全倒是沒有什么好說的。唯一要說的,【若用戶仍選擇手動完成,則忽略】這個功能,讓這個自動補全變得非常流暢。但我沒想到,要判斷用戶有沒有手動補全,竟然要判斷三個布爾:前一個字符,是否屬于要補全的符號,后一個字符是否等于應補全的符號,用戶輸入的是否等于已經補全的符號。
| var autoPairs = { |
| '{': '}', |
| '[': ']', |
| '(': ')', |
| '"': '"', |
| "'": "'", |
| '`': '`', |
| }; |
| if (['{', '(', '[', '"', "'", '`', ']', '}', ')'].includes(lastChar) && start === end) { |
| if(this.isPreventAuto){ |
| this.isPreventAuto = false; |
| return; |
| } |
| var pairChar = autoPairs[lastChar] || ''; |
| for (var leftBrace in autoPairs) { |
|
|
| |
| if (leftBrace === secondLastChar && autoPairs[leftBrace] === lastChar && nextChar === lastChar) { |
| e.target.value = value.substring(0, start) + value.substring(start + 1); |
| e.target.selectionStart = e.target.selectionEnd = start; |
| return; |
| } |
| } |
| e.target.value = value.substring(0, start) + pairChar + value.substring(start); |
| e.target.selectionStart = e.target.selectionEnd = start; |
| } |
阻止補全
其實,上面的這些行為,并不能一直都有效。所以我又設立了阻止補全。不能說我們按快捷鍵復制粘貼的時候,也順手補全了,也不能說我們在按刪除鍵的時候也給補全了(o( ̄▽ ̄)d 這樣就陷入死循環了 ~ 一個永遠也刪不掉的右括號)
| AreaEditor.prototype.isPreventAuto = false; |
檢測到一些按鍵 或 粘貼事件 addEventListener('paste', ...);
后,就不再執行。
編輯框抖動
一個不知起源于何時的 textarea
特性,當行數比較大時,回車會讓輸入框抖一下,十分影響..... 起碼影響心情:
(可以試試在 https://www.ccgxk.com/cellhtmleditor.html 這個實時編輯頁面進行調試下面的這些代碼 ~)
| <textarea id=demoEditor rows=10 placeholder="演示編輯框抖動"></textarea> |
| <script src="https://cdn.jsdelivr.net/gh/kohunglee/areaeditor/src/areaeditor.1.0.js"></script> |
| <script> |
| var content = ''; |
| for(var i = 0; i < 200; i++){ |
| content += i+'\n' |
| } |
| demoEditor.value = content; |
| </script> |

于是

這是瀏覽器的原生特性(bug),意義不明。
當然,這個問題好解決,只需要記錄下高度,在完成我們的操作后將高度還原即可。
在空行按下刪除鍵,清空
在一個只存在縮進、空格的行,我們按下刪除鍵,不出意外,目的只有一個,就是將這行刪干凈。所以,我又加上這樣一個功能,在空行按下刪除鍵,清空。
本以為只是一兩行代碼才能完成,最后搞了一坨:
| if (e.key === 'Backspace') { |
| var contentArr = value.split('\n'); |
| var startLine = (value.substring(0, start).match(/\n/g) || []).length; |
|
|
| |
| if(start === end && (/^[\s\t]*$/.test(contentArr[startLine]) && contentArr[startLine] !== '')){ |
| e.target.selectionStart = this._arrSum(contentArr, startLine) + startLine; |
| e.target.selectionEnd = start; |
| } |
| } |
體驗感大大的好。
封裝代碼
我想把它做成一個第三方的引用庫,那么我就要盡可能寫的標準一點。
模仿 jQuery、Zepto 將它封裝了一下。
首先是 UMD 模塊化,
| (function (global, factory) { |
| if (typeof define === 'function' && define.amd) { |
| define([], factory); |
| } else if (typeof module === 'object' && module.exports) { |
| module.exports = factory(); |
| } else { |
| global.AreaEditor = factory(); |
| } |
| }(this, function () { |
| 'use strict'; |
|
|
| |
| function AreaEditor(element, options = {indentType : { type: 'space', count: 4 }}) { |
|
|
| ..... |
|
|
| ..... |
|
|
| return AreaEditor; |
| })); |
第一個,是依照過去我們常用的 requireJS 要求的格式來定義的,這是一個模塊化工具,讓我們的 JS 文件們可以按需加載。雖然在 ES6 時代日薄西山,但還是能用得到。學習這個可以看阮一峰大佬的這篇文章。
第二個是 CommonJS 環境使用的,也就是服務器端。主要用于 node.js 。可以供 require('./logger.js')
這種語法使用。
第三個就是我們現在使用的這種方式,即瀏覽器直接調用。
里面的 factory()
是工廠函數, 'use strict';
及以下就是這個函數的內容。我們只需將我們的 AreaEditor
函數寫入即可。
AreaEditor()
是構造函數,通常使用大寫字母開頭。就好像一個對象一樣,不過調用的時候需要寫 new ,有了上面的 factory()
的處理后,不寫 new 關鍵字也可以。
怎么壓縮 JavaScript 代碼
初步壓縮,主流的有三個選擇:
第一個是谷歌公司使用 Java 搞的智能壓縮,可以分析代碼把冗余給鏟除,確保結果不變。不過效果和下面兩個差不多。
第三個 Terser 我們可能都間接用過,它是 webpack 這個打包工具的默認壓縮工具。其實它是在 UglifyJS 基礎上迭代的。
在這里,我使用了 UglifyJS 。它也會智能將代碼里的多余的地方優化,以實現盡可能小的體積:
| var a=1;var a=2; |
| |
| var a=2 |
|
|
| alert('a' + 'b'); |
| |
| alert("ab"); |
|
|
| function a(){ |
| var info = 'a' + 'b'; |
| alert(info); |
| } |
| |
| function a(){alert("ab")} |
它本身是一個 node.js 庫,無法直接在瀏覽器上運行,不過有大佬將其轉化為了瀏覽器端。我又將其配置表和界面給翻譯成中文,就是下面這個地址:
https://git.ccgxk.com/jscompression/jsminifier.html


使用它生成后的代碼,還沒有到 2kb 這個階段,然后我又找到了個利用字母出現頻次,構成字典,然后進行壓縮的 js 壓縮工具。
常出現在一些 代碼高爾夫 炫技比賽里,比比誰能用更小的體積實現更復雜的功能或游戲這種比賽里,類似于 https://js13kgames.com/ 。
我把那個界面搞的漂亮了一點,然后將全局變量改了一下名,就是這個頁面:
https://git.ccgxk.com/jscompression/jscrush.html

這樣就差不多 2kb 了。
轉自https://www.cnblogs.com/duyuanshang/p/18856762
該文章在 2025/6/5 9:40:49 編輯過