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

相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95272 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大2 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师2 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学2 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端