让元素变模糊
filter: blur() 接收一个长度,即模糊半径。值越大,元素越模糊:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>CSS blur</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 13px; }
.row { display: flex; gap: 16px; flex-wrap: wrap; }
img { display: block; width: 160px; height: 100px; }
.b2 { filter: blur(2px); }
.b6 { filter: blur(6px); }
</style>
</head>
<body>
<div class="row">
<figure><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 100'%3E%3Crect width='160' height='100' fill='%2393c5fd'/%3E%3Ccircle cx='120' cy='30' r='16' fill='%23f59e0b'/%3E%3Cpath d='M0 100 L0 70 Q40 40 80 70 T160 60 L160 100Z' fill='%2316a34a'/%3E%3C/svg%3E" alt="Hills and a sun"><figcaption>no filter</figcaption></figure>
<figure><img class="b2" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 100'%3E%3Crect width='160' height='100' fill='%2393c5fd'/%3E%3Ccircle cx='120' cy='30' r='16' fill='%23f59e0b'/%3E%3Cpath d='M0 100 L0 70 Q40 40 80 70 T160 60 L160 100Z' fill='%2316a34a'/%3E%3C/svg%3E" alt="Hills and a sun, slightly blurred"><figcaption>blur(2px)</figcaption></figure>
<figure><img class="b6" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 100'%3E%3Crect width='160' height='100' fill='%2393c5fd'/%3E%3Ccircle cx='120' cy='30' r='16' fill='%23f59e0b'/%3E%3Cpath d='M0 100 L0 70 Q40 40 80 70 T160 60 L160 100Z' fill='%2316a34a'/%3E%3C/svg%3E" alt="Hills and a sun, very blurred"><figcaption>blur(6px)</figcaption></figure>
</div>
</body>
</html>
filter 可以用在任何元素上,不只是图片:模糊一个 div,它的文字、边框和子元素也会一起变模糊。注意模糊图片的边缘会变得柔和:模糊会把画面向外扩散,并混入周围的透明区域。解决方法在下面的“模糊边缘的问题”中。
让背景变模糊:backdrop-filter
要让元素背后的内容变模糊(比如盖在照片或页面上的毛玻璃面板),使用 backdrop-filter。元素自身的内容保持清晰。它需要一个半透明的背景才能显示出模糊效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>backdrop-filter</title>
<style>
body { font-family: system-ui, sans-serif; margin: 0; }
.scene {
position: relative;
height: 200px;
background:
radial-gradient(circle at 25% 40%, #f59e0b 0 40px, transparent 41px),
radial-gradient(circle at 70% 60%, #16a34a 0 55px, transparent 56px),
repeating-linear-gradient(90deg, #2563eb 0 16px, #93c5fd 16px 32px);
}
.glass {
position: absolute;
inset: 40px 60px;
display: grid;
place-items: center;
background: rgb(255 255 255 / 0.35);
-webkit-backdrop-filter: blur(12px); /* older Safari */
backdrop-filter: blur(12px);
border: 1px solid rgb(255 255 255 / 0.6);
border-radius: 12px;
color: #111827;
font-weight: 700;
}
</style>
</head>
<body>
<div class="scene"><div class="glass">Sharp text on frosted glass</div></div>
</body>
</html>
面板周围的条纹和圆形是清晰的,面板背后的则变模糊了,而面板里的文字不受影响。要保证文字清晰易读:在内容复杂的图片上,背景色带一点偏浅或偏深的色调会有帮助。
所有滤镜函数,实时演示
拖动任意滑块。图片下面那一行就是准确的 filter 值,可以直接复制:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Filter playground</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 13px; }
.controls { display: grid; grid-template-columns: repeat(2, max-content); gap: 4px 18px; }
.controls input { vertical-align: middle; }
#img { display: block; width: 220px; height: 138px; margin: 12px 0 8px; }
code { display: block; background: #f3f4f6; padding: 6px; }
</style>
</head>
<body>
<div class="controls" id="controls"></div>
<img id="img" alt="Hills and a sun" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 160 100'%3E%3Crect width='160' height='100' fill='%2393c5fd'/%3E%3Ccircle cx='120' cy='30' r='16' fill='%23f59e0b'/%3E%3Cpath d='M0 100 L0 70 Q40 40 80 70 T160 60 L160 100Z' fill='%2316a34a'/%3E%3C/svg%3E">
<code id="out"></code>
<script>
// name, min, max, step, default (the value that changes nothing), unit
const fns = [
['blur', 0, 10, 0.5, 0, 'px'], ['brightness', 0, 2, 0.05, 1, ''],
['contrast', 0, 2, 0.05, 1, ''], ['grayscale', 0, 1, 0.05, 0, ''],
['sepia', 0, 1, 0.05, 0, ''], ['saturate', 0, 3, 0.1, 1, ''],
['hue-rotate', 0, 360, 5, 0, 'deg'], ['invert', 0, 1, 0.05, 0, '']
];
const controls = document.getElementById('controls');
fns.forEach(([name, min, max, step, def]) => {
controls.insertAdjacentHTML('beforeend', '<label>' + name + ' <input type="range" data-name="' + name + '" min="' + min + '" max="' + max + '" step="' + step + '" value="' + def + '"></label>');
});
const update = () => {
const parts = fns.filter(([name, , , , def]) => Number(controls.querySelector('[data-name="' + name + '"]').value) !== def)
.map(([name, , , , , unit]) => name + '(' + controls.querySelector('[data-name="' + name + '"]').value + unit + ')');
const value = parts.join(' ') || 'none';
document.getElementById('img').style.filter = value;
document.getElementById('out').textContent = 'filter: ' + value + ';';
};
controls.addEventListener('input', update);
update();
</script>
</body>
</html>
只有被你调离中性值的函数才会写出来,这样值会保持简短。brightness(1) 或 blur(0px) 什么都不会改变,所以没有必要列出来。
滤镜函数
| 函数 | 中性值 | 作用 | 示例 |
|---|
blur(r) | 0 | 半径为 r 的高斯模糊 | blur(4px) |
brightness(n) | 1 | 小于 1 变暗,大于 1 变亮 | brightness(0.7) |
contrast(n) | 1 | 小于 1 变平淡,大于 1 更鲜明 | contrast(1.3) |
grayscale(n) | 0 | 1(或 100%)为完全灰度 | grayscale(100%) |
sepia(n) | 0 | 温暖的棕褐色调 | sepia(0.6) |
saturate(n) | 1 | 0 为灰色,大于 1 更鲜艳 | saturate(1.5) |
hue-rotate(a) | 0deg | 让每种色相沿色轮旋转 | hue-rotate(90deg) |
invert(n) | 0 | 1 为完全的颜色反相 | invert(1) |
opacity(n) | 1 | 类似 opacity 属性 | opacity(0.5) |
drop-shadow(x y blur color) | | 沿形状投下的阴影 | drop-shadow(2px 4px 6px rgb(0 0 0 / 0.3)) |
数字也可以写成百分比(grayscale(100%) 等于 grayscale(1))。多个函数可以写在同一个值里,用空格分隔,并按顺序应用:filter: grayscale(1) sepia(1) 得到棕色调,而 sepia(1) grayscale(1) 最终只是普通的灰色。
drop-shadow 沿着形状
box-shadow 永远是矩形(最多带圆角)。drop-shadow() 根据可见的像素绘制,所以透明图标的阴影会带着图标的轮廓:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>drop-shadow</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 13px; }
.row { display: flex; gap: 40px; padding: 10px 20px; }
img { width: 90px; height: 90px; display: block; }
.box-shadow { box-shadow: 6px 6px 8px rgb(0 0 0 / 0.4); }
.drop-shadow { filter: drop-shadow(6px 6px 4px rgb(0 0 0 / 0.4)); }
</style>
</head>
<body>
<div class="row">
<figure><img class="box-shadow" alt="Star" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23f59e0b' d='M12 2l3 7h7l-5.5 4.5 2 7.5-6.5-4.5-6.5 4.5 2-7.5L2 9h7z'/%3E%3C/svg%3E"><figcaption>box-shadow</figcaption></figure>
<figure><img class="drop-shadow" alt="Star" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23f59e0b' d='M12 2l3 7h7l-5.5 4.5 2 7.5-6.5-4.5-6.5 4.5 2-7.5L2 9h7z'/%3E%3C/svg%3E"><figcaption>drop-shadow()</figcaption></figure>
</div>
</body>
</html>
第一颗星从它不可见的边界框投下了一个方形阴影。第二颗星投下的是星形阴影。和 box-shadow 不同,drop-shadow() 没有扩展值,也没有 inset;这些内容见 box-shadow 页面。
模糊边缘的问题
模糊后的图片在边缘会逐渐淡出,因为模糊会把周围的透明像素拉进来。要保留清晰的边缘(例如做模糊的首屏背景时),把图片放进带 overflow: hidden 的容器中,并把图片稍微放大,这样柔和的边缘就被裁掉了:
.hero { overflow: hidden; }
.hero img { filter: blur(8px); transform: scale(1.1); }
用 filter 做悬停效果
filter 可以做过渡动画,所以一面从灰色变彩色的 logo 墙,或者一张悬停前保持暗淡的卡片,只需要两条规则:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Filter on hover</title>
<style>
.logo {
width: 120px;
height: 70px;
display: inline-block;
background: linear-gradient(135deg, #2563eb, #16a34a);
border-radius: 8px;
filter: grayscale(1) opacity(0.6);
transition: filter 0.3s ease;
}
.logo:hover, .logo:focus-visible { filter: none; }
</style>
</head>
<body>
<a class="logo" href="#" aria-label="Partner one"></a>
<a class="logo" href="#" aria-label="Partner two"></a>
</body>
</html>
把鼠标悬停在某个方块上(或用 Tab 键聚焦它),它的颜色就会渐渐出现。
值得了解
- 性能。 在大面积上使用大的模糊半径开销最大,尤其是用在会移动或滚动的元素上的
backdrop-filter。请在手机上测试。
- 可读性。 永远不要模糊需要阅读的文字,并检查毛玻璃面板上文字的对比度。
- 层叠。 任何不为
none 的 filter 都会创建新的层叠上下文,并且和 transform 一样,会成为 position: fixed 后代元素的包含块。
常见错误
filter: blur(5)。 半径需要单位:blur(5px)。
- 用
filter: blur() 做毛玻璃面板。 它会让面板自己的文字也变模糊。请用 backdrop-filter 加透明背景。
- backdrop-filter 什么效果都没有。 元素的背景必须至少部分透明,而且它背后必须有内容。
- 写了两条
filter 声明。 第二条会替换第一条。把所有函数放在同一个值里。
- 透明图标上出现方形阴影。 用
filter: drop-shadow() 代替 box-shadow。