Created At 2023-08-09 • Updated At 2026-03-29 • #dev #Notion
移动端适配碎碎念
这篇文章也是源于在前公司的一次技术分享。不过我后来才意识到所谓移动端适配,其实主要是在对付 iOS Safari 或者微信浏览器这些食古不化的玩意。
TL;DR
- CSS 里面的像素
px是逻辑上的单位,而不是物理显示器上面的一个像素点。所以从 1080P 屏幕的入门手机到原来索尼的 4K 屏幕旗舰机逻辑上横向都只有不到 500px - 绝大多数情况下视口等同于屏幕宽度,正确使用
px和视口单位(vh,vw,dwi,vmax等等)就足以应付大多数情况了。
为什么
px是很常用的单位,但px到底有多大?它等于屏幕上的一个物理像素吗?
w3.org给出的解释如下:
- 在打印机上,1 inch = 96px
- 在屏幕上,一根能清晰显示的最细的直线,其宽度为1px
NOTE
怎么才算清晰显示,由设备决定。例如,
- iPhone 12 Pro 屏幕横向有1170像素,逻辑宽度390px
- iPhone SE 2 屏幕横向有750像素,逻辑宽度375px
假设有这样一个元素:
- 在屏幕宽度为375px的设计稿上,宽度为40px
- 那么在屏幕宽度为390px的设备上,则应为41.6px
当设备宽度和设计稿宽度不一致的时候,就需要进行等比例缩放。
如何做
rem方案
rem是一种相对单位,1rem 的长度会恒等于html元素的font-size 的属性值。
说白话就是,如果把font-size设置成16px,那么1rem就为16px,设置为24px,1rem就是24px,以此类推。
可以利用这个特性把设备逻辑宽度和根元素的字体关联起来。使设备宽度不管为多少px,以rem表示时都为定值,比如3.75rem,核心代码如下:
(function () {
function changeRootFont() {
const WIDTH = 375
let scale = screen.width / WIDTH
let content = `width=${WIDTH}, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}`
let meta = document.querySelector('meta[name=viewport]')
if(!meta) {
meta = document.createElement('meta')
meta.setAttribute('name', 'viewport')
document.head.appendChild(meta)
}
meta.setAttribute('content', content)
}
changeRootFont()
window.addEventListener("resize", changeRootFont, false)
})()
编写CSS代码时,把40px则写成0.4rem,则可以实现等比例缩放。
缺陷
- 修改了根元素的
font-size破坏语义。
viewport方案
viewport就是Web内容可以被看见的窗口区域。它的宽度可以设置为定值,当然若这个值超过了设备宽度会导致横向滚动。
viewport方案就是把viewport宽度固定为设计稿宽度,然后把缩放比调成设备宽度和设计稿宽度的比例。
<head>
<!-- ... -->
<meta name="viewport" content="width={设计稿宽度}, initial-scale={屏幕逻辑像素宽度/设计稿宽度}" >
</head>
const WIDTH = 750
const mobileAdapter = () => {
let scale = screen.width / WIDTH
let content = `width=${WIDTH}, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}`
let meta = document.querySelector('meta[name=viewport]')
if(!meta) {
meta = document.createElement('meta')
meta.setAttribute('name', 'viewport')
document.head.appendChild(meta)
}
meta.setAttribute('content', content)
}
mobileAdapter()
window.onorientationchange = mobileAdapter
缺陷
- 1px边框会变粗
vw方案
CSS有一些和viewport相关的单位,如果令viewport宽度为设备宽度,viewport的1%就相当于设备宽度的1%:
vw(Viewport's width):1vw等于视觉视口的1%vh(Viewport's height):1vh为视觉视口高度的1%vmin:vw和vh中的较小值vmax:选取vw和vh中的较大值
其实上面rem方案可以看作是对它的一种模拟,以兼容2013年以前的浏览器。(兼容性)
使用CSS的calc()或者Sass这样的预处理器,就可以把px转换成vw单位。
:root {
--ratio: calc(100vw/750);
}
.button {
font-size: calc(100vw*28/750); /* 可以直接用calc */
line-height: calc(100vw*48/750);
width: calc(120*var(--ratio)); /* 也可以用calc配合var使用,IE不支持var */
border: 1px solid #000; /*不需要缩放的部分用px*/
text-align: center;
}
@function px2vw($px) {
@return $px * 100vw / 750;
}
.button {
width: px2vw(120);
font-size: px2vw(28);
line-height: px2vw(48);
border: 1px solid #000;
text-align: center;
}
postcss改进版
因为px是非常直观的单位,我们可以手写px,让postcss帮忙编译成vw
pnpm add -D postcss-px-to-viewport
配置示例如下,更多选项可以参考https://github.com/evrone/postcss-px-to-viewport
// postcss.config.cjs
module.exports = {
plugins: {
'postcss-px-to-viewport': {
unitToConvert: 'px',
viewportWidth: 375,
unitPrecision: 5,
propList: ['*', "!border"],
viewportUnit: 'vw',
}
},
}
viewport演进
vh的局限性
- 很多浏览器,在计算
100vh的高度的时候,会把地址栏等相关控件的高度计算在内 - 同时,很多时候,由于会弹出软键盘等操作,在弹出的过程中,
100vh的计算值并不会实时发生变化!
为了解决上述的问题,规范新推出了三类单位,分别是:
- The large viewport units(大视口单位):
lvw,lvh,lvi,lvb,lvmin, andlvmax - The small viewport units(小视口单位):
svw,svh,svi,svb,svmin, andsvmax - The dynamic viewport units(动态视口单位):
dvw,dvh,dvi,dvb,dvmin, anddvmax
我们需要先知道什么是lsd三种viewport:
- 大视口(Large Viewport):视口大小假设任何动态扩展和缩回的 UA 界面都没有展开
- 小视口(Small Viewport):视口大小假设任何动态扩展和缩回的 UA 界面都展开了
- 动态视口(Dynamic Viewport)
- 动态工具栏展开时,动态视口等于小视口的大小
- 当动态工具栏被缩回时,动态视口等于大视口的大小

结论:全面使用 dvh 替代 vh,能有效的减少非常多因为 vh 在移动端的表现而引起的问题。