drop-shadow() 是唯一能严格贴合文字轮廓(含透明通道)做外发光的 css 方式,需设偏移为 0 0,避免 overflow: hidden 裁剪,safari 需 -webkit- 前缀,ie 不支持;浮雕效果应使用 svg feconvolvematrix 滤镜而非 text-shadow。

text-shadow 做不了外发光?别硬套,用 drop-shadow() 更准
SVG Filter 的 drop-shadow() 是目前唯一能严格贴合文字轮廓(尤其是带透明通道的 SVG 文字或 PNG)做外发光的 CSS 方式。它不是模糊矩形框,而是逐像素采样 alpha 值后生成阴影——所以哪怕你用 <svg><text></text></svg> 写了带镂空、渐变描边的文字,drop-shadow() 依然能包住真实边缘。
常见错误是把它当 box-shadow 用:写成 drop-shadow(2px 2px 4px #ff0) 就会偏移出光,失去“包裹感”。正确做法是前两位必须为 0 0:
text {
filter: drop-shadow(0 0 8px #ffcc00) drop-shadow(0 0 16px rgba(255, 100, 0, 0.4));
}
注意点:
- 必须确保文字容器没设
overflow: hidden,否则发光部分会被裁掉 - Safari 旧版本需加
-webkit-filter前缀 - IE 完全不支持,得用
text-shadow多层模拟作降级(但边缘会发虚) - 若文字是纯 HTML
<h1></h1>,drop-shadow()仍有效;但若用了background-clip: text,某些 Firefox 版本可能不触发滤镜,建议改用 SVG<text></text>元素封装
浮雕效果别只靠 CSS,SVG <feconvolvematrix></feconvolvematrix> 才可控
HTML 文本直接用 text-shadow 模拟浮雕(比如 -1px -1px 0 #fff, 1px 1px 0 #666)确实快,但它本质是“假浮雕”:没有方向梯度计算,无法响应光照角度变化,也无法和 Canvas 动态内容联动。真要基于 SVG Filter 实现可编程、可复用的浮雕,得走 <feconvolvematrix></feconvolvematrix> 路线。
关键参数不是随便填的:
-
order="3"必须显式声明,否则浏览器可能忽略 kernel -
kernelMatrix="2 0 0 0 -1 0 0 0 -1"是典型右下方向浮雕核,比1 0 0 0 -1 0 0 0 0更稳,抗噪性好 -
preserveAlpha="true"必须开,否则带透明背景的文字阴影区域会灰蒙蒙失真 -
edgeMode="duplicate"比默认的wrap更适合文字边缘,避免首尾像素错位
定义位置必须在同文档内(不能外链 SVG),且推荐塞进隐藏 <svg></svg> 中:
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0"><defs><filter id="emboss"><feconvolvematrix order="3" kernelmatrix="2 0 0 0 -1 0 0 0 -1" preservealpha="true" edgemode="duplicate"></feconvolvematrix></filter></defs></svg>
然后用:filter: url(#emboss) 绑定到目标元素。注意 ID 大小写敏感,且不能跨 iframe。
Canvas + SVG Filter 联动时,transferToImageBitmap() 是性能关键
如果你在编辑器里允许用户实时输入文本、调整字号/颜色,再叠加浮雕或发光,那纯 CSS 方案会卡顿——因为每次重排都会触发整个滤镜链重算。此时应把文本先渲染到离屏 <canvas></canvas>,再转成 ImageBitmap,最后塞进带滤镜的 SVG <image></image> 中绘制回来。
这条链路最易卡在两处:
- 用
canvas.toDataURL()导出 base64 → 创建<img>→ 等 onload:慢、内存高、跨域失败风险大 - 直接用
ctx.filter = "url(#emboss)":无效!Canvas 2D 上下文不支持该语法,浏览器静默忽略
正确路径是:
- 创建离屏 canvas,用
ctx.fillText()渲染文字 - 调用
canvas.transferToImageBitmap()(零拷贝,快) - 构造一个含
<image></image>和滤镜的 SVG 字符串,用Blob+URL.createObjectURL()转为可加载地址 - 等 SVG 加载完成,用
ctx.drawImage(svgElement, 0, 0)绘制到主 canvas
这步绕开了 DOM 图片加载队列,也避开了 base64 解码开销,实测在中端手机上 60fps 可维持。
编辑器里动态换滤镜?ID 冲突和尺寸错位是最大坑
很多编辑器尝试让用户从下拉菜单选“浮雕”“发光”“模糊”,结果一换就黑屏或错位——问题几乎都出在 SVG 滤镜 ID 和坐标系上。
ID 冲突很隐蔽:多个组件同时挂载时,如果都往 document.body 插同一个 id="emboss",后挂载的会覆盖前一个,导致旧文本突然失效。解决方法只有两个:
- 用
Symbol()或时间戳生成唯一 ID,比如emboss_${Date.now()}_${Math.random().toString(36).substr(2, 9)} - 把滤镜定义写进每个 SVG 元素内部(不放
<defs></defs>),虽然冗余但绝对隔离
尺寸错位更常被忽略:SVG 滤镜默认作用域是 x="-20%" y="-20%" width="140%" height="140%",但 Canvas 输出是像素精确的位图。若没显式设 filterUnits="userSpaceOnUse" 并匹配 canvas 宽高,滤镜就会被缩放、裁剪甚至消失。务必写全:
<filter id="emboss" x="-10" y="-10" width="220" height="220" filterunits="userSpaceOnUse"></filter>
这里的 10 和 220 要根据你实际 canvas 的宽高动态算,不能写死。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











