移动端适配

487 阅读9分钟

rem方案

媒体查询+rem

原理: 动态根据当前屏幕的宽度,自动改变页面中html font-size的大小, 能让所有使用rem单位的元素跟随着发生变化,而使用px单位的元素不受影响。

1、如何根据屏幕尺寸计算html font-size的大小?

目前rem布局方案中,将网页等分成10份, HTML标签的字号为视口宽度的 1/10

// 在index.html的head标签底部嵌入以下script脚本
<script>
   // 根据屏幕的宽度, 动态设置根元素的fontSize
  const setRootFontSize = () => {
    // 获取屏幕宽度
    var width = document.documentElement.clientWidth;
    //设置html的fontSize
    document.documentElement.style.fontSize = (width / 10) + 'px'
  }
  window.onresize = setRootFontSize;
  setRootFontSize();
</script>
// 所以 1rem = html fontSize = 屏幕宽度的十分之一

注意:10是随意设置的(将屏幕划分为10等份),为了方便计算;这个除数不宜设置太大,因为大部分浏览器的最小字体为12px。

2、若设计图宽度为375px(iPhone SE),上有一个宽度为160px,高为80px的

元素, 那么1rem = 375 / 10 = 37.5px

/* 对于某个width height 300px的元素来说 css文件中需要手动转换成rem  */

div {
  width: calc(160rem / 37.5);
  height: calc(80rem / 37.5);
}

可见上述方式,在css文件中需要进行大量的计算,十分不便

3、优化方案,引入.scss文件, 使用@function的功能来计算rem的值。 rem * html fontSize = 元素的实际px

// style/theme.scss文件中
// 设计稿屏幕的宽度
$design-width: 750;

@function px2rem($px) {
  // 计算出是几个rem
  @return $px / ($design-width / 10) + rem;
}

// demo.scss文件中使用
@import '../style/theme.scss';

.wrapper {
  width: px2rem(300);
  height: px2rem(300);
  box-sizing: border-box;
  background-color: pink;
  border: 1px solid #ccc;
}
.el {
  font-size: 12px;
}

优缺点:

  • 优点: 动态Rem方案既能实现页面级整体缩放,又能个性化控制某些元素不缩放

  • 缺点: 和根元素font-size值强耦合,系统字体放大或缩小时,会导致布局错乱;适配原理稍复杂,html文件头部需插入一段js代码

flexible 适配(过渡方案)

使用flexible js配合rem实现在不同宽度的设备中,网页元素尺寸等比缩放效果

  • flexible.js是手淘开发出的一个用来适配移动端的js框架。
  • 核心原理就是根据不同的视口宽度给网页中html根节点设置不同的font-size

1、使用customize-cra修改react的webpack配置,安装依赖更改package.json里边的配置

npm install customize-cra react-app-rewired --dev
// 更改package.json中的script设置
//原来的:
 "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
//修改后:
 "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  },

px2rem适配的配置:

npm install postcss-px2rem lib-flexible --save
npm install react-app-rewire-postcss --save-dev
cnpm i postcss-px2rem-exclude -D

2、在项目根目录创建config-overrides.js

const {override}  = require("customize-cra")
const path = require("path")
const rewirePostcss = require('react-app-rewire-postcss');
const px2rem = require('postcss-px2rem')
module.exports = override(
  (config,env)=>{
    // 重写postcss
    rewirePostcss(config,{
      plugins: () => [
        require('postcss-flexbugs-fixes'),
        require('postcss-preset-env')({
          autoprefixer: {
            flexbox: 'no-2009',
          },
          stage: 3,
        }),
        require("postcss-px2rem-exclude")({
          // remUnit 等于 设计稿宽度  750除以10 75
          remUnit: 75,
          exclude: /node_modules/i
        }),
      ],
    });
    return config
  },
);

3、index.js中引入

import 'lib-flexible'

4、在 index.html 中

<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />

5、flexible的优缺点:

  • 由于其缩放的缘故,video 标签的视频播放器的样式在不同 dpr 的设备上展示差异很大;
  • lib-flexible 对安卓手机的特殊处理,即:一律按 dpr = 1 处理;
  • 不再兼容 @media 的响应式布局,因为 @media 语法中涉及到的尺寸查询语句,查询的尺寸依据是当前设备的物理像素,和 flexible 的布局理论(即针对不同 dpr 设备等比缩放视口的 scale 值,从而同时改变布局视口和视觉视口大小)相悖,因此响应式布局在“等比缩放视口大小”的情境下是无法正常工作的;
  • 引用lib-flexible github作者的话:flexible

image.png

vw/vh 适配方案

