简介:这是一套面向前端开发者的 layui 富文本编辑器 layedit.js 功能增强资源,重点解决字体大小、字体颜色与背景颜色的自定义设置需求,适合需要在内容编辑场景中快速集成或改造 layedit 的读者。资源包共 126 个文件,压缩后约 849KB,其中包含 75 个 gif 图标、29 个 js 脚本与 6 个 css 样式表,另有 4 个 html 示例页面、3 个 png 图片及多种字体格式文件,便于直接对照调试和二次开发。该资源可帮助搭建完整的 layedit 工具栏配置,覆盖字体类型、字号缩放、前景色与背景色切换,同时可作为表格插入、图片批量上传、超链接与视频插入、全屏编辑、段落与锚点设置等功能的样式与逻辑参考。目前已有 260 人学习下载,对希望以轻量方式完善 layui 富文本体验、减少重复开发的开发者而言,是一份实用且便于查阅的素材。 做后台管理系统做久了,你会发现富文本这块是最容易扯皮的。我用 layui 做过好几个项目,每次都逃不过同一个需求:默认的 layedit.js 富文本编辑器只有加粗、斜体、下划线、超链接这几个基础按钮,用户一上来就问“字体大小在哪调?字体颜色怎么改?背景色能不能标一下?”这几个功能默认根本没做,但业务上又绕不开。这篇文章把我踩过的坑、最后稳定的实现方案完整写一遍,给正在用 layui 做后台的开发者一点参考。内容会从 layedit 的底层原理讲起,再给一套不碰源码的扩展方案,最后附上我实际遇到过的兼容性问题和解决记录。
1. 先弄清楚 layedit 的底细,才好动手改
1.1 layedit 默认到底缺了什么
layui 自身带的 layedit.js 是一个轻量级富文本,外观上跟 layui 风格统一,集成简单,一个layedit.build()就能把编辑器挂到 textarea 上。但它本质上只是把页面里的 textarea 藏起来,换成一个 iframe,iframe 内部的 body 设置了contenteditable="true",用户编辑的其实是 iframe 里的内容。所以后续所有字体操作,都要在 iframe 内部文档上做,而不是操作父页面的 DOM。
默认工具栏我在代码里核对过,大概只有这些:
strong:加粗italic:斜体underline:下划线del:删除线|:分隔符link:添加链接unlink:取消链接face:表情
一眼就能看出来,字体大小、字体颜色、背景颜色这三个编辑最常用的工具,官方根本没有封装。网上搜这个问题的人特别多,说明这不是我一个人的需求。要解决它,先得明白 layedit 是怎么处理文本格式的。
1.2 改字体颜色背景色靠的是哪几个底层命令
layedit 的所有格式化动作,底层走的都是浏览器内置的document.execCommand。我没去翻源码前也不知道它这么“原始”,后来在控制台试了一下,发现编辑器内所有加粗、斜体操作都会落到这条老命令上。execCommand 是浏览器提供给可编辑区域的一条命令通道,传入不同的命令名和参数就能修改选区内的内容。
跟标题需求直接相关的三条命令是:
fontSize:设置字号,但参数只支持 1 到 7 的整数,对应 HTML 里<font size="1">到<font size="7">,不是像素值foreColor:设置字体颜色,参数是色值,比如#ff0000hiliteColor:设置文字背景高亮色,也就是我们常说的“荧光笔”效果,参数同样是色值
知道这三个命令,就等于拿到了操作编辑器内部内容的钥匙。但实际落地的坑在于:命令是能调用,但浏览器之间对命令的解析有差异,而且怎么让用户方便地选色、选字号,也得自己想办法。这是我在实现前没预料到的,后面会一个个展开。
2. 两种扩展方案,我推荐你用第二种
2.1 改源码:短期爽,升级苦
网上很多教程的做法是直接改layedit.js源文件,把按钮定义和点击事件写死在源码里。这个方案不是不行,但有个明显的后遗症:以后 layui 版本升级,源文件一覆盖,你改的东西全没了。而且 layedit.js 源码压缩过,在里面找位置、加代码,改起来很痛苦,出了问题也不容易排查。
我自己第一次做的时候为了省事改过一版,加了个字体颜色按钮,改了大概十几行代码。当时能跑,但后面 layui 升级到新版本,覆盖了这个文件,客户反馈颜色按钮消失,我排查了半天才发现是源码被替换了。从那以后,我不再建议改源码,除非你能保证以后永远不升级。
2.2 工具栏接管:不碰源码也能加按钮
更稳的思路是:让 layedit 正常初始化,然后我自己往它的工具栏区域里追加自定义控件。layedit 在初始化后,工具栏按钮会渲染在编辑器容器上方的.layui-layedit-tool这个容器里,里面的按钮都是类似<i lay-filter="bold" class="layui-icon layui-icon-bold"></i>这样带lay-filter属性的图标。我们完全可以用 jQuery 在这个工具栏里插入自定义按钮、下拉框或颜色选择器,然后绑定自己的事件。
这样做有几个好处:
- 不动源码,升级 layui 不影响现有功能
- 自定义逻辑独立,出错时只需要排查自己那部分代码
- 交互可以做得更顺手,比如用 input type=color 当取色器,或加一组预设色板
我在实际项目中一直用这个方案,稳定跑了一年多,没出过问题。下面的实操就是基于这个思路写的。
3. 实操全流程:字号、颜色、背景色接入
3.1 初始化与自定义工具栏注入
先按正常方式初始化 layedit,textarea 可以自己定义 id,比如id="content":
var layeditIndex = layedit.build('content', { tool: ['strong', 'italic', 'underline', 'del', '|', 'link', 'unlink', 'face'], height: 300 });初始化完成后,把自定义控件塞进工具栏。工具栏容器可以用layui生成的结构去定位,更简单的方式是等编辑器渲染后,通过类名去查找:
var $toolBar = $('.layui-layedit-tool').eq(0);然后追加自定义控件。这里根据我自己的排版习惯,在工具栏后面依次追加字号下拉框、颜色选择器和背景色选择器。代码示例如下:
<div id="customToolbar" style="display:inline-block; vertical-align:middle; padding-left:6px;"> <select id="fontSizeSel" lay-ignore style="height:26px; width:72px; font-size:12px;"> <option value="">字号</option> <option value="12">12px</option> <option value="14">14px</option> <option value="16">16px</option> <option value="18">18px</option> <option value="20">20px</option> <option value="24">24px</option> <option value="32">32px</option> <option value="48">48px</option> </select> <input type="color" id="fontColorPicker" title="字体颜色" style="width:30px; height:26px; vertical-align:middle;"> <input type="color" id="bgColorPicker" title="背景颜色" style="width:30px; height:26px; vertical-align:middle;"> </div>注意 select 上加lay-ignore,防止 layui 的 form 模块把原生下拉重新渲染,否则取值麻烦。input type=color 是浏览器原生取色器,安卓和手机端上体验稍弱,但后台场景大多是电脑端,够用。
追加的 HTML 不要放在页面里,而是通过 JS 插入,这样可以保证编辑器动态创建时也能正确对位:
$($toolBar).append($('#customToolbar'));3.2 字号下拉的实现细节
字号这块最大的坑是fontSize命令不支持像素值,只认 1 到 7。如果你直接执行document.execCommand('fontSize', false, 12),浏览器不认 12 这个数字,只有 1~7 有效。我一开始就掉进这个坑里,后来换了个稳妥的做法:先用fontSize命令生成<font size="7">,再在编辑器内部把font[size]统一替换成带像素值的<span style="font-size:48px">,这样既能保留选区位置,又能得到像素级控制。
具体实现代码如下:
var frame = layedit.getFrame(layeditIndex); var doc = frame.contentDocument; $('#fontSizeSel').on('change', function() { var val = parseInt(this.value, 10); if (!val) return; doc.execCommand('fontSize', false, 7); // 把编辑器内所有 font[size] 统一替换为内联样式 var fontList = doc.querySelectorAll('font[size]'); fontList.forEach(function(font) { var oldSize = font.getAttribute('size'); var px = oldSize === '7' ? 48 : oldSize === '6' ? 32 : oldSize === '5' ? 24 : 16; font.removeAttribute('size'); font.style.fontSize = px + 'px'; // 孤立的 font 标签可以换成 span,语义更干净 var span = doc.createElement('span'); span.style.fontSize = px + 'px'; span.innerHTML = font.innerHTML; font.parentNode.replaceChild(span, font); }); });为什么固定用 size 7?因为它的字号最大,替换时不会因为浏览器默认字号映射造成偏差。你也可以按 1~7 分别映射成你想要的像素值,上面代码里做了个简单的映射表,实际项目里你可以按产品需求调整成 10px~48px 任意数值。
这里还有个小技巧:执行完替换之后,旧的<font>会被替换成<span>,选区可能丢失。所以如果要在手机上用,建议在 change 事件触发前保存当前选区,替换后再恢复选区,保证连续设置字号时不会中断。桌面端浏览器一般没事,但这是兼容性细节,我在后面会再提到。
3.3 颜色选择器与背景色命令
字体颜色和背景高亮的实现思路完全一致,只是命令不同。字体颜色用foreColor,背景颜色用hiliteColor。绑定 input 事件,取色器一变,立刻应用:
$('#fontColorPicker').on('input', function() { var color = this.value; doc.execCommand('foreColor', false, color); }); $('#bgColorPicker').on('input', function() { var color = this.value; doc.execCommand('hiliteColor', false, color); });如果项目只支持 Chrome、Edge、Firefox,这段代码已经可以用了。但如果是给客户做的系统,客户用 Safari 的情况你没法控制,Safari 对hiliteColor的支持不太好,会出现“命令执行了,但背景色没变”的问题。我的兼容方案是做一个能力检测,如果不支持就退回backColor:
function applyBgColor(color) { var support = false; try { doc.execCommand('hiliteColor', false, '#000000'); support = true; } catch (e) { support = false; } if (support) { doc.execCommand('hiliteColor', false, color); } else { doc.execCommand('backColor', false, color); } }backColor和hiliteColor的区别是:backColor在部分浏览器里会修改整个块级区域的背景色,而不只是选中的那行文字。真遇到这种情况,我建议别忘了状态检查——如果 Safari 上试出来背景色没有按预期输出,说明命令不可靠,就得用更底层的方式手动包裹<span>,具体思路我在后面的排坑章节细说。
3.4 失焦问题与选区恢复
这是最隐蔽的一个坑。用 input type=color 选择颜色时,点击取色器按钮的一瞬间,编辑器 iframe 会失去焦点,原来选中的文字选区就没了。没有选区,execCommand就不知道要改谁,结果就是颜色根本没应用上。
第一次遇到这个问题,我检查了半天代码,命令参数完全正确,但就是没效果。最后发现是选区丢失。解决办法是在打开取色器之前,记录当前的 selection 和 range,等取色完成后再恢复:
var lastRange = null; $('#fontColorPicker').on('mousedown', function(e) { e.preventDefault(); // 阻止默认行为,保住焦点 var sel = doc.getSelection(); if (sel && sel.rangeCount > 0) { lastRange = sel.getRangeAt(0).cloneRange(); } }); $('#fontColorPicker').on('input', function() { if (lastRange) { var sel = doc.getSelection(); sel.removeAllRanges(); sel.addRange(lastRange); } var color = this.value; doc.execCommand('foreColor', false, color); });mousedown 里 preventDefault 是为了阻止取色器抢焦点。注意顺序:先阻止默认,再保存 range,这样编辑器焦点还没丢,range 还在。取色器 input 事件触发时再恢复 range,最后执行命令。字体大小下拉和背景色选择器也要做同样处理,否则它们同样会触发失焦问题。
3.5 内容同步是最后一步
编辑器里显示的是 iframe 内部的东西,textarea 里保存的其实是编辑器的初始值。提交表单时如果不做同步,用户写的全部内容都会丢。layedit 提供了一个同步方法:
layedit.sync(layeditIndex);建议在表单提交事件里先执行同步:
form.on('submit(*)', function() { layedit.sync(layeditIndex); // 继续走提交逻辑 return true; });如果你是在 AJAX 提交前取内容,也可以直接用layedit.getContent(layeditIndex),这个方法本身就做了同步并返回 HTML 字符串,比 sync 再手动拿 textarea 值更省事。
4. 踩坑记录与排查速查表
4.1 Safari 下背景色失灵的手动包裹方案
前文说了hiliteColor在 Safari 上不可用,backColor又会把整个块变成背景色,效果跟荧光笔完全不同。如果你必须兼容 Safari,我测试过的最可靠方案是手动操作 Range 对象,给选中内容包裹<span>:
function applyBgColorSafari(color) { var sel = doc.getSelection(); if (!sel.rangeCount) return; var range = sel.getRangeAt(0); var span = doc.createElement('span'); span.style.backgroundColor = color; try { range.surroundContents(span); } catch (err) { // 选区跨多个块级元素时会报错,将选区内容包到 span 里 var frag = range.extractContents(); span.appendChild(frag); range.insertNode(span); } }这里要注意,surroundContents有个限制:当选区包含多个不完整的段落节点时会抛异常,所以 catch 里用了先 extractContents 再 insertNode 的迂回方案。这套代码在 Safari 和桌面 Chrome 上都验证过,能稳定给选中文字加背景色。
4.2 提交后内容里全是 font size 标签
如果你偷懒直接用fontSize命令不做后续替换,提交到后台的内容会长这样:
<font size="7">标题文字</font>font标签虽然浏览器还能识别,但维护性很差,而且size属性和像素之间没有稳定的换算关系。我在代码里用的是把<font size>替换成<span style="font-size:xxpx">的方案,提交出去的内容干净统一,后续做导出 PDF、Word 时也更容易适配。如果你想要不同字号更精确,映射表可以自己定义,完全看你业务需要。
4.3 工具栏按钮状态不同步
layedit 内置的加粗、斜体按钮,点击后会有个.layui-icon-active高亮状态,但我自定义的颜色和字号控件做不到自动回显。比如用户把字号改成 16px,光标移到别处,字号下拉还停留在默认位置;选中了一段 20px 的文字,下拉也不会有联动。这个在纯 layedit 框架下基本没法完美解决,需要监听 iframe 的 keyup 和 mouseup 事件,然后用doc.queryCommandValue('fontSize')去回填状态。我做过一版,但判断逻辑复杂且不完美,最后只在 keyup 时简单刷新字号下拉,变色状态直接放弃。后台场景下这个影响不大,文章写完了自然知道字号效果。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 点颜色按钮没反应 | iframe 失焦,选区丢失 | mousedown 里 preventDefault 并保存 range |
| 背景色点了没变化 | Safari 不支持 hiliteColor | 改用 backColor 或手动包裹 span |
| 字号执行后没效果 | fontSize 传了像素值,不是 1~7 | 先传 7 再统一替换为 span 的像素样式 |
| 提交后内容丢失 | 没有同步编辑器内容 | 提交前调用 layedit.sync(index) |
| 追加的控件被换行 | 工具栏宽度不够或容器布局问题 | 用 display:inline-block 并设置 lay-ignore |
| 取色器颜色带透明度 | input type=color 是纯色值格式 | 换成预设色板,自定义颜色面板 |
4.5 如果不想自己造轮子,换编辑器也值得考虑
如果你正在做一个新项目,layedit 不是唯一选择。layui 官方后来也意识到富文本这块比较弱,推荐过集成第三方编辑器。我自己的建议是:老项目维护,用我这套扩展没问题;新项目或者对编辑体验要求高、需要上传图片、需要表格、需要粘贴 Word 格式保真的场景,可以考虑 wangEditor、Tinymce 这类专业编辑器,它们本身就有完整的字体颜色、背景色、字号联动方案,比自己补 layedit 省心得多。不过这是看需求定的,有些项目风格统一优先,layedit 也还能打。
最后再分享一点个人体会。这套扩展方案我前后花了差不多一个下午才完全调通,核心花销不在代码,而在排查那些“命令没效果”的隐形问题。其实每个问题回头看看都挺简单的,但当时不自查的话,很容易陷入改源码、加 console、反复初始化编辑器的死循环。现在我把这些记录整理成文,也是希望后来者别再掉进同一个坑。如果按照上面的步骤操作后仍有个别浏览器行为不一致,建议优先检查一下是否真的是选区丢失,十有八九问题出在那里。
本文还有配套的精品资源,点击获取