opacity 的作用
opacity 设置元素的透明程度:1 是完全不透明(默认值),0 是不可见,介于两者之间的值会让背景透出来。下面的盒子放在条纹背景上,方便你看清效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Opacity</title>
<style>
body { font-family: system-ui, sans-serif; font-size: 13px; }
.bg {
display: flex;
gap: 12px;
padding: 16px;
background: repeating-linear-gradient(45deg, #e5e7eb 0 10px, #ffffff 10px 20px);
}
.box { width: 90px; height: 70px; background: #2563eb; color: white; display: grid; place-items: center; font-weight: 700; }
</style>
</head>
<body>
<div class="bg">
<div class="box" style="opacity: 1">1</div>
<div class="box" style="opacity: 0.75">0.75</div>
<div class="box" style="opacity: 0.5">0.5</div>
<div class="box" style="opacity: 0.25">0.25</div>
<div class="box" style="opacity: 0">0</div>
</div>
</body>
</html>
最后一个盒子其实还在:它在这一行中占据着自己 90px 的位置,只是什么都没画出来。注意,白色数字也和盒子一起变淡了。opacity 作用于元素及其内部的一切。
背景透明,文字不透明
因为 opacity 也会让子元素变淡,所以想要“在清晰的文字后面放一个半透明背景”时,它并不是合适的工具。应该改用带 alpha 通道的背景色:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Background alpha</title>
<style>
body { font-family: system-ui, sans-serif; }
.bg { padding: 16px; background: repeating-linear-gradient(45deg, #f59e0b 0 10px, #ffffff 10px 20px); }
.card { padding: 14px; margin-bottom: 10px; color: #111827; font-weight: 700; }
.with-opacity { background: #ffffff; opacity: 0.6; }
.with-alpha { background: rgb(255 255 255 / 0.6); }
</style>
</head>
<body>
<div class="bg">
<div class="card with-opacity" id="a">opacity: 0.6 (text fades too)</div>
<div class="card with-alpha" id="b">background alpha 0.6 (text stays solid)</div>
</div>
<p id="out"></p>
<script>
const s = (id) => getComputedStyle(document.getElementById(id));
document.getElementById('out').textContent =
'first: opacity ' + s('a').opacity + ' | second: opacity ' + s('b').opacity + ', background ' + s('b').backgroundColor;
</script>
</body>
</html>
两块白色面板透出条纹的程度相同。在第一块中,文字随着面板一起变淡了。在第二块中,只有背景色是透明的(rgb(255 255 255 / 0.6),等同于 rgba(255, 255, 255, 0.6)),所以文字保持不透明,清晰易读。带透明度的颜色在颜色页面中有详细介绍。
子元素无法摆脱父元素的 opacity:在子元素上设置 opacity: 1 不起作用,因为父元素被当作一个变淡的图层来绘制,子元素已经在其中了。
取值
| 值 | 效果 |
|---|
1 或 100% | 完全不透明(默认值) |
0.5 或 50% | 半透明 |
0 或 0% | 完全透明,但仍在布局中,也能被点击 |
1.5、-1 | 被限制为 1 和 0 |
opacity 在视觉上会“继承”,但作为属性并不继承:子元素计算出的 opacity 仍然是 1,只是它被绘制在一个变淡的父元素里。
opacity: 0、visibility: hidden 与 display: none 的区别
这三种写法都能让元素消失,但在其他方面各不相同:
| opacity: 0 | visibility: hidden | display: none |
|---|
| 占据空间 | 是 | 是 | 否 |
| 能接收点击和焦点 | 是 | 否 | 否 |
| 被屏幕阅读器读出 | 是 | 否 | 否 |
| 能做过渡动画 | 能,过渡平滑 | 在一端突然切换,没有淡出 | 不能 |
最容易出问题的是第二行。opacity: 0 的按钮仍然可以被点击,也可以用 Tab 键聚焦:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hidden but clickable</title>
<style>
body { font-family: system-ui, sans-serif; }
.row { display: flex; gap: 10px; }
.row button { width: 150px; height: 40px; font: inherit; }
</style>
</head>
<body>
<div class="row">
<button id="o" style="opacity: 0">opacity: 0</button>
<button id="v" style="visibility: hidden">visibility: hidden</button>
<button id="d" style="display: none">display: none</button>
<button id="shown">Visible</button>
</div>
<p id="out"></p>
<script>
const hit = (id) => {
const r = document.getElementById(id).getBoundingClientRect();
const top = document.elementFromPoint(r.left + 10, r.top + 10);
return id + ': ' + (top && top.id === id ? 'clickable' : 'not clickable');
};
document.getElementById('out').textContent = [hit('o'), hit('v')].join(' | ') +
' | the Visible button starts at x = ' + document.getElementById('shown').offsetLeft + 'px';
</script>
</body>
</html>
读数结果是询问浏览器:每个隐藏按钮内部的某个点上是哪个元素。opacity: 0 的按钮回答“clickable”(可点击);visibility: hidden 的按钮则不是。Visible 按钮排在两个空的 150px 位置之后,因为只有 display: none 的按钮把自己的空间让了出来。如果想先淡出再把元素移除,就对 opacity 做动画,并在结束时设置 visibility: hidden。
悬停时淡入淡出
opacity 是做动画开销最小的属性之一,所以它是实现淡入淡出的标准方式。下面是一个在图片上方出现的说明文字:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hover fade</title>
<style>
body { font-family: system-ui, sans-serif; }
.tile { position: relative; width: 240px; height: 150px; background: linear-gradient(135deg, #2563eb, #16a34a); }
.caption {
position: absolute;
inset: auto 0 0 0;
padding: 10px;
background: rgb(17 24 39 / 0.8);
color: white;
opacity: 0;
transition: opacity 0.25s ease-out;
}
.tile:hover .caption, .tile:focus-within .caption { opacity: 1; }
</style>
</head>
<body>
<a class="tile" href="#" style="display: block">
<span class="caption">Caption appears on hover and focus</span>
</a>
</body>
</html>
:focus-within 让使用键盘的用户也能看到说明文字。别忘了上面的表格:说明文字不可见时,仍会被屏幕阅读器读出,对说明文字来说这正是你想要的。
无障碍:变淡的文字会降低对比度
降低 opacity 就会降低对比度。白底上的灰色文字在 opacity: 0.5 时,对比度可能远低于正文所需的 4.5:1。对于次要文字,应该直接选一个对比度达标的颜色,而不是把深色文字调淡。对于禁用的控件,降低不透明度是一种常见的提示,但要同时加上 disabled 属性,这样键盘也无法聚焦到该控件,屏幕阅读器也会把它报告为已禁用。
副作用:层叠上下文
任何小于 1 的 opacity 都会创建一个新的层叠上下文,和 transform 一样。这时具有较高 z-index 的子元素只能在这个变淡的元素内部层叠,无法盖过它外面的元素。如果一个变淡面板里的下拉菜单滑到了另一个元素下面,原因就在这里(z-index 页面讲解了层叠的原理)。
常见错误
- 为了让卡片背景透明而把整张卡片调淡。 文字也会变淡。在背景上使用
rgb(... / 0.5)。
- 想让子元素重新变得不透明。 父元素的 opacity 会限制每一个子元素。
- 只用
opacity: 0 来隐藏元素。 元素仍然能接收点击和焦点。加上 visibility: hidden,或者使用 display: none(pointer-events: none 能阻止点击,但阻止不了键盘焦点)。
- 写成
opacity: 50。 opacity 的范围是 0 到 1;50 会被限制为 1。应写成 0.5 或 50%。
- 用 opacity 造成文字对比度过低。 要检查最终效果的对比度,而不是原始颜色的对比度。