用 CSS 让图片(图标)变成任意颜色:brightness / invert / mask 全讲清
起因:一个蓝底按钮上的蓝色图标"看不见",用一行 CSS 把它刷白。顺手把这类"给图片变色"的技巧整理成篇。
0. 先记住一条底层规则
filter 里这些函数只改颜色通道(RGB),不改 alpha 通道。
也就是说:图标的形状、透明背景都不会变,只会变颜色。这是所有"用 CSS 给图标换色"技巧成立的前提(前提是图片得是「形状 + 透明底」的 PNG/SVG,不能是一整张不透明的 JPG)。
另一个特性:filter 可以串联,从左到右依次生效。
1. 变白:brightness(0) invert(1)
最常用的一招。深色底 / 彩色底上想让图标变白:
css
.icon {
width: 16px;
height: 16px;
filter: brightness(0) invert(1);
}
原理是两步:
| 步骤 | 蓝色像素 #2583FF |
透明像素 |
|---|---|---|
| 原图 | #2583FF |
alpha = 0 |
① brightness(0) |
#000000(亮度 ×0 → 任何颜色都压成纯黑) |
仍透明 |
② invert(1) |
#FFFFFF(100% 反相 → 黑变白) |
仍透明 |
为什么不能只写 invert(1)? 因为直接反相蓝色得到的是橙黄,不是白:
#2583FF ──invert(1)──▶ #DA7C00 橙黄,不是我们要的
#2583FF ──brightness(0)──▶ #000000 ──invert(1)──▶ #FFFFFF 正确
brightness(0) 的作用是"归一":不管原图是蓝、绿还是花的,先统一压成黑,再翻白就没有意外。
2. 变黑 / 变深灰:brightness(0)
浅色底上,彩色图标往往显得"太跳",压成深色更协调:
css
/* 纯黑 */
.icon { filter: brightness(0); }
/* 深灰(更柔和,常用在浅灰底上) */
.icon { filter: brightness(0); opacity: 0.72; }
这是最省事的一招------不用抠图、不用换资源,一个 filter 就得到一个"单色深版"图标。
3. 变灰 / 禁用态:grayscale(1)
按钮 disabled、图标"不可用"的常见视觉:
css
.icon-disabled {
filter: grayscale(1);
opacity: 0.4;
}
配合 hover 可以做成"变亮"的交互:
css
.icon { filter: grayscale(1) opacity(.4); transition: filter .2s, opacity .2s; }
.icon:hover { filter: none; opacity: 1; }
4. 变任意精确色 :推荐 mask(而不是继续加 filter)
如果只想要白色/黑色,第 1、2 节够了。但要"精确 指定一个品牌色"(比如 #FF6B00),靠 filter 串联是很痛苦的:
css
/* 不推荐:参数靠试,还不精确 */
filter: brightness(0) invert(1) sepia(1) saturate(5) hue-rotate(350deg);
更优雅的做法是不用图片的颜色,只用它的形状 ------ mask:
css
.icon-recolor {
width: 16px;
height: 16px;
background-color: #ff6b00; /* 想要什么颜色就写什么 */
-webkit-mask: url("./icon.png") no-repeat center / contain; /* 用图片的 alpha 做遮罩 */
mask: url("./icon.png") no-repeat center / contain;
}
原理:mask 用图片的 alpha 通道 当"镂空模板",不透明的地方透出 background-color,透明的地方保持透明。
mask 方案的好处:
- 颜色精确可控 ,写什么色就是什么色(还能用
linear-gradient做渐变图标); - 可以配合
transition: background-color做平滑换色动画; - 想换色只改
background-color一行,不用再碰 filter 参数。
注意事项:
- 必须加
-webkit-mask前缀(移动端 WebView 尤其); - 原图必须是带透明背景的 PNG/SVG(用 alpha 当模板)。整张不透明的图会变成一整块色块;
- 如果图片边缘有抗锯齿的半透明像素,接缝处仍能平滑过渡(这正是用 alpha 而不是简单阈值的好处)。
5. 顺手的两个"图片效果"技巧
贴合形状的投影:drop-shadow(不是 box-shadow)
css
.icon {
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
}
区别很关键:
| 阴影范围 | |
|---|---|
box-shadow |
元素的矩形盒子(透明区域也会投影,方方正正一格) |
drop-shadow |
图片实际图形轮廓(只沿着图形边缘投,自然得多) |
处理透明 PNG 图标、不规则图形时,drop-shadow 明显更对。
提亮 / 压暗 / 模糊
css
filter: brightness(1.2); /* 提亮 20% */
filter: brightness(0.8); /* 压暗 20% */
filter: blur(2px); /* 模糊(做占位图/毛玻璃氛围) */
filter: contrast(1.2); /* 加对比 */
6. 速查表
| 需求 | 写法 | 备注 |
|---|---|---|
| 彩色 → 纯白 | brightness(0) invert(1) |
深底/彩底最常用 |
| 彩色 → 纯黑 | brightness(0) |
浅底 |
| 彩色 → 深灰 | brightness(0) opacity(.72) |
更柔和 |
| 彩色 → 任意精确色 | background-color + mask |
推荐,可做渐变/动画 |
| 禁用态 / 变灰 | grayscale(1) opacity(.4) |
配合 hover 恢复 |
| 贴形状投影 | drop-shadow(...) |
优于 box-shadow |
| 模糊 | blur(2px) |
占位、氛围 |
7. 几个容易踩的坑
- 图片必须"形状 + 透明底" 。
brightness(0) invert(1)把整张 JPG 变成一片白,mask也会得到一整块色块。 - 半透明边缘 :PNG 的抗锯齿像素是"半透明色",被 filter 着色后会变成"半透明白",在深底上可能看到一圈淡淡的光晕。追求极致清晰的话,改用 SVG 图标 (+
fill: currentColor),或直接用字体图标(van-icon/ iconfont)------它们本来就是"随字号/颜色变化"的矢量。 - 性能 :
filter/mask会触发图层合成。长列表里不要给每个 item 都挂带transition的 filter ,容易掉帧;必要时给容器加will-change或只在 hover 时加。 - 兼容性 :
filter→ Android WebView 4.4+ / iOS 9+;mask→ 需带-webkit-前缀。移动端项目可放心用,但要写全前缀。 - 留一行注释 :如果图标以后换成了白色资源,那行
filter: brightness(0) invert(1)就该删掉。写下"为什么",避免后人误删或对着它发愣。
8. 选型建议:什么时候用什么
只要白 / 黑 / 灰 → filter(一行搞定)
要精确品牌色、要渐变/动画 → mask + background-color
要"随文字颜色变化" → SVG + fill: currentColor(最优雅)
要是空心/实心多套图标 → 直接找设计出图,别硬用 CSS 拼
一句话:CSS 能解决的问题就别加图片资源;但需要多色或复杂图形时,也不要为了省资源把 CSS 写成一串魔法参数。