react工程搭建系列之---移动端适配与antd-mobile高清适配方案

SuRuiGit

一、逻辑像素(css像素)与物理像素(设备像素)

机型 逻辑像素 物理像素 Scale Factor
iphone 3GS 320 x 480 320 x 480 1x
iphone 4 320 x 480 640 x 960 2x
iphone 4S 320 x 480 640 x 960 2x
iphone 5 320 x 568 640 x 1136 2x
iphone 5C 320 x 568 640 x 1136 2x
iphone 5S 320 x 568 640 x 1136 2x
iphone 5SE 320 x 568 640 x 1136 2x
iphone 6 375 x 667 750 x 1134 2x
iphone 6P 414 x 736 1080 x 1920 2.6x
iphone 6S 375 x 667 750 x 1134 2x
iphone 6SP 414 x 736 1080 x 1920 2.6x
iphone 7 375 x 667 750 x 1134 2x
iphone 7P 414 x 736 1080 x 1920 2.6x
  • 设备像素:设备硬件的物理像素
  • 逻辑像素:软件所支持的像素
  • dpr(Device Pixel Ratio: Number of device pixels per CSS Pixel): 设备像素比
    也叫dppx 就是一个css像素控制几个物理像素,物理分辨率/逻辑分辨率(css分辨率)= dpr
  • iphone 3GS,可以看到一个逻辑像素是由一个物理像素构成,随着技术发展出现了Retina屏使得设备分辨率提高一倍,一个逻辑像素可以由 (640/320)* (960/480) = 4个物理像素构成,这样屏幕看起来更清晰

图片描述

二、三种viewport

1.the visual viewport

the visual viewport是在屏幕上显示页面的一部分,用户可以滚动以更改他看到的页面部分,或者缩放以更改可视视口的大小
图片描述

the visual viewport的大小等于window.innerWidth/Height

2.the layout viewport

css布局尤其是百分比宽是相对于the layout viewport来计算的,the layout viewport比the visual viewport宽的多。
浏览器会控制layout viewport尺寸使其在完全缩小的情况下覆盖整个屏幕,这时the visual viewport=the layout viewport
图片描述

因此,the layout viewport的宽度和高度等于在最大缩小模式下可以在屏幕上显示的任何宽度和高度。当用户放大这些尺寸时保持不变
图片描述

the layout viewport的大小等于document.documentElement.clientWidth/Height

3. the ideal viewport

它为每个设备上的web页面提供了一个理想尺寸,每个设备的理想尺寸都会不同。在非Retina屏的时代,the ideal viewport等于物理像素数,但这不是必须的。具有高物理像素密度的新型设备任然保留了原有的ideal viewport,因为它非常适合设备。
4S以上版本包含4S,iPhone理想的视口是320x480,无论它是否有视网膜屏幕。那是因为320x480是这些iPhone上web页面的理想尺寸。

关于ideal viewport有两点很关键:

  1. the layout viewport可以被设置成the ideal viewport,使用meta标签的The width=device-width 和initial-scale=1指令实现
  2. 所有的scale指令是相对于the ideal viewport而言,不管the layout viewport拥有多大的宽度,因此maximum-scale=3 意味着web页面可以放大到the ideal viewport的300%

三、meta viewport

1.meta viewport标签

meta viewport标签包含有关视口(viewports)和缩放(zooming)的浏览器指令。特别是,它允许Web开发人员设置layout viewport的宽度,这个宽度直接影响到width:20%这样的css声明的计算

meta viewport标签具有以下语法:

<meta name="viewport" content="name=value,name=value">

2.指令

viewport mata标签的每一对name/value都是一条指令。总共有6条指令:

  1. width: 用来设置layout viewport的宽度。
  2. initial-scale: 用来设置页面的初始缩放值以及layout viewport的宽度。
  3. minimum-scale: 用来设置允许的最小缩放值(例如,用户可以缩小至什么程度)。
  4. maximum-scale: 用来设置允许的最大缩放值(例如,用户可以放大至什么程度)。
  5. height: 期望用于设置layout viewport的高度。但一直没被支持。
  6. user-scalable: 当设置为no时,则禁止用户进行缩放。

3.device-width值

width指令有一个特殊的值:device-width。它能将layout viewport的宽度设置成ideal viewport宽度。 理论上同样有一个类似的device-height值,但实际上这个值并不起作用。

四、缩放对viewport的影响

1.缩放

缩放是棘手的。理论上讲很简单:确定用户可以放大或缩小的缩放系数(zoom factor)。这里存在两个问题:

  1. 我们不能够直接读取缩放系数,而是需要读取visual viewport的宽度,它与缩放系数成反比关系。缩放系数越大,visual viewport的宽度越小。因此,最小缩放系数决定了最大visual viewport宽度,反之亦然。
  2. 事实证明,无论layout viewport的当前大小是什么,所有缩放因子都相对于ideal viewport

