1. Core concepts

1.1 Physical pixels of the device

  • It’s a physical concept, it’s the smallest physical unit that the display displays, the physical pixels of the device screen, the number of physical pixels of any device is fixed
  • The iPhone6’s pixel resolution is 750 x 1334

1.2 Device-independent pixels

  • Is a logical concept created for Web developers, an abstract layer used in CSS and javascript to provide information about the width, height, and so on in CSS

  • The iPhone6’s logical resolution is 375 by 667

  • iPhone6:window.screen.width=375,window.screen.height=667

  • Px is a unit relative to the device pixel.

    Pixel, also known as picture element, is the basic unit of image display. It is translated from The English word “pixel”. Pix is the abbreviation of the English word “picture”. Pixels, sometimes referred to as pel(picture element), are the basis of the layout of a web page. A pixel is the smallest area that a computer can display a particular color. When devices are the same size but more densely populated with pixels, the transitions on the screen are more detailed and websites look brighter.

    CSS pixels are used for every CSS declaration and almost all javascript attributes, so device pixels are never actually used, with the exception of screen.width/height

1.3 Device pixel ratio

  • DPR(Device pixel ratio) = Device pixel /CSS pixel

  • DevicePixelRatio window.devicepixelratio

    In earlier mobile devices, there was no concept of DPR. With the development of technology, the screen density of mobile devices is getting higher and higher. Starting with the iphone4, apple introduced the so-called retina display. It is called a retina screen because the PPI(screen pixel density) is so high that the human retina cannot distinguish pixels on the screen. The iphone4 has doubled the resolution, but the screen size is the same, which means we have twice as many pixels on a screen of the same size, so DPR is equal to 2 and in fact, the CSS pixels at this point correspond to the ideal viewport that we’ll talk about later, Its corresponding javascript attribute is screen width/screen height for equipment pixels than the DPR also has the corresponding javascript properties window. DevicePixelRatio iphone 5, for example, The iphone5 has a CSS pixel of 320px by 568px and a DPR of 2, so its device pixel is 640px by 1136px

1.4 Mobile Adaptation

  • Generally, designers make design drafts according to the unit of device pixel
  • The conversion is then done by the front end engineers using the device Pixel ratio

1.4.1 rem

  • Refer to the font size of the root element
  • Adaptation allows the font size of the root element to change dynamically based on resolution
  • px2rem-loader

1.4.2 vw and vh

  • The reference is the Viewport
  • Vw refers to the viewport width (1vw= viewport width /100)
  • Vh refers to the height of the viewport (1vh= viewport height /100)
  • Vw iPhone6 1 = 3.75 px
  • postcss-px-to-viewport

2. Px2rem – loader of actual combat

2.1 installation

npm install webpack webpack-cli html-webpack-plugin style-loader css-loader amfe-flexible px2rem-loader --save-dev
Copy the code

2.2 the SRC \ index. Js

import './base.css'
Copy the code

2.3 the SRC \ base CSS

 width:750px;
  height:750px;
  background-color: red;
  font-size:12px;/*px*/
  border: 1px solid #ddd; /*no*/
  box-shadow: 0 2px 0 rgb(0 0 0 / 5%);
}
Copy the code

SRC \ index 2.4 HTML

<! DOCTYPEhtml>
<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>webpack</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>
Copy the code

2.5 webpack. Config. Js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    mode:'development'.devtool: false.entry: './src/index.js'.output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js',},resolveLoader: {
        alias: {
          "px2rem-plus-loader": path.resolve('./loaders/px2rem-plus-loader.js')},modules: [path.resolve('./loaders'), 'node_modules']},module: {
        rules: [{test: /\.css$/,
            use: [{
                loader: 'style-loader'
            }, {
                loader: 'css-loader'
            }, 
            / / {
            // loader: 'px2rem-loader',
            // options: {
            // remUni: 75,
            // remPrecision: 8
            / /}
            // },
            {
              loader: 'px2rem-plus-loader'.//path.resolve(__dirname, 'loaders/px2rem-plus-loader.js'),
              options: {
                  remUnit: 75.remPrecision: 8.baseDpr:1.exclude:/antd\.css/}}]}, {test:/\.js$/,
            use: {
                loader: 'babel-loader'.options: {
                  presets: ['@babel/preset-env'.'@babel/preset-react'].plugins: ['@babel/transform-runtime']}},exclude: /node_modules/}},],plugins: [
        new HtmlWebpackPlugin(
          { template: './src/index.html'.title:'index'.inject: true})]};Copy the code

2.6 package. Json

{
   "scripts": {
    "build": "webpack"}}Copy the code

3.loader

  • Loader is used to convert the module source code
  • Loader allows you to preprocess files when importing modules
  • Loader can convert files from different languages (such as TypeScript) to JavaScript

