Day 07 _ 那条 1px 的线,为什么在手机上忽粗忽细

本文是《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: 1pxscaleY(.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:从静态美化到动画实战》每天更新,订阅专栏不迷路。

相关推荐
小江的记录本25 分钟前
【CSS】CSS 动画:transition、animation、transform、CSS3 新特性(附《思维导图》)
前端·css·安全·spring·前端框架·css3·动画
circuitsosk30 分钟前
任务规划器的三种范式对比:ReAct、Plan-and-Execute 与 Tree-of-Thought 在真实业务中的取舍
前端·javascript·python·react.js·llm·ai agent
hzxpaipai2 小时前
企业官网技术架构拆解:前端、后台、数据库、服务器如何协同
前端·数据库·架构
深念Y4 小时前
Lenovo XiaoXinAir 14 — 性能模式切换
前端·网络
计算机魔术师4 小时前
Meta突然杀进第一梯队:一个新模型,把AI推理成本打下来8倍
前端
IT_陈寒4 小时前
Redis缓存雪崩把我坑惨了,这次长记性了
前端·人工智能·后端
风骏时光牛马5 小时前
提示词工程:大模型效能挖掘与指令设计实战
前端
仓三5 小时前
Chrome DevTools MCP 上手:让 Coding Agent 自己调试前端页面
前端·chrome devtools·mcp