news 2026/10/23 4:00:56

SVG换色实战:从fill、currentColor到CSS变量的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SVG换色实战:从fill、currentColor到CSS变量的完整指南

日常被问得最多的需求之一,就是“使用SVG并改变其颜色”。这事听着特别简单,感觉只要把元素的fill属性改一改就能收工,但真正动手之后就会发现,SVG在页面里呈现颜色的规则,和普通PNG、字体图标完全不是一回事。你可能会遇到改了CSS却没有反应、悬浮态颜色只变了一半、导出后的SVG颜色写死在内部标签里、外链SVG根本无法用fill覆盖等问题。

我这几年在跨终端、多主题项目里反复折腾SVG换色,踩了不少坑,也沉淀出一套相对成熟的思路。这里把所有直接可操作的方案、背后的原理、常见的坑,一次性整理给你。不管你是前端新手,还是准备做组件库、主题体系的老手,都能从里面找到能用得上的东西。

1. 先把SVG颜色机制的关键点捋清楚

1.1 颜色不只在fill里,SVG内部有一套独立的“渲染属性”体系

很多人第一次接触SVG,就知道一个fill属性,结果看别人的SVG代码时发现里面还有stroke、stroke-width、stop-color、flood-color、color等一堆跟颜色相关的属性。这就是SVG和PNG最大的不同:它不是一个记录每个像素颜色的位图,而是一套用XML描述“图形元素和它们的绘制规则”的矢量结构。

如果有设计工具直接导出SVG,比如从绘图软件里“另存为SVG”,导出的文件里经常会在<path>或<polygon>标签上直接写好fill="#ff0000"之类的预设颜色。这种写在标签上的颜色属性,专业叫法是“presentation attribute”,你可以把它理解成一种介于HTML属性和CSS样式之间的东西:它有默认优先级,能被CSS样式覆盖,但行为细节和普通CSS属性不太一样。

只改一个静态图标颜色还好,一旦项目里有深色模式、主题换肤、按钮状态切换这类需求,你就会被这些内联颜色卡住。你写了一句.icon { fill: red; },但图标在浏览器里一动不动,原因多半就是这个写死在标签属性里的颜色,没有按照你预期的CSS优先级走。

1.2 优先级顺序,一句话说不完但可以记住结论

SVG颜色相关的优先级规则,简单版本是:内联style样式大于CSS选择器样式,CSS选择器样式大于标签上的fill属性。注意,标签上的fill属性,优先级其实非常低,它相当于一个“默认值”。

这个规则听起来很友好,好像CSS永远能覆盖,但实际开发中还有两个变量:CSS选择器的特异性,和CSS样式加载顺序。当你的代码里写了多个选择器都能作用到同一个<path>上时,特异性更高的那一个赢。比如.wrapper .icon path通常比.icon path的优先级更高,即便后者写在后面也一样。

所以当你发现SVG颜色“改不动”时,先别怀疑是不是CSS不支持SVG,第一步应该检查是不是有更高特异性的选择器,或者有没有行内样式把颜色写死在style里。浏览器开发者工具里选中那个元素,看一下计算样式,大部分改色问题都能在这里现出原形。

1.3 需要主题化时,最稳的姿势是“把颜色写进CSS变量”

如果你只想做一次静态的变色,用fill覆盖问题不大。但如果说要做一套能够整体换主题的界面,我更推荐的做法是:在SVG内部不要写死任何颜色,而是通过CSS变量来控制颜色。比如在一个图标组件的根节点上定义一个--icon-color,SVG内部的路径统一使用fill: var(--icon-color)。这样你切换主题时,只需在更外层的容器上重新定义这个CSS变量,整套图标的颜色就会全部跟着走,不用逐个图标去匹配特殊选择器。

上面这个方案能成立的前提是:SVG必须是内联在页面结构里的,而不是通过<img>标签或CSS背景引用的。外链的SVG是一个独立文档,页面CSS根本进不去,这一点我在后面专门展开讲。

2. 内联SVG的换色实操:class、currentColor、CSS变量

2.1 先用class接管颜色,避免到处写属性

内联SVG,就是把<svg>标签直接写在HTML里。这种形式下,页面里的CSS规则能直接影响SVG内部的所有元素,因此自由度非常高。