因此关于缩放这个名字的问题,缩放实际上是比例,而viewport meta的指令称之为initial-scale、minimum-scale、maximum-scale。其它浏览器为了保持和针对iPhone适配的网站兼容也只好被迫实现了这些指令。
这三个指令期望一个缩放因子,例如2意味着“缩放到ideal viewport宽度的200%”

2.公式

visual viewport width = ideal viewport width / zoom factor
zoom factor = ideal viewport width / visual viewport width

3.理解

我们先来梳理一下:
  • 我们平时开发的css是基于layout viewport来计算
  • 在完全缩小的情况下layout viewport=visual viewport
  • 使用meta viewport的width指令设置layout viewport的宽,当width=device-width 和initial-scale=1时,layout viewport=ideal viewport。以iphone4S为例,ideal width是320,此时layout viewport也是320,初始缩放系数是1也就是没有缩放
  • 当用户进行缩放的时候layout viewport是不会变的,visual viewport与缩放系数成反比
我的理解:

这里以手机拍照为例,用手机后置摄像头拍摄电脑上的一个网页,调整手机与电脑之间的距离使得整个页面刚好拍进手机里,但是网页变小了,这时layout viewport=visual viewport,这种情况就叫做完全缩小
缩放系数,如果我想让网页变大,调近手机与电脑之间的距离,这时网页变大了,但是网页看不全了也就是可视区域变小了;同理我想让网页变小,那么调远手机与电脑之间的距离,这时网页变小了,但是网页能看到的东西多了,也就是可视区域变大了

五、移动端适配方案

1.目前行业内流行几种适配方法

  • JS根据屏幕动态计算 使用js判断页面宽度算出页面应有的font-size
  • 媒体查询 使用媒体查询 来兼容不同尺寸屏幕 设置不同尺寸下的rem大小
  • flex布局 CSS3中提出的新布局方案 移动端的兼容性较好

使用rem作为移动端尺寸单位替代px,那么1rem=?px
目前1rem有三种方案:

  1. 1rem=16px
    这个是默认的大小
  2. 1rem= 75px
    这个是手淘团队在flexible方案中在iphone6中的显示结果
    flexible方案核心就是根据屏幕的dpr和尺寸 动态算出当前页的rem大小 动态的修改meta标签
    该方案目前也被应用在手淘首页中
  3. 1rem=100px
    这个是阿里旗下的蚂蚁金服在Ant-mobile中的方案
    ant-mobile也有自己高清解决方案 其核心跟flexible类似
    现应用于ant-mobile中

如果项目中使用的是1rem=16px,又集成了antd-mobile,那么就会导致antd-mobile中的组件特别小,这就面临着方案转换的问题,如果我们在项目的样式中以rem作为单位,现在是16px转100px,如果以后用75px那么所有的样式文件中rem就都需要进行转换工程量很大。所以,样式文件中我们还使用px作为单位,然后使用插件将px转成rem,这样就算有方案转换,我们也只需要修改插件中的配置和一些脚本文件

图片描述

六、高清适配方案

1.在public/index.html中删除meta viewport标签,然后用下列代码动态生成meta viewport标签

'use strict';

/**
 * @param {Number} [baseFontSize = 100] - 基础fontSize, 默认100px;
 * @param {Number} [fontscale = 1] - 有的业务希望能放大一定比例的字体;
 */
const win = window;
export default win.flex = (baseFontSize, fontscale) => {
  const _baseFontSize = baseFontSize || 100;
  const _fontscale = fontscale || 1;

  const doc = win.document;
  const ua = navigator.userAgent;
  const matches = ua.match(/Android[\S\s]+AppleWebkit\/(\d{3})/i);
  const UCversion = ua.match(/U3\/((\d+|\.){5,})/i);
  const isUCHd = UCversion && parseInt(UCversion[1].split('.').join(''), 10) >= 80;
  const isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);
  let dpr = win.devicePixelRatio || 1;
  if (!isIos && !(matches && matches[1] > 534) && !isUCHd) {
    // 如果非iOS, 非Android4.3以上, 非UC内核, 就不执行高清, dpr设为1;
    dpr = 1;
  }
  const scale = 1 / dpr;

  let metaEl = doc.querySelector('meta[name="viewport"]');
  if (!metaEl) {
    metaEl = doc.createElement('meta');
    metaEl.setAttribute('name', 'viewport');
    doc.head.appendChild(metaEl);
  }
  metaEl.setAttribute('content', `width=device-width,user-scalable=no,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale}`);
  doc.documentElement.style.fontSize = `${_baseFontSize / 2 * dpr * _fontscale}px`;
};
flex(100, 1);