loaders\px2rem-plus-loader.js

var loaderUtils = require('loader-utils');
//var Px2rem = require('px2rem');
var Px2rem = require('./px2rem');
function loader(source) {
  var options = loaderUtils.getOptions(this);
  if(options.exclude && options.exclude.test(this.resource)){
       return source;
  }
  var px2remIns = new Px2rem(options);
  let targetSource = px2remIns.generateRem(source);
  return targetSource;
}
module.exports = loader;
Copy the code

4. Use a custom Loader

Configuration in webpack.config.js

resolveLoader: {
        alias: {
          "px2rem-plus-loader": path.resolve('./loaders/px2rem-plus-loader.js')},modules: [path.resolve('./loaders'), 'node_modules']},Copy the code

5.css

5.1 the AST

  • astexplorer

  • JavaScript Parser converts source code to an abstract syntax tree (AST) that defines the structure of the code

5.2 AST Workflow

  • Parse converts source code into an abstract syntax tree with many ESTree nodes
  • Transform Transforms the abstract syntax tree
  • Generate generates new code from the transformed abstract syntax tree of the previous step

5.3 px2rem. Js

px2rem

/* * @Description: * @Author: changqing * @Date: 2021-04-29 11:54:54 * @LastEditTime: 2021-05-02 11:46:46 * @LastEditors: changqing * @Usage: */
'use strict';

var css = require('css');
var extend = require('extend');

var defaultConfig = {
  baseDpr: 2.// base device pixel ratio (default: 2)
  remUnit: 75.// rem unit value (default: 75)
  remPrecision: 6.// rem value precision (default: 6)
  forcePxComment: 'px'.// force px comment (default: `px`)
  keepComment: 'no'       // no transform value comment (default: `no`)
};

var pxRegExp = /\b(\d+(\.\d+)?) px\b/;

function Px2rem(options) {
  this.config = {};
  extend(this.config, defaultConfig, options);
}

// generate @1x, @2x and @3x version stylesheet
Px2rem.prototype.generateThree = function (cssText, dpr) {
  dpr = dpr || 2;
  var self = this;
  var config = self.config;
  var astObj = css.parse(cssText);

  function processRules(rules) {
    for (var i = 0; i < rules.length; i++) {
      var rule = rules[i];
      if (rule.type === 'media') {
        processRules(rule.rules); // recursive invocation while dealing with media queries
        continue;
      } else if (rule.type === 'keyframes') {
        processRules(rule.keyframes); // recursive invocation while dealing with keyframes
        continue;
      } else if(rule.type ! = ='rule'&& rule.type ! = ='keyframe') {
        continue;
      }

      var declarations = rule.declarations;
      for (var j = 0; j < declarations.length; j++) {
        var declaration = declarations[j];
        // need transform: declaration && has 'px'
        if (declaration.type === 'declaration' && pxRegExp.test(declaration.value)) {
          var nextDeclaration = rule.declarations[j + 1];
          if (nextDeclaration && nextDeclaration.type === 'comment') { // next next declaration is comment
            if (nextDeclaration.comment.trim() === config.keepComment) { // no transform
              declarations.splice(j + 1.1); // delete corresponding comment
              continue;
            } else if (nextDeclaration.comment.trim() === config.forcePxComment) { // force px
              declarations.splice(j + 1.1); // delete corresponding comment
            }
          }
          declaration.value = self._getCalcValue('px', declaration.value, dpr); // common transform
        }
      }
    }
  }

  processRules(astObj.stylesheet.rules);

  return css.stringify(astObj);
};