第一步建议先把SVG内部标签上的fill、stroke等颜色属性全部清掉。如果是设计工具导出的文件,你可以在文本编辑器里删除类似fill="#333333"这样的内容,但更推荐的做法是保留标签,但统一用CSS接管。比如结构像下面这样:

<svg class="icon icon-user" viewBox="0 0 24 24" aria-hidden="true"> <path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"></path> </svg>

这里没有写任何fill,然后在CSS里统一处理:

.icon { width: 24px; height: 24px; } .icon-user path { fill: #28a745; }

这样做的好处是:当后续需要给图标做hover、focus、深色模式适配时,就不必在SVG内部找分散的填充颜色,而可以直接在CSS统一控制。

提示:如果你担心SVG路径在无样式环境下默认是黑色,可以在CSS里加一条回退规则,确保就算某个图标漏写了路径选择器,颜色也不会太唐突。

2.2 currentColor是最被低估的换色利器

我一开始也不喜欢currentColor,觉得它是CSS颜色关键字里比较“玄”的一个。但它实际上是最适合做图标颜色联动的一种方案。currentColor表示“当前元素继承下来的color属性值”,初次听到会绕,但理解起来也不难:只要给父级设了color,元素内部凡是使用currentColor的地方,就会自动采用这个颜色。

SVG里也可以直接用currentColor作为fill或stroke的值。这种做法非常适合做按钮图标和文字共存的情况。比如:

<button class="btn btn-primary"> <svg class="btn__icon" viewBox="0 0 16 16" aria-hidden="true"> <path fill="currentColor" d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 8.5H8.5v3h-1v-3h-3v-1h3v-3h1v3h3v1z"></path> </svg> 添加 </button>

然后只需要控制按钮的color属性,图标颜色会跟着一起变:

.btn { color: #333; } .btn:hover { color: #007bff; } .btn:disabled { color: #999; }

这里有一段必须注意:currentColor要求SVG内部不能有更高优先级的fill写死,否则fill="currentColor"会被覆盖。调试时如果发现图标颜色不跟随color变化,基本就是SVG里面还有一条硬编码的fill。

使用currentColor还有一个额外的好处是:它天然支持继承。比如深色模式下你把外层容器的color改成浅色,图标颜色也会跟着改,不需要为每个图标再单独维护一个颜色变量。

2.3 CSS变量实现真正的组件级主题换色

如果你的项目里同一个图标需要在浅色、深色等多个主题间切换,我会优先考虑用CSS变量。这个方案的核心思路是:不在图标内部暴露具体颜色值,而是让图标引用一个由外部控制的变量。

先给SVG内部路径设置一个变量引用:

<svg class="icon-calendar" viewBox="0 0 24 24" aria-hidden="true"> <path style="fill: var(--icon-color, #333);" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z"></path> </svg>

这里var(--icon-color, #333)表示:如果外部没有定义--icon-color,就使用默认颜色#333。然后你在页面任意父元素上定义这个变量,就能改变图标颜色:

:root { --icon-color: #2867b2; } .dark-theme { --icon-color: #e0e0e0; } .success-theme { --icon-color: #28a745; }

CSS变量的穿透能力很强,SVG内部的元素也能读取到从外部继承来的变量值,这一点我实测过很多次,非常稳。对比一下用n个class或n条ID去控制n个图标的颜色,CSS变量这套方案在维护性上会好一个量级。

如果你在封装组件,还可以把SVG包装成类似这样的结构:

<!-- 组件内部,箭头图标 --> <svg class="arrow-icon" viewBox="0 0 16 16" aria-hidden="true"> <path style="fill: var(--arrow-color, currentColor);" d="..."/> </svg>

外层使用时只需设置--arrow-color,这个图标就能用于不同状态的按钮、标签、菜单项,而不需要每个地方重新复制一套SVG代码。

2.4 内联SVG的两种常见结构:多路径图标怎么换色

有些图标并不只是一条路径,而是由多个部分组成的,比如一个分享图标里既有主体也有高光小圆点。如果整个图标只需要一种颜色,那么把统一规则写到svg或svg path上就行。如果希望不同部分颜色不同,那就需要给不同路径设置不同class,或者用CSS变量分别接管。

<svg class="icon-complex" viewBox="0 0 24 24"> <path class="icon-complex__body" d="..."/> <circle class="icon-complex__dot" cx="12" cy="12" r="3"/> </svg>
.icon-complex__body { fill: #333; } .icon-complex__dot { fill: #ff5722; }

这种写法在图标组件里很常见,优点是即使在同一个SVG里,也能做到局部换色。不过也要提醒你,不要滥用。如果一个SVG里塞了十几个不同颜色的部分,维护成本会急剧上升。真到了那种复杂度,更合理的做法是考虑把图标拆散成多个独立元素,或者直接改用HTML/CSS拼出图形。

3. 外链SVG和背景图时的换色方案

3.1 你无法用CSS直接控制<img>里的SVG颜色

很多人第一次在项目里把SVG通过<img src="icon.svg">引入页面后,就在CSS里写.icon { fill: red; },然后发现完全无效果。这是因为<img>里的SVG被当作独立文档加载,页面CSS的作用域根本进不去。

同样的道理也适用于CSS背景图:background-image: url("icon.svg")这种用法,背景图案被视作一张图片,内部颜色无法被CSS属性直接更改。

那是不是外链SVG就完全不能换色了?不是,只是思路要换,从“精准修改某个部分”变成“对整个图像做滤镜处理”。

3.2 用CSS filter把整张SVG“变”成目标颜色

CSS里有一套滤镜函数,比如invert(1)可以把黑白图反色,hue-rotate(90deg)可以把色相旋转,brightness(0)让图片变黑,sepia(1)把图片变成类似棕褐色的底子。对这些函数做组合,就能把任意颜色的SVG近似成另一种颜色。

一个非常经典的组合是把任意彩色SVG统一染成你想要的纯色:

.icon-background { background: url("../assets/icon.svg") no-repeat center / contain; filter: brightness(0) invert(1); /* 把图片变成白色 */ }

原理是:brightness(0)先把整个图片压成全黑,再通过invert(1)把黑色反转为白色。如果目标颜色是红色系、蓝色系,可以再做一层sepia(1)和hue-rotate:

.icon-red { filter: brightness(0) sepia(1) hue-rotate(-50deg) saturate(5); }

这里sepia(1)把纯黑图形变成了棕褐色底,然后hue-rotate旋转色相,再通过saturate提高饱和度,从而接近目标颜色。

这套方案有个特点:适合图标、logo这类大面积色块图形,不适合需要精细过渡颜色的照片类素材。因为滤镜是对整个元素的最终呈现做处理,没法精确到某个独立路径上。如果你只是想让一个单色图标从黑色变成红色,这套方法非常高效,还不用改动源文件。

注意:滤镜是作用在整个元素上的,如果你有边框、阴影等CSS效果,滤镜会把它们一起处理,容易导致意外结果。遇到这种情况,可以把滤镜放到一个专门包裹图标的容器上,或者单独用伪元素来做背景。

3.3 把SVG转成symbol雪碧图,变成内联可控的图

如果你想在保留外链便利性的同时又能换色,还有一个很成熟的方案:把多个SVG图标封装成一个“symbol sprite”,也就是符号雪碧图。

做法是在页面上先定义一个隐藏的<svg>,把各个图标以<symbol>的形式放进去:

<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <symbol id="icon-home" viewBox="0 0 24 24"> <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"></path> </symbol> <symbol id="icon-setting" viewBox="0 0 24 24"> <path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"></path> </symbol> </svg>

然后在页面里使用:

<svg class="icon icon-home"><use href="#icon-home"></use></svg> <svg class="icon icon-setting"><use href="#icon-setting"></use></svg>

由于<use>最终会把<symbol>里的内容复制到当前文档中,因此页面CSS可以完全控制它的颜色:

.icon { fill: #333; } .icon:hover { fill: #ff8800; }

这种方式的优势是:你仍然只需要维护一套SVG源文件,所有图标都在一个地方统一处理,颜色可以任意通过CSS控制。它比<img>方式灵活,也比把每个SVG都复制进HTML的方式更干净。

要注意一点:<use>的引用地址有跨文档限制,如果你页面上同时存在很多脚本对DOM做动态操作,需要留意symbol是否被意外移除。实际项目里推荐把雪碧图放在页面头部统一管理。

3.4 修改SVG源文件本身也算一种“换色”

有时候最直接的方案反而是去改源文件。如果你的SVG是作为静态资源被多个页面引用的,而且大家对换色的诉求并不复杂,那你完全可以把源文件里写死的颜色统一改成currentColor,然后配合外层CSS的color来控制。但这要求使用SVG的所有地方都通过内联或<use>方式引用,否则没有主机CSS能控制它。

如果你必须继续用<img>外链,但只是想让某个版本变成白色,你可以复制一份源文件出来,把里面的fill="#333"全局替换成fill="#ffffff",然后给不同版本放到不同目录。这种做法看起来“笨”,但在小项目里其实最稳定,因为它不依赖任何CSS或JS环境。

4. 做主题化和动态状态时,SVG换色的进阶玩法

4.1 悬浮、选中、禁用状态的图标颜色切换

图标在交互状态下的换色,常见的做法是在CSS里为不同状态写颜色规则。以内联SVG为例:

.icon { fill: #6c757d; transition: fill 0.2s ease; } .nav-item:hover .icon { fill: #007bff; } .nav-item.active .icon { fill: #28a745; } .nav-item.disabled .icon { fill: #dee2e6; pointer-events: none; }

这里利用了SVG内联后“路径可以继承父级选择器”的特点,配合CSS过渡,可以让图标在切换颜色时有一个平滑的视觉变化。这只对CSS属性接管的颜色有效,如果你的SVG写死了fill属性,过渡效果会突然“跳变”。我在实际项目里主导航栏的图标切换,就遇到过这种问题,最后统一清理了一遍SVG内联颜色,才恢复正常。

实操心得:transition只能对能被CSS数值插值的属性产生渐变效果。fill是支持过渡的颜色属性之一,但需要确保隐藏状态和显示状态的fill值都可以被CSS控制,而不是被内联属性覆盖。

4.2 用JavaScript读取和修改SVG颜色

动态场景下,你可能需要根据用户操作即时改变SVG的颜色,比如点击按钮后给图表元素上色,或者根据数据字段设置不同颜色。这时候用JavaScript操作会最简单。

如果你操作的是内联SVG,可以直接通过DOM属性或style对象修改:

const svgPaths = document.querySelectorAll('.icon-warning path'); svgPaths.forEach(path => { path.style.fill = '#ff5722'; });

如果你希望保留CSS变量式的写法,可以在根节点或任意父节点上动态设置style.setProperty('--icon-color', '#e91e63'),这样所有引用了这个变量的SVG子内容都会一起触发变色。这是一个非常轻盈的换主题方法。

document.documentElement.style.setProperty('--icon-color', '#e91e63');

这种方式比直接循环修改几十个SVG路径要优雅很多,因为页面上的主题色变动只需要一个入口:主题配置对象。

4.3 复杂动态场景:同一图标在不同数据状态下不同颜色

有一种很常见的业务场景:一个统计图表里使用同一个图标来表示不同风险级别。这时如果只有一条固定CSS,显然不够用。我的推荐是结合数据属性和CSS属性选择器来解耦。

<span class="status-icon">.status-icon[data-level="high"] svg { fill: #dc3545; } .status-icon[data-level="medium"] svg { fill: #ffc107; } .status-icon[data-level="low"] svg { fill: #28a745; }

这种方案的优点是:你不需要在每个SVG路径上写无数个style,数据状态和颜色表现的映射关系非常清楚。后端返回的数据里只要带上级别字段,前端直接把它映射到>const levelMap = { high: '#dc3545', medium: '#ffc107', low: '#28a745' }; function updateStatusIcon(el, level) { const color = levelMap[level] || '#6c757d'; el.style.setProperty('--icon-color', color); }

这里同时用到了前面提的CSS变量,优点是你只需要维护一份映射表,不用写大量选择器。你要根据项目实际情况选择方案,不要认为“别人推荐哪种就永远用哪种”。

4.4 动态加载SVG时的换色问题

如果你是通过 Ajax 拉取一段SVG字符串再插入页面的,这时要注意:SVG插入后,页面CSS是能控制的,因为它的最终位置在DOM里。但如果这个SVG字符串是从后端接口返回的,后端返回时很可能在标签里带了fill属性,导致CSS覆盖不了。稳妥做法是插入后统一把fill和stroke属性清空,或把需要的值重新设置一遍。

fetch('/api/icon') .then(res => res.text()) .then(svgText => { const container = document.getElementById('dynamic-icon'); container.innerHTML = svgText; // 清除或覆盖内联颜色 container.querySelectorAll('[fill]').forEach(el => el.removeAttribute('fill')); });

为什么我总强调先移除内联颜色?因为SVG的fill属性优先级低于CSS,但如果同时存在行内style="fill: red",那再高优先级的CSS也赢不了。动态插入的第三方或后端返回的SVG更需要注意这个,因为它们经常会把颜色写进style里。

5. 换色失败自查与调试建议

5.1 常见问题速查表

下面的问题清单是我在开发和排查过程中经常遇到的,整理成表给你。

现象可能原因快速排查方法
CSS里fill不生效SVG标签上有内联fill或style用开发者工具查看计算样式是否被覆盖
currentColor没反应路径或外层容器上有更高优先级的颜色设置确认路径的fill值是currentColor,外层容器确实设置了color
背景SVG变成黑色或白色但不对CSS filter组合函数顺序有误先只写一个brightness(0),再加invert(1)逐步测试
多个图标里只有一个颜色没变那个SVG内部有独立的fill值或额外的style检查图标源码,删掉内联颜色
<use>引用图标没有颜色引用的<symbol>内部没有颜色定义,默认黑色可能和预期不符给外层svg设置fill或color
动态插入SVG后颜色全部错插入的字符串里有style="fill:..."在插入后统一清掉style和fill,再重新设置
SVG在<img>里无法用CSS变色外部SVG是独立文档,页面CSS无法进入改用<use>或内联SVG方案

5.2 我所坚持的几个操作习惯

  • 保留原始SVG,永远做副本。设计工具导出的SVG我一般不会直接放进生产环境,而是先放到一个“原始素材”目录,然后把清理过颜色属性的版本放到“优化后”目录。这样一旦要换颜色规则,还能回原图去对比。

  • 统一颜色变量名。团队项目里很容易出现这个图标的变量叫--icon-color,另一个叫--svg-color的情况。时间一长,全局替换颜色时非常费劲。我会在项目刚开始就统一所有图标颜色变量命名,比如--icon-color、--icon-color-hover,并写进组件库规范。

  • 优先使用currentColor,其次用CSS变量。currentColor的好处是它和文字颜色天然同步,特别适合按钮、菜单、标签这类有文字的场景。CSS变量则适合更复杂的主题体系。绝大多数图标的换色需求,用这两套方法就足够了,反而那些临时加在路径上的fill才是长期维护的痛点。

  • 不要迷信“用滤镜染一切颜色”。CSS filter确实能在不修改SVG源文件的情况下改变呈现颜色,但它是“整体处理”,不能做局部换色,而且不同浏览器对滤镜组合的渲染结果可能有细微差异。单色图标用它很方便,彩色复杂图标还是老老实实用内联方式处理。

SVG换色这件事,从表面看只是填一个颜色值,背后却暴露了SVG在整个前端生态里的特殊身份:它既是图形,也是文档,还承担着和CSS、JS的联动。搞清楚它“可被CSS控制的边界”在哪里,很多问题都会迎刃而解。我现在的习惯是,拿到任何一份SVG,第一件事先看它是怎么被引入页面的,再决定用fill覆盖、currentColor同步、CSS变量还是滤镜方案。想清楚这一步,你就已经解决了换色方案的80%问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 4:00:54

HIL测试中总线仿真从能通到像真车的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/23 3:59:46

工控AI落地六大技术支点:从边缘确定性到产线闭环

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/23 3:58:00

ESP32+树莓派双芯个人AI Agent硬件框架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/23 3:57:43

显卡驱动深度清理与重装:DDU安全模式排错指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/23 3:57:40

KMS激活全解析:从原理到三步实操与常见问题排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/23 3:57:38

AI短剧分镜头模板:结构化提示词框架与批量生成实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华