做前端时间长了,几乎人人都被“1px”折磨过。你在 PC 上调好的页面,拿 iPhone 一看,字体小得可怜,边框糊成一团,元素怎么都对不齐。折腾半天,最后发现根子不在代码逻辑,而在你对像素的认知。今天这篇就聊聊物理像素、CSS 像素这几个基础概念,以及怎么用 rem、vw、clamp() 这些现代 CSS 单位,把“在不同屏幕上显示正常”这件事做好。
这篇东西适合谁看?刚入门 Responsive 布局的朋友,还有已经在做移动端 H5 但对一些奇怪 bug 知其然不知其所以然的开发者。我会把概念、方案对比、实操步骤和常见坑都串起来讲,你看完可以直接回到项目里改造。
1. 先搞懂底层:物理像素、CSS 像素和 DPR 到底指什么
很多同学一上来就背结论:移动端用 rem,PC 用 px。但一旦遇到问题就傻眼,因为不知道背后的换算逻辑是什么。所以第一步,把三个基本概念吃透,后面所有方案都是这三个概念的组合拳。
1.1 物理像素:屏幕与生俱来的“分辨率网格”
物理像素是屏幕硬件上真实存在的发光单元,它出厂就定了,没法改。比如 iPhone 15 Pro Max 的屏幕分辨率是 2796 x 1290,意思是这块屏幕上横向排列了 2796 颗、纵向排列了 1290 颗物理像素点,每颗像素点独立控制亮度和颜色。所有图像、文字、边框,最终都要靠这些物理点“点亮”出来。
你可以把屏幕想象成一面贴满马赛克瓷砖的墙。物理像素就是每一小块瓷砖,瓷砖的总数决定了墙面的“物理分辨率”。墙面本身多大多高是固定的,瓷砖数量也是固定的。问题是:我们设计师和前端写代码时,根本没法直接按“瓷砖”来思考,因为不同墙面的瓷砖大小、密集程度完全不同。
所以就有了逻辑层。就像你在图纸上画房间布局时,标注的是“米”而不是“用了多少块砖”,浏览器里布局时用的单位,也不是物理像素,而是 CSS 像素。
1.2 CSS 像素:浏览器给你的“逻辑尺子”
CSS 像素是一种抽象单位,它代表的是浏览器绘制页面时使用的逻辑坐标。你写width: 100px,浏览器不会直接点亮 100 个物理像素,而是先把这个值换算成当前设备上需要的物理像素数量,再去渲染。
这种抽象有什么好处?最直接的好处是:不同尺寸、不同密度的屏幕上,一个 CSS 像素对应的物理大小大致接近,布局才不会完全崩掉。举个例子,你在 24 寸 1080p 显示器上看一个 100px 的按钮,和在一个 6 寸手机上通过 CSS 像素放大到同样视口比例后看,按钮不会小到点不到,也不会大到占掉整个屏幕。
但 CSS 像素和物理像素的对应关系不是固定的,它由设备的 DPR(Device Pixel Ratio)决定。这也是为什么同样的 CSS 代码,在不同设备上显示的“细腻程度”完全不一样的核心原因。
1.3 DPR:连接“逻辑”与“物理”的换算系数
DPR 的全称是 Device Pixel Ratio,翻译过来就是设备像素比,在浏览器里可以通过window.devicePixelRatio拿到。它的计算方式很简单:DPR = 物理像素 / CSS 像素。
举个例子,一部 DPR = 3 的手机,逻辑宽度可能是 393px,物理分辨率却是 1179 x 2556。这意味着你写width: 100px时,浏览器实际要用 300 个物理像素来渲染这条边。DPR 越高,同一段 CSS 背后消耗的物理像素越多,画面越细腻,图片需要提供的分辨率也要跟着翻倍,不然就糊。
这里有个容易绕进去的点:DPR 并不仅仅由硬件决定,它还会跟着浏览器缩放而变化。你按住键盘上的 Ctrl 和 + 把页面放大 200%,window.devicePixelRatio会变成原来的两倍。所以 DPR 本质上是“物理像素与 CSS 像素之间的动态换算系数”,硬件决定了基础值,用户行为可以改变它的实时值。理解了这三个概念,我们才能真正理解单位适配为什么不是简单的“用 rem 还是用 px”。
2. 为什么单位适配会让人头大:三种常见失控现场
适配问题的本质,是我们要在“物理尺寸差异巨大”的设备上,用“同一套逻辑单位”去表达“一致的视觉体验”。听起来是个不可能三角:又要字体看得清,又要比例协调,又要像素级别不糊。那我们就来看看,常见的几种做法各自是怎么翻车的。
2.1 固定 px 在响应式场景下的失效
很多 PC 项目里,设计师给 1920 宽的设计稿,前端也习惯直接写 px。这在单屏单一的办公场景下问题不大,但一旦拿到不同宽度的屏上就露馅:一个 100px 宽的按钮,在 1920px 的屏幕上只占约 5% 的宽度,在 320px 的老安卓机上占了近三分之一。也不说完全没法用,但视觉重量完全变味了。
还有一层更隐蔽的问题:物理尺寸。把同样的 100px 放在 DPR=1 的 24 寸显示器和 DPR=3 的 6 寸手机上,后者每个物理像素点更小更密,100px 的物理长度反而可能只有前者的三分之一。也就是说,你写死一个 px,它在不同设备上显示出来的实际物理大小完全不一样。桌面端用户看着舒服的按钮,手机上可能跟米粒一样小。
所以纯 px 方案的问题是它既不管“视口宽度”,也不管“物理像素密度”。它只适合在那些视口和 DPR 都被严格锁死的场景里用,比如后台管理系统的固定布局、打印样式、以及一些不需要响应式的嵌入场景中。响应式页面如果全部用它,就是在拿尺子的固定刻度去硬量各种不同单位的墙,一定会出问题。
2.2 视口单位与 rem 的救场逻辑
为了应对视口宽度差异,CSS 引入了视口单位 vw、vh、vmin、vmax。这里的逻辑很朴素:既然我不知道用户屏幕有多宽,那就按百分比来。1vw = 当前视口宽度的 1%,所以视口 375px 时 1vw = 3.75px,视口 1440px 时 1vw = 14.4px。这样,宽度、字号、间距都可以随时跟随视口变化。
vw 的问题在于:它是完全线性等比的。你从手机切到 4K 显示器,整体放大 10 倍,但设计上并不一定需要。比如正文 16px 在手机上是合理的,等比放大到 4K 屏幕上变成 160px,反而突兀。所以 vw 适合做“需要严格跟随视口宽度变化”的元素,比如全屏 Banner、栅格间距,但不适合做所有字号的唯一单位。
rem 的引入则是另一个思路:它不是直接跟随视口,而是跟随“根元素的字号”。前端通过 JS 动态设置<html>的font-size,比如视口宽度 375px 时设 37.5px,视口宽度 750px 时设 75px。这样一来,所有以 rem 为单位的属性都会按比例伸缩,但你在代码里写的数字仍然可读,因为 1rem 始终等于根字号。过去几年移动端 H5 项目里,这个方案几乎是标配。
2.3 DPR 变化与用户缩放的隐藏坑
视口和 rem 解决的是“宽度”问题,但还有一块难啃的骨头:DPR 变化。最典型的坑是移动端 1px 边框。设计师说“这条线要非常细,像发丝一样”,但在 DPR=3 的手机上,你写border-bottom: 1px solid #eee,浏览器会用 3 个物理像素来画,结果就是一条粗粗的灰线,高度明显比其他元素对不齐。
另一个坑出现在用户主动缩放页面上。浏览器把页面放大到 125%,devicePixelRatio跟着变大,但 CSS 像素其实没有变,页面的布局宽度也没有变。你会发现有些基于 rem 的布局在用户缩放后比例失调,因为根字号会跟着缩放而重新计算,但内部某些用固定 px 写的模块没跟上。
还有一类 DPR 问题是图片。DPR=2、DPR=3 的设备上,同等 CSS 尺寸的图片需要更清晰的位图资源。如果只给一张 1 倍图,浏览器只能拉伸物理像素,结果就是边缘发虚、文字模糊。这些坑单靠一个单位选择是解决不了的,必须在方案设计一开始就把 DPR 变量纳入考量。
3. 现代化单位适配方案横向对比
前面讲了问题的根源,现在到了选型环节。这一节我会把目前市面上主流方案放在一张表里对比,然后聊聊各自的适用场景和取舍逻辑。没有银弹,每种方案都有它的甜区和雷区。
3.1 六个候选方案的特性拆解
我把固定 px、rem + JS、vw/vh、clamp()、媒体查询 + px、以及以物理像素为目标的方案挑出来,逐个说明适用场景。
| 方案 | 核心原理 | 优点 | 缺点 | 典型使用场景 |
|---|---|---|---|---|
| 固定 px | 直接用逻辑像素单位 | 直观、简单、可控 | 不随视口变化,跨设备物理尺寸差异大 | PC 后台、组件内部细节、打印样式 |
| rem + JS | 根字号跟随视口宽度 | 移动端自适应好,换算直观 | 依赖 JS,可能闪屏,超大屏等比放大失控 | 移动端 H5 活动页、营销页 |
| vw/vh | 长度等于视口宽度/高度的百分比 | 纯 CSS、响应式天然、无 JS | 没有上下限,极端尺寸下失调 | 全屏容器、栅格、移动端排版 |
| clamp() / min() / max() | 数学函数在约束范围内动态取值 | 灵活,适配与限幅兼得 | 老 WebView 不支持,写法稍复杂 | 现代浏览器下的响应式字体、间距 |
| 媒体查询 + px | 按断点切换固定数值 | 精准控制,效果稳定 | 代码量大、断点割裂、难覆盖全机型 | 桌面端复杂响应式布局 |
| 物理像素适配 | 通过 DPR 反推 target 物理像素 | 实现发丝线、高清图等精密效果 | 需要特殊写法,不能做全局主方案 | 1px 边框、图标、背景图清晰度 |
这张表不是让你只选一个,而是让你知道:不同颗粒度的需求,应该用不同的工具去解决。全局布局可以用 rem 或 vw,细节的边框、阴影、图标用物理像素适配,字号的上下限用 clamp() 收住。真正成熟的适配方案一定是组合拳。
3.2 不同层级该选哪个:从页面骨架到小组件
从页面拆分来看,我会把适配任务分成三层:布局层、内容层、细节层。布局层是大的栅格和容器宽度,这个层面对视口宽度最敏感,优先考虑vw或rem;内容层是字体、间距、按钮高度,它需要等比但不希望无限放大,我会用clamp()把上限限制住;细节层是边框、圆角、阴影、图标,这类东西对物理像素密度很敏感,必须用 DPR 感知的写法。
举个典型的组合:移动端 H5 页面里,整页容器宽度直接用100%,外层间距用rem,保证在 320px 到 428px 的视口区间内平滑变化;正文和标题字号用clamp(14px, 2vw + 8px, 20px)这类写法,既随屏变化又不会大到失控;底部 tab 的 1px 分割线,用伪元素加transform: scaleY(0.5)按 DPR 缩放,确保物理像素级清晰。
这套组合的逻辑是:每个层级解决自己最擅长的问题,而不是把所有问题都压在一个单位上。我之前见过有的项目全部用rem,连 1px 边框都用0.05rem写,结果 DPR=2 和 DPR=3 的设备上渲染结果居然不一样。原因很简单:0.05rem会被浏览器四舍五入到不同物理像素数,稳定性非常差。
3.3 关于“物理像素级适配”的冷静思考
网上很多文章喜欢强调“物理像素级适配”这个说法,好像你能精确控制每一个物理像素。真实情况是:你在 CSS 里写的所有数值,最终落到物理屏幕上时,都要经过浏览器的舍入和反走样。尤其是当你处理的是 0.5px、0.333px 这类小数,浏览器的渲染引擎可能按照自己的规则做四舍五入,最终效果在不同机型的 WebView 里并不完全一致。
我做过一个实验:在 DPR=3 的 iPhone 上,height: 0.333px的线条,有的 WebView 认,有的直接把它当 0px 忽略。所以在谈物理像素适配时,我们真正能做的不是“精确控制每个物理像素”,而是“尽量让关键视觉元素(边框、细线、图标)在不同 DPR 下都保持足够锐利”,手段包括伪元素缩放、rem避开极小值、用 SVG 替代位图等。
你听完可能有点失望,但这就是实际工程的真相。把“物理像素级适配”理解成“一种尽可能接近物理像素的渲染策略”,而不是“精确到发丝的魔法”,你后续踩的坑会少很多。
4. 实操:一套可落地的适配方案搭建全流程
概念和选型聊完了,进入动手环节。我以移动端 H5 项目最常用的场景来做完整演示:设计稿宽度 750px,目标设备从 320px 到 430px 视口宽度的智能手机,同时兼容现代浏览器。这套流程你可以直接抄回项目里。
4.1 从设计稿到计算规则:750 宽设计稿怎么换算
为什么很多移动端设计稿都是 750px 宽?因为早期 iPhone 6/7/8 的逻辑宽度是 375px,DPR=2,物理分辨率 750 x 1334。设计稿按物理像素 750 给出,实际上就是“DPR=2 的双倍图”,方便切图和标注。虽然现在已经有很多 390、393、430 逻辑宽度的新机型,但 750 宽设计稿作为行业惯性仍然占据主流。
我们要做的事情很简单:把设计稿上的 px 数值,换算成适合目标视口的 CSS 单位。最朴素的做法是除以 2,因为 750/375=2。但除以 2 属于“死换算”,碰到 393 宽的新 iPhone 就没法精确对应了。更稳的思路是建立一个可缩放的比例基准。
以 rem 为例:把根字号设为“视口宽度的 1/10”,那么视口 375px 时根字号为 37.5px,视口 390px 时根字号为 39px。此时设计稿中任意元素的 px 值换算成 rem,公式就是设计稿px / (设计稿宽度 / 10)。设计稿 750 宽,所以desc 宽度 = xpx / 75单位 rem。比如设计稿字体 30px,对应 0.4rem;按钮宽 600px,对应 8rem。核心逻辑其实是:不管视口怎么变,设计稿中的比例在 rem 世界里被完整保留。
如果你不想用 JS 动态设置根字号,也可以用 vw 来换算:设计稿 750px 等于视口宽度 100vw,那么任意 xpx 换算成 vw 就是x / 750 * 100,单位是 vw。比如 30px 字体 → 4vw,600px 宽按钮 → 80vw。这两种方式数学上是等价的,只是 rem 写法在你的 CSS 里更接近“原来的数值感”,vw 则是真实比例裁量。
4.2 根字号与 rem 库的代码实现
如果你的项目需要兼容旧版 WebView,或者你习惯 rem 的语义,那可以搭建一个简单的 rem 适配脚本。核心代码不复杂,我做了一次完整的封装:
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">(function (win, doc) { function setRemUnit() { var docEl = doc.documentElement; var docWidth = docEl.getBoundingClientRect().width; if (!docWidth) return; var rem = docWidth / 10; docEl.style.fontSize = rem + 'px'; } var resizeEvt = 'orientationchange' in win ? 'orientationchange' : 'resize'; setRemUnit(); win.addEventListener(resizeEvt, setRemUnit, false); win.addEventListener('pageshow', function (e) { if (e.persisted) setRemUnit(); }, false); })(window, document);注意几个细节。getBoundingClientRect().width拿到的是布局视口宽度,比window.innerWidth更可靠,兼容性也更好;监听orientationchange是为了处理横竖屏切换;监听pageshow并判断e.persisted,是为了防止用户从浏览器缓存里恢复页面时,根字号还是上次的旧值。
用的时候,根字号只是基准,不要给<html>之外的标签主动设置font-size,否则 rem 的计算基准就乱套了。还有一个我踩过多次的坑:不要在 CSS 里对根元素写死font-size: 62.5%,那会和 JS 逻辑冲突。二选一,要么靠 JS,要么靠纯 CSS,不要混着来。
4.3 用 clamp() 做字体和间距的流式约束
纯 rem 全局等比缩放有个明显的副作用:在超大屏上,页面元素无限制放大,看起来又大又傻。所以我会结合clamp()给关键字号打上限。clamp(MIN, VAL, MAX)接收三个参数,最后取中间有效值,意思是“小于 MIN 取 MIN,大于 MAX 取 MAX,否则取 VAL”。
一个常见写法是让字体跟随视口宽度线性变化,但限制在合理区间:
:root { --text-sm: clamp(12px, 1vw + 8px, 14px); --text-base: clamp(14px, 2vw + 8px, 18px); --text-title: clamp(20px, 4vw + 8px, 32px); } body { font-size: var(--text-base); }这段代码的逻辑是:视口越宽,2vw越大,但算出来的值永远不会超过 18px,也不会低于 14px。在移动端 375px 下,2vw = 7.5px,加 8px 得 15.5px,符合预期;在桌面 1440px 下,2vw = 28.8px,加 8px 得 36.8px,但被 MAX 截断成 18px,就不会出现“台式机上字大得像标题”的尴尬。
间距我也喜欢用clamp(),比如卡片内边距:padding: clamp(12px, 3vw, 24px)。这样在手机和小屏上间距紧凑,在宽屏上稍微放开,但始终在手感舒适范围内。注意clamp()的 VAL 部分不一定非得包含 vw,你完全可以写成clamp(14px, 0.8rem + 0.5vw, 18px),混用 rem 和 vw 来获得“既有基准,又有微调”的效果。
4.4 移动端 1px 物理细线的四种做法
1px 细线是物理像素适配里最经典的战役。需求很简单:在 DPR=2 或 DPR=3 的设备上,画一条视觉上只有 1 个物理像素的横线。CSS 写border-bottom: 1px solid会渲染成 2 到 3 个物理像素,太粗了。我总结四种主流做法,按稳定性排序。
第一种是伪元素 +transform: scaleY(),兼容性最好,思路也简单:在元素内部创建一个高度为 1px 的伪元素,然后按 DPR 的倒数缩小。DPR=2 时缩小 0.5,DPR=3 时缩小 0.333。具体代码:
.hairline { position: relative; } .hairline::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: #ddd; transform: scaleY(0.5); transform-origin: 0 100%; } @media (-webkit-min-device-pixel-ratio: 3) { .hairline::after { transform: scaleY(0.3333); } }第二种是直接写border-width: 0.5px。现代浏览器已经支持小数像素,但在一些旧版 WebView 里会被忽略变成0px,所以它只能作为渐进增强的替代。第三种是box-shadow: 0 0.5px 0 #ddd,缺点是阴影的渲染和抗锯齿表现受背景影响,颜色一复杂就容易发虚,我基本不推荐。
第四种是在设计层面回避:不用边框线,改用 8px 间距 + 背景色区块去表达分隔。这个方案彻底绕开了物理像素问题,代价是视觉样式受限,适合对设计有强控制权的团队。四种方案里,我最常用的是第一种,稳定、可控、兼容性广。唯一要注意的是伪元素会撑开元素尺寸吗?不会,因为它是绝对定位且不占文档流,但记得给父元素设置position: relative或fixed等定位上下文。
5. 常见问题与排查技巧实录
适配方案搭起来不难,难的是线上真机环境千奇百怪。这一节我把这些年遇到的真实问题和排查方法整理出来,按发生频率排序,你可以直接对照排查。
5.1 meta viewport 设置的连环坑
一个最常见的问题:页面在手机上打开,整体被缩小,或者横向出现大面积空白。十有八九是meta viewport没写对。如果你写的是<meta name="viewport" content="width=750">,浏览器会把布局视口强行按 750px 算,DPR 这个概念直接失效,页面内容会小得可怜。
正确写法是width=device-width,让布局视口等于设备的 CSS 像素宽度,然后再让 DPR 按设备默认值去映射。同时建议加viewport-fit=cover,适配刘海屏和圆角屏,否则 iOS 的 Safe Area 会让你的布局在某些机型上出现黑边。
还有一个小细节:user-scalable=no能禁掉用户双指缩放,但它同时也会影响无障碍体验,很多团队因为以前被页面缩放 bug 折磨过,就直接禁用了。我的建议是保留user-scalable=yes,因为大部分缩放适配问题可以用touch-action: manipulation解决。这个属性可以消除移动端 300ms 点击延迟,也不会影响页面放大功能,副作用比禁用缩放小得多。
5.2 根字号被浏览器最小字号“顶”乱了
另一个高频问题:rem 方案在部分安卓 WebView 上字体异常变大。原因不是 rem 计算错,而是浏览器强制了“最小字号”。比如 Chrome 桌面端默认最小字号 12px(历史原因,现在版本有变化),如果你的根字号设的是 10px,里层某元素用0.8rem算出 8px,浏览器会把 8px 强制渲染成 12px,比例就被破坏了。
排查方法很简单:在目标浏览器里打开控制台,执行getComputedStyle(document.documentElement).fontSize和getComputedStyle(targetElement).fontSize,对比实际渲染值是不是预期值。如果发现被强制提升,解决办法有两个。一个是把根字号基数调大,比如视口 375px 时设 37.5px,这样内部字号算出来通常在 15px 以上,不容易碰到最小字号线。另一个是避开 rem 来做纯小数场景,具体元素的具体字号用 clamp() 或者直接 px 覆盖。
5.3 vh 与移动端地址栏的抖动问题
移动端浏览器地址栏的收起和展开,会实时改变视口高度。早期用vh做全屏容器,会出现一个很恼人的现象:滚动页面时地址栏消失,容器高度突然跳一下,底部按钮位置也跟着抖。
现在浏览器社区给出的新单位是svh(小视口高度)、lvh(大视口高度)和dvh(动态视口高度)。如果你希望侧边栏或全屏弹层始终跟随“当前可见视口”,用dvh;如果你希望稳定在一个不随地址栏变化的最小安全值,用svh。我用下来最稳的组合是:默认写100dvh,需要兼容老 WebView 时回退到100vh。
.fullscreen { height: 100vh; height: 100dvh; }这种“先写老单位,再写新单位覆盖”的写法,可以在不支持新单位的浏览器里优雅降级,是处理 CSS 新特性最常用的技巧。
5.4 高 DPR 下的图片模糊与 srcset
不少同学在适配方案里只关注布局单位,忽略了图片资源。DPR=2 的屏幕上,一张 100x100 CSS 像素的图片,必须有 200x200 物理像素的位图资源才能显示锐利。如果设计师只给了一张 1 倍图,浏览器拉伸后边缘全是锯齿,尤其是文字、logo、二维码这类高信息密度图片,一眼就能看出糊。
解决办法是使用响应式图片属性srcset。浏览器会根据当前 DPR 和视口尺寸自动选择合适的图:
<img src="pic-1x.jpg" srcset="pic-1x.jpg 1x, pic-2x.jpg 2x, pic-3x.jpg 3x" alt="示例图片" >移动端 H5 里,我习惯给关键位图准备 2x 和 3x 两档,并把sizes属性一起写上,帮助浏览器更精准地判断。图标类资源则建议直接用 SVG,位图管它几倍都不如矢量清晰,还省流量。
6. 少有人提的适配细节
最后聊几个偏门但实际存在的内容,我觉得它们才是区分“会用单位”和“会做适配”的关键。看完这节,你对适配方案的理解会比大多数人深一层。
6.1 浏览器缩放到底改变了什么
这是个很容易被误解的点。用户按 Ctrl + 放大页面时,CSS 像素值没有变,布局宽度也没有变,真正变化的是 DPR。也就是说,物理像素总数没变,但浏览器要用更多物理像素去渲染同样的 CSS 像素,所以页面看起来“变大了”,同时也牺牲了清晰度。
这个机制带来的实际影响是:你用clamp()设定的最大字号并不能阻止用户手动放大页面以后字变得很大,因为那是用户体验层面的强制缩放,不是单位换算能控制的。反过来,这也提醒你:不要因为担心用户缩放,就把字号基线设得异常大。该尊重用户可访问性的时候,就放开让浏览器去处理。
6.2 用户无障碍字体放大设置与适配
很多手机系统都有“特大字体”模式,用户打开后,系统会强制放大浏览器渲染的字体。这和浏览器缩放还不是一回事,它是系统级的字体偏好,而且经常优先于你的 CSS 设置。在部分安卓机型上,即使你写了font-size: 14px,系统字体偏好设置为 1.3 倍时,最终渲染可能是 18px。
对这个情况,我的态度是:布局单位用 rem/vw 没问题,但在正文阅读类的容器上,尽量让字体尺寸“可被系统覆盖”,不要给容器设高度,让它随内容自然撑开。强行用物理像素去锁死字号,一方面对抗不了系统的可访问性设计,另一方面也让真正有视觉障碍的用户没法顺畅使用你的产品。遇到这类问题,宁可让页面在某些字号设置下换行变多,也不要给用户“字体调不动”的糟糕体验。
6.3 打印场景下的物理单位
适配方案不只是在屏幕上生效,还有打印场景。CSS 提供了cm、mm、pt这类绝对物理单位,它们不随 DPR 变化,而是在打印纸上直接对应真实尺寸。1pt = 1/72 英寸,1cm = 37.8px左右,具体值取决于打印机的分辨率设置。
如果你做过电子票据、成绩单、发货单这类需要精确排版的功能,肯定会用到它们。给打印需求单独写一份样式表,使用@page设置页面边距,正文用pt或cm,这样页面在不同打印机上打印出来尺寸一致。屏幕上的 rem 方案在打印样式里可以直接整套废弃,不用硬让它去适配纸张。
写在最后的一点个人经验
做了这么多年前端,我踩过的适配坑数不胜数,也逐渐形成了一套固定打法:移动端 H5 以 vw 和 rem 为基础框架,所有字号用 clamp() 设上下限,边框和发丝线用伪元素按 DPR 缩放,图片一律按 2x 起步准备。这套组合短期看没有某一个单位“一统天下”,但胜在每一层都用了最合适的工具,线上的投诉率明显低很多。
最后再分享一个小技巧:适配方案搭好之后,别只在 Chrome DevTools 的设备模拟器里测试,有条件的话拿几台覆盖 DPR=1、DPR=2、DPR=3 的真机实际过一遍。设备模拟器能模拟宽度,却模拟不了 WebView 对小数像素的渲染差异,这恰恰是很多隐蔽 bug 的发源地。真机测一圈,远比你在代码里纠结半天有用得多。