怎么画一条 0.5px 的边(更新)
Category(分类): Browser, CSS Status: 已更新
在设计稿中,分隔线有时希望比一个 CSS 像素更细。原文比较了直接设置 0.5px、transform: scale()、渐变、box-shadow、SVG 和 viewport 缩放等方案。本文保留这些思路,但不再把某个浏览器在某个年份的表现当成通用规则。
一、CSS 像素、设备像素和 DPR
px 在 CSS 中首先是一个逻辑长度,不是永远等于屏幕上的一个物理像素。window.devicePixelRatio 表示设备物理像素与 CSS 像素的比例:
console.log(window.devicePixelRatio)
例如:
- DPR 为 1 时,1 CSS px 约对应 1 个设备像素;
- DPR 为 2 时,1 CSS px 约对应 2×2 的设备像素区域;
- DPR 为 3 时,1 CSS px 约对应更高密度的设备像素区域。
浏览器缩放、系统显示缩放、屏幕切换和 WebView 配置都可能改变 DPR。不要把“Mac 默认 DPR=2”“iPhone 某型号宽度固定为 750 CSS px”当成当前所有设备的事实。

CSS 的长度值可以是浮点数:
.example {
width: 12.5px;
transform: translateX(0.5px);
}
但最终绘制要经过布局、栅格化和抗锯齿。对于 border-width 等属性,浏览器还可能根据设备像素对计算值进行取整或栅格化,因此不同 DPR、缩放比例、浏览器和平台的视觉结果可能不同。
二、直接使用 border: 0.5px
最简单的方案:
.hairline {
border-top: 0.5px solid #d9d9d9;
}
现代移动浏览器和高 DPR 屏幕通常可以呈现比 1 CSS px 更细的线,但不能保证所有浏览器、DPR=1 显示器、打印输出和嵌入式 WebView 的效果完全一致。
可以给普通屏幕提供 1px 回退:
.hairline {
border-top: 1px solid #d9d9d9;
}
@media (min-resolution: 2dppx) {
.hairline {
border-top-width: 0.5px;
}
}
这个 media query 是能力提示,不是“DPR≥2 就一定清晰”。仍要在目标设备上检查颜色、对齐和可见性。
直接设置小数 px 的注意点
- 不是所有属性都以相同方式处理小数长度;
- 高 DPR 不意味着线一定比 1 CSS px 更清晰;
- 细线颜色太浅时可能不满足对比度和可访问性;
- 浏览器缩放后 DPR 可能变化;
- 设计系统应允许 1px 回退,而不是依赖某个历史浏览器的特殊四舍五入行为。
三、使用伪元素 + transform: scaleY(.5)
这个方案保留一个 1px 的布局盒子,再缩放它的视觉内容:
.hairline-transform {
position: relative;
}
.hairline-transform::after {
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 1px;
background: #d9d9d9;
content: '';
transform: scaleY(0.5);
transform-origin: center bottom;
}

优点:
- 不依赖
border-width对小数的具体处理; - 不改变主要元素的布局尺寸;
- 可以通过伪元素控制位置、圆角和颜色。
限制:
- 缩放后可能落在半个设备像素上,出现抗锯齿或发虚;
- 变换元素可能影响层化和合成成本,但不能保证一定独立成层;
- 需要处理
position、层叠、点击区域和伪元素覆盖; - 对垂直线使用
scaleX(.5),对四边分别处理。
如果线条需要贴合设备像素,可结合实际 DPR 调整位置,但不要写死某个 iOS/Android 型号的偏移。最终仍然应该在真实目标设备上验证。
四、线性渐变
可以用渐变在一个 CSS 像素区域内混合颜色:
.hairline-gradient {
height: 1px;
background: linear-gradient(
to bottom,
transparent 0,
#d9d9d9 50%,
transparent 100%
);
}