// generate rem version stylesheet
Px2rem.prototype.generateRem = function (cssText) {
  var self = this;
  var config = self.config;
  var astObj = css.parse(cssText);

  function processRules(rules, noDealPx) { // FIXME: keyframes do not support `force px` comment
    for (var i = 0; i < rules.length; i++) {
      var rule = rules[i];
      if (rule.type === 'media') {
        processRules(rule.rules); // recursive invocation while dealing with media queries
        continue;
      } else if (rule.type === 'keyframes') {
        processRules(rule.keyframes, true); // recursive invocation while dealing with keyframes
        continue;
      } else if(rule.type ! = ='rule'&& rule.type ! = ='keyframe') {
        continue;
      }

      if(! noDealPx) {// generate 3 new rules which has [data-dpr]
        var newRules = [];
        for (var dpr = 1; dpr <= 3; dpr++) {
          var newRule = {};
          newRule.type = rule.type;
          newRule.selectors = rule.selectors.map(function (sel) {
            return '[data-dpr="' + dpr + '"]+ sel; }); newRule.declarations = []; newRules.push(newRule); }}var declarations = rule.declarations;
      for (var j = 0; j < declarations.length; j++) {
        var declaration = declarations[j];
        // need transform: declaration && has 'px'
        if (declaration.type === 'declaration' && pxRegExp.test(declaration.value)) {
          var nextDeclaration = declarations[j + 1];
          if (nextDeclaration && nextDeclaration.type === 'comment') { // next next declaration is comment
            if (nextDeclaration.comment.trim() === config.forcePxComment) { // force px
              // do not transform `0px`
              if (declaration.value === '0px') {
                declaration.value = '0';
                declarations.splice(j + 1.1); // delete corresponding comment
                continue;
              }
              if(! noDealPx) {// generate 3 new declarations and put them in the new rules which has [data-dpr]
                for (var dpr = 1; dpr <= 3; dpr++) {
                  var newDeclaration = {};
                  extend(true, newDeclaration, declaration);
                  newDeclaration.value = self._getCalcValue('px', newDeclaration.value, dpr);
                  newRules[dpr - 1].declarations.push(newDeclaration);
                }
                declarations.splice(j, 2); // delete this rule and corresponding comment
                j--;
              } else { // FIXME: keyframes do not support `force px` comment
                declaration.value = self._getCalcValue('rem', declaration.value); // common transform
                declarations.splice(j + 1.1); // delete corresponding comment}}else if (nextDeclaration.comment.trim() === config.keepComment) { // no transform
              declarations.splice(j + 1.1); // delete corresponding comment
            } else {
              declaration.value = self._getCalcValue('rem', declaration.value); // common transform}}else {
            declaration.value = self._getCalcValue('rem', declaration.value); // common transform}}}// if the origin rule has no declarations, delete it
      if(! rules[i].declarations.length) { rules.splice(i,1);
        i--;
      }

      if(! noDealPx) {// add the new rules which contain declarations that are forced to use px
        if (newRules[0].declarations.length) {
          rules.splice(i + 1.0, newRules[0], newRules[1], newRules[2]);
          i += 3; // skip the added new rules
        }
      }
    }
  }

  processRules(astObj.stylesheet.rules);

  return css.stringify(astObj);
};

// get calculated value of px or rem
Px2rem.prototype._getCalcValue = function (type, value, dpr) {
  var config = this.config;
  var pxGlobalRegExp = new RegExp(pxRegExp.source, 'g');

  function getValue(val) {
    val = parseFloat(val.toFixed(config.remPrecision)); // control decimal precision of the calculated value
    return val == 0 ? val : val + type;
  }

  return value.replace(pxGlobalRegExp, function ($0, $1) {
    return type === 'px' ? getValue($1 * dpr / config.baseDpr) : getValue($1 / config.remUnit);
  });
};

module.exports = Px2rem;


Copy the code

5.4 usePx2rem. Js

let Px2rem = require('./px2rem');
let px2rem = new Px2rem({
    remUnit: 75.remPrecision: 8
});
let cssText = ` #root{ width:750px; height:750px; font-size:12px; /*px*/ border: 1px solid #ddd; /*no*/ } `;
let newCSS = px2rem.generateRem(cssText);
console.log(newCSS);

// #root {
// width: 10rem;
// height: 10rem;
// border: 1px solid #ddd;
// }

// [data-dpr="1"] #root {
// font-size: 6px;
// }

// [data-dpr="2"] #root {
// font-size: 12px;
// }

// [data-dpr="3"] #root {
// font-size: 18px;
// }
Copy the code

6. px2rem-loader.js

  • Loader-utils is a Webpack utility class

  • px2rem-loader

  • I’ll just write px, and when I compile it, it will go straight to REM

  • Add /no/ after px, it will not convert px, it will print the border as it is

  • Adding /px/ after px will generate three sets of code depending on the DPR used for fonts

7. lib-flexible

import './base.css';
import 'amfe-flexible/index.js';
Copy the code

8. Third-party framework style issues

  • If a third party component is already adapted for mobile, pX2REM converts to REM, causing the style to become very small

8.1 the index. Js

import './base.css'
//import 'amfe-flexible/index.js';
import 'lib-flexible/flexible.js'
import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import {Button} from 'antd';
ReactDOM.render(<div>I am a<Button type="primary">button</Button>
</div>.document.getElementById('root'));
Copy the code

8.2 webpack. Config. Js

{
    loader: 'px2rem-plus-loader'.//path.resolve(__dirname, 'loaders/px2rem-plus-loader.js'),
     options: {
                  remUnit: 75.remPrecision: 8.baseDpr:1.exclude:/antd\.css/}}Copy the code

Px2rem-plus-loader adds the exclude parameter to exclude unnecessary CSS files

The warehouse address