移动端border-left 和 width:1px,同样写1px为什么粗细不同?

0 阅读4分钟

一次UI走查,发现了一个之前未曾注意到的点,记录一下。

前言

(本来是想让AI帮我写篇文章,自己省点事,但真的味儿太重了,还是自己来吧.....)

上周在做一个首页列表展示信息的功能,就那种一个卡片里堆叠很多内容的样式。很自然的就用到了分割线来分割不同内容,这种需求很常见了。更为常见的是一些纵向排版中两个板块之间的分割线。对于前者一般都是伪类一把梭,对于后者一个border-bottom轻松搞定。

这本来没什么好说的,但开发过程中我就发现那个border-bottom的线随着热更新和tab切换有的地方有,有的地方没有。但是看样式这根线确实存在。同时UI也拿着显微镜放大500倍后的图来找我,同样的分割线怎么有的粗有的细?

image.png

在我检查过代码过后发现:之前我认为的border-bottom它使用<div class="line"></div>实现的,而这个分割线我用了before+width:1px

带着这个困惑,直接AI启动,发现了问题的真相。

本质上是浏览器对这两种样式的渲染处理方式不同。

一、老生常谈的 DPR

什么逻辑像素、物理像素、这像素、那像素、移动端1px的解决等前几年就烂大街的内容,常被面试官加入豪华八股文套餐。实际面试这个答的好不好并没什么卵用,绕来绕去的,这里不再赘述,自己去搜。下面贴一段ai的示例意思一下。

CSS 像素 vs 物理像素

┌─────────────────────────────────────────────────────┐
│                    设备屏幕                          │
│  ┌───┬───┬───┬───┬───┬───┬───┬───┬───┐             │
│  │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │ ■ │  物理像素     │
│  └───┴───┴───┴───┴───┴───┴───┴───┴───┘             │
│                                                      │
│  CSS 说:width: 1px                                 │
│                                                      │
│  iPhone 15 (DPR=3)                                 │
│  → 浏览器说:用 3×3 = 9 个物理像素显示这个 1px        │
│                                                      │
│  关键:这 9 个物理像素是排成 3 行 3 列                │
│  所以 1px 的物理宽度 = 3 个物理像素并排               │
└─────────────────────────────────────────────────────┘

设备像素比 (DPR)

设备DPR1 CSS px 占用物理像素
iPhone SE22×2 = 4
iPhone 14/1533×3 = 9
iPhone 15 Pro Max33×3 = 9

核心公式:物理像素宽度 = CSS像素宽度 × DPR

所以理论上,1px 在 iPhone 15 上应该占用 3 个物理像素的宽度

那为什么 width: 1px 会变成 2px(6 个物理像素)?


二、根本原因:两条不同的渲染管线

border 的专属优化通道

浏览器对 border 有专门的渲染管线:

┌─────────────────┐
│  border: 1px    │
└────────┬────────┘
         ▼
┌─────────────────┐
│  边框优化算法     │  ← 关键!浏览器在这里做亚像素计算
│  计算:1px ÷ DPR  │     1 ÷ 30.333 物理像素宽度
│  = 0.333px       │     但实际至少 1 物理像素,所以 = 1 物理像素
└────────┬────────┘
         ▼
┌─────────────────┐
│  智能四舍五入     │
│  1 物理像素      │  ← 真正的 1px!
└────────┬────────┘
         ▼
┌─────────────────┐
│  GPU 光栅化      │
│  输出到屏幕       │
└─────────────────┘

width 的普通布局流程

┌─────────────────┐
│  width: 1px     │
└────────┬────────┘
         ▼
┌─────────────────┐
│  盒模型计算      │  ← 普通布局,没有优化
│  1px × DPR = 3 物理像素 │
└────────┬────────┘
         ▼
┌─────────────────┐
│  强制整数对齐     │  ← 问题在这里!
│  3 物理像素不够?  │     某些渲染引擎认为 3 不够清晰
│  对齐到 6 物理像素 │     于是翻倍到 6(2px CSS)
└────────┬────────┘
         ▼
┌─────────────────┐
│  普通渲染        │
│  输出 2px 粗细    │
└─────────────────┘

简单说

  • border 走的是“VIP 通道”,浏览器会精打细算,尽量给你 1 物理像素
  • width 走的是“普通通道”,浏览器为了渲染简单,直接给你翻倍

三、backface-visibility 让 border 更稳

.divider {
  width: 0;
  border-left: 1px solid #999;
  -webkit-backface-visibility: hidden;  /* 这行是干嘛的? */
  backface-visibility: hidden;
}

这个属性可以强制浏览器开启 GPU 硬件加速,将元素提升到独立的合成层。渲染起来有以下三个优势:

  • 页面滚动时分隔线不模糊
  • 动画执行更流畅
  • iOS Safari 中避免白色闪烁
┌─────────────────────────────────────────────────┐
│              没有 backface-visibility            │
│                                                 │
│  页面滚动时:                                    │
│  ┌──┐                                          │
│  │░░│░░░░░░░░░░░░░░  ← 边框抖动、模糊           │
│  └──┘                                          │
│                                                 │
├─────────────────────────────────────────────────┤
│          有 backface-visibility: hidden          │
│                                                 │
│  页面滚动时:                                    │
│  ┌──┐                                          │
│  │██│████████████████  ← 边框稳如泰山,始终清晰   │
│  └──┘                                          │
└─────────────────────────────────────────────────┘

结语

无论是横向width:1px的分割线还是纵向height:1px的分割线,在移动端渲染上都会有这个区别,本质上是一个问题。

答案很简单:浏览器对 border 有特殊优化,对普通元素没有。

但这个问题又不简单:它揭示了浏览器渲染引擎的设计哲学 —— 为了性能,牺牲精度;为了常见场景,优化边缘场景。

下次别再用盒子模型的宽高了,1px都尽量用border吧。

如果这篇文章对你有所帮助,请动动发财的小手点个免费的赞~~~