移动端适配

移动端开发目前主要包括三类:
- 原生APP开发(IOS、Android、RN、uniapp、Flutter)
- 小程序开发(原生小程序、uniapp、Taro)
- Web 页面(移动端的Web页面,可以使用浏览器或者webview浏览)

目前的移动端设备较多,所以我们需要对其进行一些适配

这里有两个概念
- 自适应:根据不同的设备屏幕大小来自动调整尺寸、大小
- 响应式:会随着屏幕的实时变动而自动调整,是一种自适应

移动端视口(viewport)

视口的概念
- 在一个浏览器中,我们可以看到的区域就是视口(viewport
- 我们都知道 fixed 就是根据视口来进行定位的
- 在 PC 端的页面中,我们是不需要对视口进行区分,因为我们的布局视口和视觉视口是同一个

在移动端,布局的视口和可见的视口是不太一样的
- 这是因为移动端的网页窗口往往比较小,可能会希望一个大的网页在移动端可以完整的显示
- 所有在默认情况下,移动端的布局视口是大于视觉视口的

所以在移动端,可以将视口划分为三种情况
- 布局视口(layout viewport)
- 视觉视口(visual viewport)
- 理想视口(ideal layout)

这些概念的区分,来自于 ppk,他是一位在前端贡献比较大的人(特别是移动端)
- https://www.quirksmode.org/mobile/viewports2.html

布局视口和视觉视口

布局视口

默认情况下,一个 PC 端的网页在移动端会如何显示呢
- 第一,它会按照宽度为 980px 来布局一个页面的盒子和内容
- 第二,为了页面可以完整的显示,会对整个页面进行缩小

我们相对于 980px 布局的这个视口,称之为布局视口
- 布局视口的默认宽度就是 980px

视觉视口

如果默认情况下,我们按照 980px 显示内容,那么右侧有一部分区域就会无法显示,所以手机浏览器会默认对页面进行缩放以显示到用户的可见区域中

那么显示在可见区域的这个视口,就是视觉视口

在Chrome 上按 shift + 鼠标左键可以进行缩放

理想视口

如果所有的网页都按照 980px 在移动端布局,那么最终页面都会被缩放显示
- 事实上这种方式不利于我们进行移动端开发的,我们希望的是设置一个盒子的宽度为 100px,那么显示的就是100px
- 想要做到这一点,需要设置理想视口

理想视口(ideal viewport)
- 默认的 layout viewport 并不适合我们进行布局,可以对 layout viewport 进行宽度和缩放设置,以满足正常在一个移动端窗口的布局
-设置 meta中的 viewport = width=device-width

移动端适配方案

移动端的屏幕尺寸通常是非常多的,很多时候我们希望在不同的屏幕尺寸上显示不同的大小

比如我们设置一个 100 * 100 的盒子
- 在 375px 的屏幕上显示是 100*100
- 在 320px 的屏幕上显示是 90+*90+
- 在 414px 的屏幕上显示是 100+*100+
其他尺寸也是类似,比如 padding、margin、border、left、甚至是 font-size 等等

这个时候,我们可以想到一些方案来处理尺寸
- 方案一:百分比设置
- 因为不同属性的百分比值,相对的可能是不同的参照物,所以百分比往往很难统一
- 所以百分比在移动端适配中使用是非常少的
- 方案二:rem 单位 + 动态 html 的 font-size
- 方案三:vw 单位
- 方案四:flex 的弹性布局

适配方案-rem+动态html的font-size

rem 单位是相对于 html 元素的 font-size 来设置的,那么如果我们需要在不同的屏幕下有不同的尺寸,可以动态的修改 html 的 font-size

比如下面的案例
1. 设置一个盒子的宽度是 2rem
2. 设置不同屏幕上的 html 的 font-size 不同

这样在开发中,只需要考虑两个问题
- 问题一:针对不同的屏幕,设置不同的 font-size
- 问题二:将原来要设置的尺寸,转化为 rem 单位

rem 的 font-size 尺寸

方案一:媒体查询
- 可以使用媒体查询来设置不同尺寸范围内的屏幕 html 的 font-size 尺寸
- 缺点:
1. 我们需要针对不同屏幕编写大量的代码
2. 如果动态改变尺寸,不能实时更新

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    @media screen and (min-width: 320px){
      html{
          font-size: 20px;
        }
    }

    @media screen and (min-width: 375px){
      html{
          font-size: 24px;
        }
    }

    @media screen and (min-width: 414px){
      html{
          font-size: 28px;
        }
    }
    
    @media screen and (min-width: 480px){
      html{
          font-size: 32px;
        }
    }

    .box {
      width: 5rem;
      height: 5rem;
      background-color: orange;
    }
  </style>
</head>
<body>
  <div class="box"></div>
</body>
</html>

方案二:js动态修改
- 如果希望实时改变屏幕尺寸,font-size也可以实时修改,需要通过 js 代码
- 方法:
1. 根据 html 的宽度计算出 font-size 的大小,并且设置到 html 上
2. 监听页面的实时改变,并且重新计算 font-size 的大小到 html 上

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script>
    // 获取 html 元素
    const htmlEl = document.documentElement
    function setRemUnit(){
      // 获取 html 的宽度(视口的宽度)
      const htmlWidth = htmlEl.clientWidth
      // 根据宽度计算一个 html 的 font-size 大小
      const htmlFontSize = htmlWidth / 10
      // 将 font-size 设置到 html 上
      htmlEl.style.fontSize = htmlFontSize + 'px'
    }
    // 保证第一次进入的时候,设置一次 font-size
    setRemUnit()
    // 当屏幕发生变化的时候,实时的修改 html 的font-size
    window.addEventListener('resize',setRemUnit)
    // 375px -> 16px
    // 320px -> 12px

  </script>
  <style>
    html{
      font-size: 16px;
    }
    .box {
      width: 5rem;
      height: 5rem;
      background-color: orange;
    }
    p {
      font-size: 0.5rem;
    }
  </style>
</head>
<body>
  <div class="box"></div>
  <p>我是文本</p>
</body>

</html>

方案三:lib-flexible
- https://github.com/amfe/lib-flexible

rem 单位换算

方案一:手动换算
- 比如一个在 375px(设计稿) 屏幕上,100px 宽度和高度的盒子
- 我们需要将 100px 转换成对应的 rem 值
- 100 / 37.5 = 2.6667 其他屏幕也是相同的计算方法即可

方案二:less/scss 函数

.pxToRem(@px) {
	result: 1rem * (@px / 37.5);
}

.box {
	width: .pxToRem(100)[result];
	height: .pxToRem(100)[result];
	background-color: orange;
}

p {
	font-size: .pxToRem(14)[result];
}

方案三:postcss-pxtorem
- 目前在前端的工程化开发中,我们可以借助与 webpack 的工具来完成自动的转换

方案四:vscode 插件
- px to rem 的插件,在编写时自动转换

适配方案 - vw

由于viewport单位得到众多浏览器的兼容,lib-flexible这个过渡方案已经可以放弃使用,不管是现在的版本还是以前的版本,都存有一定的问题。建议大家开始使用viewport来替代此方。

vw 和 rem 的对比

rem 事实上是作为一种过渡方案,它利用的是 vw 的思想

  • 不管是自己编写 js 代码,还是使用 flexible 这种项目的源码
  • 都是将 1rem 等同于设计稿的 1/10,再利用 1rem 计算相对于整个屏幕的尺寸大小
  • 1vw 不是刚好等于屏幕的 1/100 吗,而且相对于 rem 还是更加有优势

vw 相比于 rem 的优势
1. 不需要去计算 html 的 font-size 大小,也不需要给 html 设置这样一个 font-size
2. 不会因为设置 html 的 font-size 大小而必须给 body 再设置一个 font-size 避免继承
3. 因为不依赖 font-size 的尺寸,所以不需要担心某些原因 html 的 font-size 尺寸被篡改,导致页面混乱
4. vw 相比于 rem 更加语义化,1vw 刚好是 1/100 的 viewport 的大小
5. 可以具备 rem 之前所有的优点

vw 我们只需要考虑一个问题,将尺寸换算成 vw 的单位即可

所以,目前相比于 rem,更推荐使用 vw(但是理解rem很重要)

vw 单位换算

方案一:手动换算
- 比如在一个 375px 屏幕上,100px 宽度和高度的盒子
- 我们需要将 100px 转换成对于的 vw 值
- 100/3.75=26.6667,其他尺寸的设计稿也是相同的方法计算

方案二:less/scss 函数

方案三:postcss-px-to-view-8-plugin
- 和 rem 一样,在前端工程化中,可以借助于 webpack 的插件来自动转化

方案四:VSCode插件
- px to vw 的插件,在编写时自动转换

Q.E.D.