vw 作为布局单位,从底层根本上解决了不同尺寸屏幕的适配问题,因为每个屏幕的百分比是固定的、可预测、可控制的。 viewport 相关概念如下:

  • vw:是 viewport's width 的简写,1vw 等于 window.innerWidth 的 1%;
  • vh:和 vw 类似,是 viewport's height 的简写,1vh 等于 window.innerHeihgt 的 1%;
  • vmin:vmin 的值是当前 vw 和 vh 中较小的值;
  • vmax:vmax 的值是当前 vw 和 vh 中较大的值;

1、设置meta标签:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">

2、插件px 自动转换为 vw

npm install postcss-px-to-viewport --save-dev

3、webpack配置:vue cli2.0

// .postcssrc.js
module.exports = {
  plugins: {
    // ...
    'postcss-px-to-viewport': {
      unitToConvert: 'px',    // 需要转换的单位,默认为"px"
      viewportWidth: 750,     // 设计稿的视窗宽度
      unitPrecision: 5,       // 单位转换后保留的精度
      propList: ['*', '!font-size'],        // 能转化为 vw 的属性列表 即font-size不进行转换vw
      viewportUnit: 'vw',     // 希望使用的视窗单位
      fontViewportUnit: 'vw', // 字体使用的视窗单位
      selectorBlackList: [],  // 需要忽略的 CSS 选择器,不会转为视窗单位,使用原有的 px 等单位
      minPixelValue: 1,       // 设置最小的转换数值,如果为 1 的话,只有大于 1 的值会被转换
      mediaQuery: false,      // 媒体查询里的单位是否需要转换单位
      replace: true,          // 是否直接更换属性值,而不添加备用属性
      exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配 默认值undefined
      include: //src/mobile//, // 如果设置了include,那将只有匹配到的文件才会被转换, 只转换 'src/mobile' 下的文件
      landscape: false,       // 是否添加根据 landscapeWidth 生成的媒体查询条件
      landscapeUnit: 'vw',    // 横屏时使用的单位
      landscapeWidth: 1125,   // 横屏时使用的视窗宽度
    },
  },
};

4、标注不需要转换的属性

在项目中,如果设计师要求某一场景不做自适配,需为固定的宽高或大小,这时我们就需要利用 postcss-px-to-viewport 插件的 Ignoring 特性,对不需要转换的 css 属性进行标注,示例如下所示:

  • /* px-to-viewport-ignore-next */ —> 下一行不进行转换.
  • /* px-to-viewport-ignore */ —> 当前行不进行转换
/* example input: */
.class {
  /* px-to-viewport-ignore-next */
  width: 10px;
  padding: 10px;
  height: 10px; /* px-to-viewport-ignore */
  border: solid 2px #000; /* px-to-viewport-ignore */
}

/* example output: */
.class {
  width: 10px;
  padding: 3.125vw;
  height: 10px;
  border: solid 2px #000;
}

5、react中使用 postcss-px-to-viewport 转换px

5、1 安装:postcss-px-to-viewport 和 postcss-loader

npm install postcss-loader postcss-px-to-viewport --save-dev

5、2 暴露webpack.config.js

npm run eject

5、3 inject导出 webpack.config.js(操作不可逆 慎用)

 loader: require.resolve('postcss-loader'),
        options: {
          postcssOptions: {
            // Necessary for external CSS imports to work
            // https://github.com/facebook/create-react-app/issues/2677
            ident: 'postcss',
            config: false,
            plugins: !useTailwind
              ? [
                  'postcss-flexbugs-fixes',
                  [
                    'postcss-preset-env',
                    {
                      autoprefixer: {
                        flexbox: 'no-2009',
                      },
                      stage: 3,
                    },
                  ],
                  // Adds PostCSS Normalize as the reset css with default options,
                  // so that it honors browserslist config in package.json
                  // which in turn let's users customize the target behavior as per their needs.
                  // 新增
                  [
                    'postcss-px-to-viewport',
                    {
                      unitToConvert: 'px',    // 需要转换的单位,默认为"px"
                      viewportWidth: 750,     // 设计稿的视窗宽度
                      unitPrecision: 5,       // 单位转换后保留的精度
                      propList: ['*', '!font-size'],        // 能转化为 vw 的属性列表 即font-size不进行转换vw
                      viewportUnit: 'vw',     // 希望使用的视窗单位
                      fontViewportUnit: 'vw', // 字体使用的视窗单位
                      selectorBlackList: ['vant'],  // 需要忽略的 CSS 选择器,不会转为视窗单位,使用原有的 px 等单位
                      minPixelValue: 1,       // 设置最小的转换数值,如果为 1 的话,只有大于 1 的值会被转换
                      mediaQuery: false,      // 媒体查询里的单位是否需要转换单位
                      replace: true,          // 是否直接更换属性值,而不添加备用属性
                      exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配 默认值undefined
                      include: //src//, // 如果设置了include,那将只有匹配到的文件才会被转换, 只转换 'src/mobile' 下的文件
                      landscape: false,       // 是否添加根据 landscapeWidth 生成的媒体查询条件
                      landscapeUnit: 'vw',    // 横屏时使用的单位
                      landscapeWidth: 1125,   // 横屏时使用的视窗宽度
                    }
                  ],
                  'postcss-normalize',
                ]
              : [
                  'tailwindcss',
                  'postcss-flexbugs-fixes',
                  [
                    'postcss-preset-env',
                    {
                      autoprefixer: {
                        flexbox: 'no-2009',
                      },
                      stage: 3,
                    },
                  ],
                ],
          },
          sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
        },

