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

相关推荐
Java后端的Ai之路8 小时前
06-React-Props
前端·react.js·前端框架
Flynt8 小时前
开源"间谍卫星"说数据全是真的,我把它的免key数据源挨个试了一遍
前端·开源·数据可视化
Csvn9 小时前
渲染管线
前端
程序员大阳9 小时前
副队长CSS教程(33)–圆角与透明
前端·css·透明·圆角
志尊宝9 小时前
Vue3 零基础每日笔记(082):文件上传实战——el-upload 校验、自定义上传、头像预览
前端·javascript·vue.js·笔记·#javascript·#vue.js
鬼手点金10 小时前
opencode-rules 插件
java·开发语言·前端·人工智能·chrome·git·github
柳杉10 小时前
借助 Codex 和可视化工作流,从调研到完整实现,3D 大屏竟然可以这么简单
前端·ai编程·数据可视化
IMPYLH10 小时前
HTML 的 <wbr> 元素
前端·html
Dovis(誓平步青云)10 小时前
常用命令藏得太深?怎么做一个快捷命令坞
运维·前端·自动化·音视频·prometheus
IT_陈寒11 小时前
Vite的这个问题坑了我三天,原来如此解决!
前端·人工智能·后端