Created At 2023-08-09 • Updated At 2026-03-29 • #dev #Notion

移动端适配碎碎念

这篇文章也是源于在前公司的一次技术分享。不过我后来才意识到所谓移动端适配,其实主要是在对付 iOS Safari 或者微信浏览器这些食古不化的玩意。

TL;DR

  1. CSS 里面的像素 px 是逻辑上的单位,而不是物理显示器上面的一个像素点。所以从 1080P 屏幕的入门手机到原来索尼的 4K 屏幕旗舰机逻辑上横向都只有不到 500px
  2. 绝大多数情况下视口等同于屏幕宽度,正确使用 px 和视口单位(vh, vw, dwi, vmax等等)就足以应付大多数情况了。

为什么

px是很常用的单位,但px到底有多大?它等于屏幕上的一个物理像素吗?

w3.org给出的解释如下:

NOTE

怎么才算清晰显示,由设备决定。例如,

假设有这样一个元素:

x40=390375,x=41.6\frac{x}{40} = \frac{390}{375}, x = 41.6

当设备宽度和设计稿宽度不一致的时候,就需要进行等比例缩放。

如何做

rem方案

rem是一种相对单位,1rem 的长度会恒等于html元素的font-size 的属性值。

说白话就是,如果把font-size设置成16px,那么1rem就为16px,设置为24px1rem就是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,则可以实现等比例缩放。

缺陷

参考

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

缺陷

参考

vw方案

CSS有一些和viewport相关的单位,如果令viewport宽度为设备宽度,viewport的1%就相当于设备宽度的1%:

其实上面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的局限性

  1. 很多浏览器,在计算 100vh 的高度的时候,会把地址栏等相关控件的高度计算在内
  2. 同时,很多时候,由于会弹出软键盘等操作,在弹出的过程中,100vh 的计算值并不会实时发生变化!

为了解决上述的问题,规范新推出了三类单位,分别是:

  1. The large viewport units(大视口单位):lvw, lvh, lvi, lvb, lvmin, and lvmax
  2. The small viewport units(小视口单位):svw, svh, svi, svb, svmin, and svmax
  3. The dynamic viewport units(动态视口单位):dvw, dvh, dvi, dvb, dvmin, and dvmax

我们需要先知道什么是lsd三种viewport:

  1. 大视口(Large Viewport):视口大小假设任何动态扩展和缩回的 UA 界面都没有展开
  2. 小视口(Small Viewport):视口大小假设任何动态扩展和缩回的 UA 界面都展开了
  3. 动态视口(Dynamic Viewport)
    • 动态工具栏展开时,动态视口等于小视口的大小
    • 当动态工具栏被缩回时,动态视口等于大视口的大小

viewport comparison

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

参考