渐变的本质是颜色插值,不是严格意义上的“只绘制半个物理像素”。它可能产生柔和、抗锯齿的效果,适合视觉分隔线,但不适合要求一条绝对锐利的几何线的场景。
原文使用从白色到黑色的渐变来“凑出半像素”,这种写法会让线条变成颜色过渡,不建议直接复制:
/* 不推荐:线条颜色和背景耦合,容易得到渐变而非 hairline */
background: linear-gradient(#fff, #000);
五、box-shadow
历史上有人用小数偏移的阴影模拟细线:
.hairline-shadow {
height: 1px;
background: transparent;
box-shadow: 0 0.5px 0 #d9d9d9;
}

优点是写法简单,阴影不会改变文档流布局;缺点是:
- 阴影同样经过栅格化和抗锯齿;
- 不同浏览器和输出媒介的最小可见宽度不同;
- 阴影可能被
overflow: hidden、层叠和裁剪影响; - 过多阴影会增加绘制成本。
把它作为普通 UI 分隔线方案时,伪元素通常更容易控制。
六、SVG 线条
SVG 可以描述矢量几何图形:
<svg
class="hairline-svg"
viewBox="0 0 100 1"
preserveAspectRatio="none"
aria-hidden="true"
>
<line x1="0" y1="0.5" x2="100" y2="0.5" stroke="currentColor" />
</svg>
.hairline-svg {
display: block;
width: 100%;
height: 1px;
color: #d9d9d9;
}

SVG 的 stroke-width 默认是 SVG 用户坐标中的值,最终仍然需要栅格化,并不会神奇地绕过设备像素限制。它适合:
- 需要画任意几何图形、圆角、路径和装饰;
- 希望把线条作为图片或图标复用;
- 需要通过
currentColor主题化。
使用 data URL 时要处理 URL 编码、CSP、缓存和可读性:
.hairline-svg-bg {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 1' preserveAspectRatio='none'%3E%3Cline x1='0' y1='.5' x2='100' y2='.5' stroke='%23d9d9d9'/%3E%3C/svg%3E");
}
直接把 #000 放进未编码的 data URL 可能被 URL 解析器截断或解释,生产代码更推荐独立 SVG 文件或经过可靠编码的资源。
七、不要用 viewport 缩放制造 0.5px
原文提出把移动端 viewport 设置成 scale=0.5,让 1 CSS px 对应一个设备像素。这个方法会改变整个页面的布局宽度和缩放比例,可能导致:
- 文本、按钮和点击区域变得不符合设计预期;
- 用户缩放和可访问性受到影响;
- 不同设备、WebView 和浏览器行为不一致;
- 需要把所有 UI 尺寸按倍数重新设计,维护成本很高。
正常移动页面应使用正确的 viewport:
<meta
name="viewport"
content="width=device-width, initial-scale=1"
>
原文中的 initial-sacle 是拼写错误,应为 initial-scale。不要通过禁止用户缩放来“保证像素”,例如不建议添加 user-scalable=no;可访问性优先于一条视觉细线。
rem 也不能解决物理像素对齐问题:它最终会参与 CSS 计算,仍然要经过 CSS 像素到设备像素的转换。
八、更加实用的方案选择
1. 普通分隔线
.divider {
border-top: 1px solid color-mix(in srgb, currentColor 12%, transparent);
}
如果目标浏览器不支持 color-mix(),使用预先计算好的颜色。许多场景 1px 加合适的颜色已经足够清晰,不需要追求 0.5px。
2. 移动端高 DPR hairline
.divider {
border-top: 1px solid #e5e5e5;
}
@media (min-resolution: 2dppx) {
.divider {
border-top-width: 0.5px;
}
}
3. 不影响布局的装饰线
.card {
position: relative;
}
.card::after {
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 1px;
background: #e5e5e5;
content: '';
transform: scaleY(0.5);
transform-origin: center bottom;
pointer-events: none;
}
4. 需要图形能力
使用 SVG 或 CSS border/gradient;如果是图标、曲线、圆角和复杂路径,不要为了“0.5px”强行用 box-shadow。
九、如何验证效果
可以用下面的方式观察目标环境:
console.table({
devicePixelRatio: window.devicePixelRatio,
width: window.innerWidth,
visualScale: window.visualViewport?.scale
})
验证时至少覆盖:
- DPR=1 的普通显示器;
- DPR=2/3 的移动设备;
- 浏览器缩放 80%、100%、125%、200%;
- Chromium、Safari、Firefox 和目标 WebView;
- 浅色/深色背景、不同线条颜色;
- 页面缩放、打印和无障碍字体设置;
- 线条是否影响布局、点击和滚动。
devicePixelRatio 变化时可以重新计算样式或使用 resolution 媒体查询:
const watchDpr = () => {
console.log('当前 DPR:', window.devicePixelRatio)
const media = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`)
media.addEventListener('change', watchDpr, { once: true })
}
watchDpr()
十、原文结论的现代修正
- “一个像素就是一个固定大小的色块”不准确,CSS px 和设备像素不是一回事;
- 现代浏览器可以接受浮点 CSS 长度,但最终效果受属性、DPR、缩放和栅格化影响;
- 不能把 Chrome、Firefox、Safari 某个旧版本的 0.5px 结果当作当前通用规则;
transform: scale(.5)可能产生抗锯齿,不能保证所有设备都锐利;- 渐变和阴影是视觉模拟,不是严格物理半像素;
- SVG 是矢量描述,但最终仍然要栅格化;
initial-sacle应改为initial-scale,不建议通过 viewportscale=0.5改变整站坐标系;- 是否使用 0.5px 要服从设计、可访问性和目标设备验证,1px 回退仍然必要。
总结
- CSS px 是逻辑单位,DPR 表示 CSS 像素和设备像素的比例;
border: 0.5px在现代高 DPR 环境通常可用,但不是所有环境都一致;- 伪元素加
transform: scaleY(.5)适合不改变布局的装饰线,但可能抗锯齿; - 渐变、box-shadow 和 SVG 各有适用场景,都不能绕过最终栅格化;
- 不要使用 viewport
scale=0.5作为全局 hairline 方案; - 先提供 1px 回退,再按 DPR、浏览器和设计目标增强;
- 最终必须在真实目标设备和缩放比例下验证清晰度、对比度与可访问性。