react工程搭建系列之---移動端適配與antd-mobile高清適配方案

1、邏輯像素(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個物理像素構成,這樣屏幕看起來更清晰

圖片描述

2、三種viewport

1.the visual viewport

the visual viewport是在屏幕上顯示頁面的一部分,用戶能夠滾動以更改他看到的頁面部分,或者縮放以更改可視視口的大小
圖片描述css

the visual viewport的大小等於window.innerWidth/Heighthtml

2.the layout viewport

css佈局尤爲是百分比寬是相對於the layout viewport來計算的,the layout viewport比the visual viewport寬的多。
瀏覽器會控制layout viewport尺寸使其在徹底縮小的狀況下覆蓋整個屏幕,這時the visual viewport=the layout viewport
圖片描述react

所以,the layout viewport的寬度和高度等於在最大縮小模式下能夠在屏幕上顯示的任何寬度和高度。當用戶放大這些尺寸時保持不變
圖片描述webpack

the layout viewport的大小等於document.documentElement.clientWidth/Heightgit

3. the ideal viewport

它爲每一個設備上的web頁面提供了一個理想尺寸,每一個設備的理想尺寸都會不一樣。在非Retina屏的時代,the ideal viewport等於物理像素數,但這不是必須的。具備高物理像素密度的新型設備任然保留了原有的ideal viewport,由於它很是適合設備。
4S以上版本包含4S,iPhone理想的視口是320x480,不管它是否有視網膜屏幕。那是由於320x480是這些iPhone上web頁面的理想尺寸。github

關於ideal viewport有兩點很關鍵:web

  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%

3、meta viewport

1.meta viewport標籤

meta viewport標籤包含有關視口(viewports)和縮放(zooming)的瀏覽器指令。特別是,它容許Web開發人員設置layout viewport的寬度,這個寬度直接影響到width:20%這樣的css聲明的計算npm

meta viewport標籤具備如下語法:json

<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值,但實際上這個值並不起做用。

4、縮放對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,這種狀況就叫作徹底縮小
縮放係數,若是我想讓網頁變大,調近手機與電腦之間的距離,這時網頁變大了,可是網頁看不全了也就是可視區域變小了;同理我想讓網頁變小,那麼調遠手機與電腦之間的距離,這時網頁變小了,可是網頁能看到的東西多了,也就是可視區域變大了

5、移動端適配方案

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,這樣就算有方案轉換,咱們也只須要修改插件中的配置和一些腳本文件

圖片描述

6、高清適配方案

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...

相關文章
相關標籤/搜索