代码理解:以iphone6为准,dpr=2,scale=1/2,fontSize=100;由之前介绍的viewport可以知道,scale=1/2那么visual viewport=2*ideal viewport

2.修改package.json

"theme": {
    "hd": "2px",
    "brand-primary": "red",
    "color-text-base": "#333"
  },

3.修改config-overrides.js在webpack配置中使用 postcss-pxtorem 把 px 转成 rem 单位

安装react-app-rewire-postcss
npm install react-app-rewire-postcss --save-dev
配置postcss,完整代码如下:
const { injectBabelPlugin, getLoader } = require('react-app-rewired');
const rewirePostcss = require('react-app-rewire-postcss');
const pxtorem = require('postcss-pxtorem');
const autoprefixer = require('autoprefixer');
const theme = require('./package.json').theme;
const fileLoaderMatcher = function (rule) {
    return rule.loader && rule.loader.indexOf(`file-loader`) != -1;
}
module.exports = function override(config, env) {
    // do stuff with the webpack config...
    config = injectBabelPlugin(['import', {
        libraryName: 'antd-mobile',
        // style: 'css',
        style: true, // use less for customized theme
    }], config);
    console.log(config.module.rules[2].oneOf);

    // sass
    config.module.rules[2].oneOf.unshift(
        {
            test: /\.scss$/,
            use: [
                require.resolve('style-loader'),
                require.resolve('css-loader'),
                require.resolve('sass-loader'),
                {
                    loader: require.resolve('postcss-loader'),
                    options: {
                        // Necessary for external CSS imports to work
                        // https://github.com/facebookincubator/create-react-app/issues/2677
                        ident: 'postcss',
                        plugins: () => [
                            require('postcss-flexbugs-fixes'),
                            autoprefixer({
                                browsers: [
                                    '>1%',
                                    'last 4 versions',
                                    'Firefox ESR',
                                    'not ie < 9', // React doesn't support IE8 anyway
                                ],
                                flexbox: 'no-2009',
                            })
                        ],
                    },
                }
            ]
        }
    );
    //less
    config.module.rules[2].oneOf.unshift(
        {
            test: /\.less$/,
            use: [
                require.resolve('style-loader'),
                require.resolve('css-loader'),
                {
                    loader: require.resolve('postcss-loader'),
                    options: {
                        // Necessary for external CSS imports to work
                        // https://github.com/facebookincubator/create-react-app/issues/2677
                        ident: 'postcss',
                        plugins: () => [
                            require('postcss-flexbugs-fixes'),
                            autoprefixer({
                                browsers: [
                                    '>1%',
                                    'last 4 versions',
                                    'Firefox ESR',
                                    'not ie < 9', // React doesn't support IE8 anyway
                                ],
                                flexbox: 'no-2009',
                            }),
                        ],
                    },
                },
                {
                    loader: require.resolve('less-loader'),
                    options: {
                        // theme vars, also can use theme.js instead of this.
                        modifyVars: theme,
                    },
                },
            ]
        }
    );

    config = rewirePostcss(config,{
        plugins: () => [
            require('postcss-flexbugs-fixes'),
            require('postcss-preset-env')({
                autoprefixer: {
                    flexbox: 'no-2009',
                },
                stage: 3,
            }),
            pxtorem({
                rootValue: 100,    //以100px为准,不同方案修改这里
                propWhiteList: [],
            })
        ],
    });

    // file-loader exclude
    let l = getLoader(config.module.rules, fileLoaderMatcher);
    l.exclude.push(/\.scss$/);
    l.exclude.push(/\.less$/);
    return config;
};
将src/App.css改成scss格式并修改App.js如下:
/*src/App.scss*/
.App {
  text-align: center;
  .App-Button{
    width: 750px;
    height: 88px;
  }
}
/*src/App.js*/
import React, { Component } from 'react';
import './App.scss';
import {Button} from 'antd-mobile';

class App extends Component {
  render() {
    return (
      <div className="App">
          <Button type='primary' className='App-Button'>{document.documentElement.clientWidth}</Button>
      </div>
    );
  }
}

export default App;
最终运行结果如下图:

图片描述

可以看到px已经被转换成rem了,layout viewport = 750px

项目地址:https://github.com/SuRuiGit/m...
阅读 14.6k

前端随笔
前端开发学习总结
264 声望
20 粉丝
0 条评论
264 声望
20 粉丝
文章目录
宣传栏