LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

自制体积不到 2kB 的代码编辑器,areaEditor.js,增强 textarea 标签的代码编辑体验

freeflydom
2025年6月5日 9:35 本文热度 145

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');  // 选中所有的 textarea 元素

var editor = new AreaEditor('.code-editor');  // 选中所有 class 为 code-editor 的 textarea 元素

var editor = new AreaEditor('#code-editor');  // 选中 id 为 code-editor 的 textarea 元素

缩进功能

最开始,我找了很多资料,发现貌似没人有针对 <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') {  // 按下 TAB 键

           e.preventDefault();  // 阻止默认事件

           e.target.value = value.substring(0, start) + '\t' + value.substring(end);  // 添加 tab 字符

           e.target.selectionStart = e.target.selectionEnd = start + 1;  // 光标向前 +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') {  // 按下 TAB 键

           e.preventDefault();  // 阻止默认事件

           e.target.value = value.substring(0, start) + tabChar + value.substring(end);  // 添加 tab 字符

           e.target.selectionStart = e.target.selectionEnd = start + indentCount;  // 光标向前 +1

       }

   });

</script>

代码里 Array(indentCount + 1).join(' ') 是一种技巧,可以返回 n 个空格,比如 Array(5 + 1).join(' ') 就是 5 个空格,Array(3 + 1).join(' ') 就等于 3 个空格。

根据这个原理,代码的收缩也实现了。不过,还是有很多坑,习惯上的坑:

  • 收缩后,光标的选择怎么计算?

  • 如果光标的 start 位于缩进,比如空格上,又该怎么计算??

  • ...

增加缩进很简单,减少缩进没想到这么复杂,计算逻辑还挺抽象,花了好几个小时润色,才算完美解决,下面是最终的代码:


// TAB 键盘的处理

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) {  // 按下 Shift 键(减少缩进)。

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);



// 计算最小可缩进值,以防止起始位置(如行5)缩进至行4。

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 {  // 单独按 Tab 键(增加缩进),这个简单,文章上面已经写了

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) {  // UMD 模式

       define([], factory);  // AMD

   } else if (typeof module === 'object' && module.exports) {

       module.exports = factory();  // CommonJS

   } else {

       global.AreaEditor = factory();  // 这样写,可以不用 new 关键字来调用

   }

}(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 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved