layui富文本编辑器layedit.js改造:添加字体大小、颜色和背景色 简介这份资源是 layui 富文本编辑器 layedit.js 的完整功能演示与源码包面向使用 layui 框架进行模块化 Web 开发的前端工程师尤其适合需要快速集成内容编辑能力的项目。它围绕字体大小、字体颜色与背景颜色三大核心编辑功能展开同时覆盖插入表格、批量上传图片、图片尺寸与对齐控制、超链接与视频插入、全屏编辑、段落格式及锚点导航等扩展能力可满足从博客文章到复杂网页内容制作的多种场景。压缩包共 126 个文件以 75 个 gif 演示图、29 个 js 脚本、6 个 css 样式、4 个 html 页面及 png、woff、ttf、svg 等字体图标资源为主整体约 849KB结构轻量便于查阅与二次开发。目前已有 262 人学习下载读者可借此快速掌握 layedit.js 的字体与颜色配置方式并参考其目录组织与功能实现思路高效完成编辑器集成与定制。1. layui 富文本 layedit.js 的字体大小与颜色改造为什么默认工具栏总差一口气用 layui 做后台管理系统的同学大概率都碰过 layedit.js 这个富文本编辑器。它轻、跟 layui 风格统一、集成成本低但真到业务里要写公告、发通知、编辑商品详情时你会发现工具栏上只有加粗、斜体、下划线、链接、图片这几样字体大小、字体颜色、背景颜色统统没有。运营同事一句“这段字放大一点、标红、再加个黄底”你就得回去翻源码。这个标题讲的就是这件事在 layedit.js 现有架构上把字号、前景色、背景色三个能力补进去并且保证插入的内容能正确回显、能提交到后端、不会污染原有格式。它适合两类人一类是正在用 layui 做中后台、被富文本格式需求卡住的开发者另一类是接手了老项目、不想换编辑器只想做增量改造的维护者。核心难点不在“加三个按钮”而在于 layedit.js 用的是document.execCommand这套老 API命令怎么传参、选区怎么保住、样式怎么落成内联每一步都有坑。下面按“先搞懂它怎么工作再动手改最后排错”的顺序讲透。2. 先搞懂 layedit.js 的执行链路从工具栏点击到 HTML 落库2.1 layedit.js 的工具栏是怎么渲染出来的layedit.js 的工具栏不是写死在 HTML 里的而是通过配置项tool数组动态生成。默认配置大致是这样一组字符串每个字符串对应一个内置命令// layedit.js 内部默认 tool 配置简化示意 tool: [ strong, italic, underline, del, |, left, center, right, |, link, unlink, face, image ]每个字符串在渲染时会映射到一张图标表和一个点击处理函数。|是分隔符不绑定事件。理解这一点很关键你要加字号、颜色、背景色本质上就是往这个数组里塞自定义标识再在点击分发逻辑里接住它。很多人第一次改的时候直接去改 HTML 模板结果发现刷新就没了就是因为工具栏是运行时生成的。2.2 execCommand 的参数规则与选区丢失问题layedit.js 底层调用的是浏览器原生的document.execCommand(command, showUI, value)。三个能力对应的命令分别是能力execCommand 命令value 传参形式字体大小fontSize1~7 的档位字符串字体颜色foreColor颜色值如#ff0000背景颜色hiliteColor或backColor颜色值如#ffff00这里第一个坑就来了fontSize只认 1 到 7 这七个档位它生成的是font size4这种老标签而不是font-size:16px。如果你直接传16px命令会静默失败什么都不发生。第二个坑是选区点击工具栏按钮的瞬间编辑区的焦点会转移到按钮上原来的文字选区就丢了execCommand作用不到任何内容。所以必须在按钮的mousedown阶段阻止默认行为把焦点留在编辑区。2.3 内容回显时样式为什么会被吃掉内容存到数据库再取出来塞回编辑区常见现象是字号没了、颜色变黑白。原因通常有两个一是后端或前端做了 XSS 过滤把font标签或style属性过滤掉了二是编辑器初始化时用了layedit.set的过滤配置把不认识的标签清理了。所以改造时不能只顾插入还要确认“存进去的 HTML”和“读出来的 HTML”走的是同一套白名单。这一点在选型阶段就要想清楚是继续用font标签还是统一转成span style两条路后续的过滤规则完全不同。3. 动手改造给 layedit.js 加字号、前景色、背景色三个按钮3.1 扩展 tool 配置并注册自定义命令第一步是告诉 layedit 工具栏要多几个按钮。不要直接改源码里的默认数组推荐在初始化时用layedit.set覆盖这样升级 layui 版本时改动可控// 在 layedit 初始化之前调用 set扩展工具栏 layedit.set({ // 在原有工具后面追加自定义标识 tool: [ strong, italic, underline, del, |, left, center, right, |, fontsize, forecolor, bgcolor, // 三个自定义按钮 |, link, unlink, face, image ], // 隐藏默认的“插入代码”等不用的项按需保留 hideTool: [code] });tool数组里的fontsize、forecolor、bgcolor是我们自己起的标识layedit 不认识它们所以下一步必须补上图标和点击逻辑否则工具栏会渲染出空白按钮。参数说明tool决定按钮顺序和分组|控制视觉分隔hideTool用来屏蔽不需要的内置项避免工具栏过长换行。3.2 用 mousedown 保住选区再执行 execCommand这是整个改造里最关键的一段。核心思路是按钮按下时不让它抢焦点然后在编辑区还保有选区的情况下执行命令。下面这段代码可以直接放进页面脚本里// 等待 layui 和 layedit 加载完成后执行 layui.use([layedit, layer], function () { var layedit layui.layedit; var layer layui.layer; // 建立编辑器实例拿到 index 和编辑区 DOM var editIndex layedit.build(content, { height: 320, tool: [ strong, italic, underline, del, |, left, center, right, |, fontsize, forecolor, bgcolor, |, link, unlink, face, image ] }); // 拿到编辑区 iframe 内的 bodyexecCommand 要作用在它上面 function getEditBody() { var iframe document.getElementById(LAY_layedit_ editIndex); return iframe.contentWindow.document.body; } // 通用执行函数先聚焦编辑区再执行命令 function execOnSelection(cmd, value) { var body getEditBody(); body.focus(); // 把焦点还给编辑区恢复选区 document.execCommand(cmd, false, value); } // 绑定三个自定义按钮用事件委托监听工具栏点击 document.addEventListener(mousedown, function (e) { var target e.target; // 通过自定义属性识别按钮实际项目中建议给按钮加>// 开启用 CSS 样式代替 font 标签 document.execCommand(styleWithCSS, false, true); // 字号映射表把 1~7 档位换算成 px var sizeMap { 1: 10px, 2: 13px, 3: 16px, 4: 18px, 5: 24px, 6: 32px, 7: 48px }; // 用 fontSize 命令生成 font 标签后再替换成 span function applyFontSize(px) { execOnSelection(fontSize, 7); // 先占位 var body getEditBody(); // 把刚生成的 font 标签替换为带 style 的 span var fonts body.querySelectorAll(font[size7]); fonts.forEach(function (f) { var span document.createElement(span); span.style.fontSize px; span.innerHTML f.innerHTML; f.parentNode.replaceChild(span, f); }); }逻辑说明styleWithCSS让foreColor、hiliteColor输出style而不是color属性兼容性更好。字号因为fontSize不支持 px用“先插入占位标签再替换”的方式绕过去。参数说明sizeMap的 px 值可以按设计稿调整不必和浏览器默认一致替换时用querySelectorAll而不是getElementById因为一次可能选中多段文字。3.4 提交前做一次内容清洗与白名单校验插入的样式能不能安全落库取决于清洗规则。建议在提交表单前统一走一遍白名单过滤只保留允许的标签和属性// 提交前清洗只保留白名单标签和 style 属性 function cleanHtml(html) { var allowTags [p, br, strong, em, u, span, a, img, div]; var allowStyle [font-size, color, background-color]; var div document.createElement(div); div.innerHTML html; // 遍历所有节点移除不在白名单的标签 var all div.querySelectorAll(*); all.forEach(function (node) { if (allowTags.indexOf(node.tagName.toLowerCase()) -1) { node.replaceWith.apply(node, node.childNodes); // 拆掉标签保留文字 return; } // 清理 style只留允许的属性 var style node.getAttribute(style); if (style) { var kept style.split(;).filter(function (item) { var key item.split(:)[0].trim().toLowerCase(); return allowStyle.indexOf(key) ! -1; }); node.setAttribute(style, kept.join(;)); } }); return div.innerHTML; }逻辑说明白名单同时管标签和样式属性防止有人通过富文本注入脚本或外链样式。参数说明allowTags和allowStyle按业务需要增减比如商品详情允许table公告就不需要。清洗放在提交前而不是插入时是为了不打断编辑体验。4. 避坑与排查改造 layedit.js 时最容易翻车的五个点4.1 点了按钮没反应选区悄悄丢了现象选中一段文字点字号按钮弹窗出来后确认文字毫无变化。原因按钮点击时焦点转移execCommand找不到选区命令作用在空位置。解决在mousedown阶段preventDefault并在执行命令前调用body.focus()。如果用了弹窗layer.prompt弹窗关闭后还要再 focus 一次因为弹窗本身也会抢焦点。4.2 字号传了 px 却毫无效果现象execCommand(fontSize, false, 16px)执行后没反应。原因fontSize只接受 1 到 7 的档位传其他值浏览器直接忽略而且不报错属于典型的“黑匣子”行为。解决要么传档位字符串要么走 3.3 节的“占位再替换”方案。排查时可以在控制台单独执行一次命令看 DOM 里有没有生成font标签没有就说明参数被拒了。4.3 背景色在部分浏览器不生效现象hiliteColor在 A 浏览器正常在 B 浏览器没反应。原因hiliteColor不是所有内核都支持部分环境只认backColor而backColor在某些浏览器里又只作用于整块背景。解决先执行styleWithCSS开启 CSS 模式再优先用hiliteColor失败时降级到backColor。判断是否生效看选中文字外层有没有生成带background-color的span。4.4 存进去有颜色读出来变黑白现象编辑时颜色正常保存后重新打开颜色全没了。原因后端或前端框架的 XSS 过滤器把style属性或font标签过滤了。解决确认过滤白名单里包含span、font以及style属性并且style里允许color、background-color、font-size。如果用的是统一清洗函数确保读写两侧用的是同一份规则别一边放行一边拦截。4.5 多次设置字号后标签层层嵌套现象同一段文字反复调字号DOM 变成span span span套娃体积越来越大。原因每次execCommand都在现有标签外再包一层不会合并。解决在清洗阶段做一次扁平化遇到同类型span且样式可合并的就合并或者限制用户对同一选区重复设置设置前先移除已有的字号样式。这个坑在长文编辑场景里尤其明显血泪经验是尽早加清洗别等数据脏了再回头修。5. 进阶技巧把三个按钮做成可配置的取色器与字号下拉前面用的是弹窗输入和写死颜色实际项目里体验更好的做法是字号做成下拉菜单颜色做成取色面板并且把可选值抽成配置项方便不同业务复用。下面给一个可落地的结构// 可配置的样式面板配置 var styleConfig { fontSizes: [ { label: 小, value: 12px }, { label: 正常, value: 14px }, { label: 中, value: 16px }, { label: 大, value: 20px }, { label: 标题, value: 28px } ], colors: [#333333, #ff0000, #ff9900, #00aa00, #0066ff, #9900ff], backgrounds: [#ffff00, #ffcccc, #ccffcc, #cce5ff, #eeeeee] }; // 渲染一个简单的下拉选中后调用 applyFontSize function renderSizeMenu(container) { var html styleConfig.fontSizes.map(function (item) { return a hrefjavascript:; style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />