本文是《30天精通CSS》专栏第 07 篇,订阅专栏不迷路。
本文目录
-
先看这两条线,一条粗一条细
-
三个嫌疑,逐个排除
-
元凶:物理像素和 CSS 像素不是一回事
-
修好它:scaleY(.5) 画真细线
-
举一反三:什么时候 1px 就够用
-
留个作业
-
明天讲什么

先看这两条线,一条粗一条细
不解释,先看现场。同一个页面,两条分隔线,都写着 1px。在高分屏手机上看,一条是纤细的发丝线,另一条明显粗一圈,发灰发闷。
Plaintext
<!-- 《30天精通CSS》Day 07 · 平头哥 · 技术改变世界,勤奋改变人生 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>两条 1px,一粗一细</title>
<style>
body { max-width: 640px; margin: 40px auto; padding: 0 20px;
font-family: "Microsoft YaHei", sans-serif; background: #fff; color: #24292f; }
.bar { background: #fff3e0; border: 1px solid #e3e0d8; border-radius: 6px;
padding: 8px 14px; font-size: 14px; color: #8a3a28; margin-bottom: 24px; }
.thick { border-bottom: 1px solid #c9c4b8; padding-bottom: 14px; margin-bottom: 26px; }
.thin { position: relative; padding-bottom: 14px; }
.thin::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0;
height: 1px; background: #c9c4b8; transform: scaleY(.5); }
.label { font-size: 13px; color: #8a8a86; margin-top: 6px; }
</style>
</head>
<body>
<div class="bar">两条线都声明 1px,高分屏上一条粗一条细</div>
<div class="thick">
列表项 A
<div class="label">↑ border-bottom: 1px ------ 在 2x/3x 屏上被取整成 2~3 个物理像素,发粗</div>
</div>
<div class="thin">
列表项 B
<div class="label">↑ scaleY(.5) ------ 真正占半个 CSS 像素,渲染成 1 个物理像素,发丝线</div>
</div>
</body>
</html>

第一条粗,第二条细。作者很困惑:"我两个都写的 1px。"
三个嫌疑,逐个排除
嫌疑一:颜色深浅造成的错觉。 颜色确实影响观感,深线看起来粗。但这里两条颜色一样。排除。
嫌疑二:浏览器渲染 bug。 不至于,这是标准行为。排除。
嫌疑三:像素取整。 CSS 的 1px 是逻辑像素,在 2x 屏上等于 2 个物理像素,3x 屏等于 3 个。当浏览器做亚像素取整时,有的设备把 1px 渲染成 2 个物理像素(偏粗),有的恰好 1 个(偏细)。成立,是主因。
元凶:物理像素和 CSS 像素不是一回事
CSS 里的 1px 不是屏幕上的一个点,而是一个逻辑单位。设备像素比(DPR)决定它对应几个物理点:iPhone 常见 2x 或 3x。
所以 border: 1px 在 3x 屏上,浏览器要拿 3 个物理像素去填,这条线天然就比 1x 屏粗三倍。你没法让 border 只占 1 个物理像素,因为 border 的最小单位是 1 个 CSS 像素。

修好它:scaleY(.5) 画真细线
思路:既然 border 最小是 1 个 CSS 像素,那我画一条 1px 的线,再用 transform: scaleY(.5) 把它纵向压成一半。transform 发生在渲染后期,可以突破 1 像素的限制,在高分屏上呈现真正的发丝线。
标准写法是用伪元素,不占文档流:
Plaintext
<!-- 《30天精通CSS》Day 07 · 平头哥 · 技术改变世界,勤奋改变人生 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>发丝分隔线</title>
<style>
body { max-width: 640px; margin: 40px auto; padding: 0 20px;
font-family: "Microsoft YaHei", sans-serif; background: #fff; color: #24292f; }
.item { position: relative; padding: 14px 0; }
/* 发丝线:伪元素 + scaleY(.5) */
.item::after {
content: "";
position: absolute;
left: 0; right: 0; bottom: 0;
height: 1px;
background: #e3e0d8;
transform: scaleY(.5);
}
</style>
</head>
<body>
<div class="item">列表项一</div>
<div class="item">列表项二</div>
<div class="item">列表项三</div>
</body>
</html>


几个细节。伪元素要配 position: absolute,父元素配 position: relative,线才贴在每项底部。height: 1px 再 scaleY(.5),在 2x 屏上就是 1 个物理像素。
更严谨的版本会按 DPR 分档:2x 屏 scaleY(.5),3x 屏 scaleY(.3333),用媒体查询 (-webkit-min-device-pixel-ratio: 3) 切换。但日常项目里 .5 已经足够细,不必过度工程。
举一反三:什么时候 1px 就够用
不是所有线都要发丝线。外边框、卡片描边 用 1px border 没问题,因为它们本来就要"看得见"。列表分隔线、表格行线这种"存在感要低"的线,才值得上发丝线。
判断标准一句:这条线的目的是"分隔"还是"描边"?分隔要弱,用发丝;描边要实,用 border。
另外提醒:别用 border-bottom: .5px。小数 border 兼容性差,很多设备直接取整成 1px 或 0,不可靠。transform 方案才是稳的。
留个作业
把今天那个发丝线案例复制下来,做两个对比。第一,把 scaleY(.5) 删掉,刷新,看线变粗多少;第二,把 height: 1px 改成 height: 2px 但保留 scaleY(.5),确认它和原来 1px 的效果一致(2×0.5=1)。做完你应该能说出 scaleY 到底在哪一步起作用。
明天讲什么
Day 08 是阶段一收尾。我会把整张名片页改成"可整体缩放"的:换一套单位,让字号、间距跟着一个根字号走,以后调大小只改一处。你会彻底搞清 px、em、rem、%、vw 各自该用在哪。
参考资料
投票:你的项目里分隔线怎么画的?选项:border 1px / 发丝线 / 没注意过
我是平头哥,技术改变世界,勤奋改变人生。
《30天精通CSS:从静态美化到动画实战》每天更新,订阅专栏不迷路。