5、4 重写webpack.config.js

// 项目根目录创建config-overrides.js
const {override}  = require("customize-cra")
const path = require("path")
const rewirePostcss = require('react-app-rewire-postcss');
module.exports = override(
  (config,env)=>{
    // 重写postcss
    rewirePostcss(config,{
      plugins: () => [
        require('postcss-flexbugs-fixes'),
        require('postcss-preset-env')({
          autoprefixer: {
            flexbox: 'no-2009',
          },
          stage: 3,
        }),
        require('postcss-px-to-viewport')({
            unitToConvert: 'px',    // 需要转换的单位,默认为"px"
            viewportWidth: 750,     // 设计稿的视窗宽度
            unitPrecision: 5,       // 单位转换后保留的精度
            propList: ['*', '!font-size'],        // 能转化为 vw 的属性列表 即font-size不进行转换vw
            viewportUnit: 'vw',     // 希望使用的视窗单位
            fontViewportUnit: 'vw', // 字体使用的视窗单位
            selectorBlackList: [],  // 需要忽略的 CSS 选择器,不会转为视窗单位,使用原有的 px 等单位
            minPixelValue: 1,       // 设置最小的转换数值,如果为 1 的话,只有大于 1 的值会被转换
            mediaQuery: false,      // 媒体查询里的单位是否需要转换单位
            replace: true,          // 是否直接更换属性值,而不添加备用属性
            exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配 默认值undefined
            include: //src/mobile//, // 如果设置了include,那将只有匹配到的文件才会被转换, 只转换 'src/mobile' 下的文件
            landscape: false,       // 是否添加根据 landscapeWidth 生成的媒体查询条件
            landscapeUnit: 'vw',    // 横屏时使用的单位
            landscapeWidth: 1125,   // 横屏时使用的视窗宽度
        }),
      ],
    });
    return config
  },
);

vw适配方案(手动计算)

不需要javascript的适配方案

原理: vw是相对单位,vw = viewportWidth 视口宽度, 1vw = 屏幕视口宽度的1%;可以将需要适配屏幕大小等比例缩放的元素都使用vw作为单位,不需要缩放的元素使用px作为单位

计算公式: 100vw * 设计稿标注大小 / 设计稿的宽度

/* 可以在CSS里使用calc来换算换,只不过需要注意新语法的兼容性。*/
:root {
  --ratio: calc(100vw/750);
}

.button {
  font-size: calc(100vw*28/750); /* 直接使用calc */
  width: calc(120*var(--ratio)); /* 也可以用calc配合var使用, IE不支持var */
}
/* 正式项目中,使用scss 将换算公式交给预处理器 */
// style/theme文件中
$aide-color: #999;

@function px2vw($px) {
  @return $px * 100vw / 750;
}

/* 使用时 */
@import '../../../style/theme';
.button {
  width: px2vw(120);
  font-size: px2vw(28); /* 直接写设计稿标注的尺寸 */
  border: 1px solid #ccc;
}

优缺点:

  • 优点: 适配原理简单;不需要JS即可适配;方案灵活既能实现整体缩放又能局部不缩放。
  • 缺点:虽然不用写JS做适配,但标注尺寸px换算为css的vw计算复杂, 使用viewport 适配方案
  • 总结: vw的兼容性比rem稍微差一些;ios8、安卓4.4及以上才完全支持。这也是为什么之前rem布局一直更流行的原因。

参考文档

script适配脚本

postcss-px-to-viewport中文文档

基于create-react-app 适配移动端方案

以上两种方案,都只是在不同设备上的等比缩放;并不是响应式布局(响应式布局还需配合媒体查询)