1、核心概念

1-1、物理像素

  • 一个物理概念,显示器显示的最小物理单位
  • iphone分辨率750*1334
  • px是一个相对单位,相对的是设备像素

1-2、设备独立像素

1-3、设备像素比

  • DPR设备像素比 = 设备像素/CSS像素

1-4、移动端适配

  • 一般设计师按照设备像素为单位设计
  • 前端工程师根据设备像素比进行换算

1-4-1、rem

  • 参照跟元素的字体大小
  • 适配就是让跟元素的字体大小根据分辨率进行动态改变
  • px2rem-loader

1-4-2、vw和vh

  • 参照的是viewport视口
  • vw参照的是视口的宽度(1vw = 视口宽度/100)
  • vh参照的是视口的宽度(1vh = 视口宽度/100)
  • iPhone6 1vw = 3.75px
  • postcss-px-to-viewport

2、px2rem-loader实战

2-1、安装

npm i webpack webpack-cli style-loader css-loader px2rem-loader html-webpack-plugin css -D

  • amfe-flexible

    npm i amfe-flexible -D 自动转化font-size: 37.5px; 核心是设置根元素的大小

  1. // 导入
  2. import 'amfe-flexible'

image.png

src/index.js

  1. // import 'px2rem-loader!./index.css' // 使用内联loader 单独设置这
  2. import './index.css'
  3. // import 'amfe-flexible'
  4. import '../amfe.flexible.js'
  5. import React from 'react'
  6. import ReactDOM from 'react-dom'
  7. import 'antd/dist/antd.css'
  8. import { Button } from 'antd'
  9. // antd已经做了转换,再次转换会有问题
  10. ReactDOM.render(<Button type='primary'>按钮</Button>,document.getElementById('root'))

src/index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <div id="root">root</div>
  11. </body>
  12. <script src="./main.js"></script>
  13. </html>

webpack.config.js

  1. const path = require("path");
  2. const HtmlWebPackPlugin = require("html-webpack-plugin");
  3. const px2rem2LoaderPath = path.resolve(__dirname, "loaders/px2rem2-loader.js");
  4. module.exports = {
  5. entry: "./src/index.js",
  6. mode: "development",
  7. devtool: false,
  8. output: {
  9. path: path.resolve(__dirname, "dist"),
  10. filename: "main.js",
  11. },
  12. resolveLoader: {
  13. // alias:{
  14. // 'px2rem2-loader': px2rem2LoaderPath
  15. // },
  16. modules: ["loaders", "node_modules"], // 找loaders文件夹 没有在找node_module 三种方式引用
  17. },
  18. module: {
  19. rules: [
  20. {
  21. test: /\.js$/,
  22. use: [
  23. {
  24. // babel-core @babel/preset-env @babel/preset-react
  25. loader: "babel-loader",
  26. options: {
  27. presets: [
  28. "@babel/preset-env", //=> ES6 转成 ES5
  29. "@babel/preset-react", //=> 支持 React
  30. ],
  31. },
  32. },
  33. ],
  34. include: path.resolve(__dirname, "src"),
  35. exclude: /node_modules/,
  36. },
  37. {
  38. test: /\.css$/,
  39. use: [
  40. "style-loader",
  41. "css-loader",
  42. {
  43. // loader: "px2rem-loader", // 这种找不到 要设置 resolveLoader 的别名
  44. loader: px2rem2LoaderPath, // 可以直接放一个绝对路径
  45. options: {
  46. // 1rem 设计为设计稿 / 10
  47. remUnit: 75,
  48. remPrecision: 8, // 8位小数
  49. exclude: /antd.css/
  50. },
  51. },
  52. ],
  53. },
  54. ],
  55. },
  56. plugins: [
  57. new HtmlWebPackPlugin({
  58. template: "./src/index.html",
  59. }),
  60. ],
  61. };
  62. // 750px =》 375
  63. // 图片宽75px =》 37.5

3、实现px2rem-loader

px2rem2-loader

  1. /**
  2. * parser转化成AST语法树
  3. * 1. 分词
  4. * 2. 语法分析、
  5. *
  6. */
  7. // let Px2rem = require("px2rem");
  8. let Px2rem = require("./pm2rem");
  9. function loader(source) {
  10. // 安装了webpack后 就会有一个这个工具类 通过它可以获取loader,里面配置的参数对象
  11. // 通过getOptions方法可以获得webpack.config.js中设置的options
  12. let loaderUtils = require("loader-utils");
  13. // console.log(source);
  14. let options = loaderUtils.getOptions(this);
  15. console.log(options,'options...')
  16. // let options = loaderUtils.getOptions({ remUnit: 75, remPrecision: 8 });
  17. // let options = { remUnit: 75, remPrecision: 8 }
  18. let px2rem = new Px2rem(options);
  19. let targetSource = px2rem.generateRem(source); // 生成rem
  20. return targetSource;
  21. }
  22. module.exports = loader;
  23. // let source = `
  24. // #root{
  25. // width: 750px;
  26. // height: 750px;
  27. // }
  28. // `;
  29. // loader(source);

pm2rem.js

  1. /**
  2. * 核心就是将css转换成rem
  3. * 靠的就是CSS抽象语法树
  4. */
  5. const css = require("css");
  6. const pxRegExp = /\b(\d+(\.\d+)?)px\b/;
  7. class Px2rem {
  8. constructor(config) {
  9. this.config = config;
  10. }
  11. generateRem(cssText) {
  12. let self = this; // 缓存this
  13. function processRules(rules) {
  14. console.log(rules,'ssssssss')
  15. for (let i = 0; i < rules.length; i++) {
  16. let rule = rules[i];
  17. let declarations = rule.declarations;
  18. for (let j = 0; j < declarations.length; j++) {
  19. let declaration = declarations[j];
  20. if (
  21. declaration.type === "declaration" &&
  22. pxRegExp.test(declaration.value)
  23. ) {
  24. declaration.value = self._getCalcValue("rem", declaration.value);
  25. }
  26. }
  27. }
  28. }
  29. var astObj = css.parse(cssText); // 解析css AST
  30. // console.log(JSON.stringify(astObj, {}, 2));
  31. console.log(astObj.stylesheet,'///')
  32. processRules(astObj.stylesheet.rules);
  33. let newCSSText = css.stringify(astObj); // 处理完后 在转换回来
  34. // console.log(newCSSText, "newCSSText");
  35. return newCSSText;
  36. }
  37. _getCalcValue(type, value) {
  38. let { remUnit, remPrecision } = this.config;
  39. return value.replace(pxRegExp, (_, $1) => {
  40. // console.log($1);
  41. // console.log(this.config);
  42. let val = parseFloat($1) / remUnit.toFixed(remPrecision);
  43. return val + type;
  44. });
  45. }
  46. }
  47. module.exports = Px2rem;

AST工作流

  • Parse解析,江原道吗转换成抽象语法树,时尚很多estree节点
  • Transform对臭显语法书进行转换
  • Generate(代码生成)将上一步的结果转换成新的代码

在线语法转换: astexpoler

lib-flexible

  1. let docEl = document.documentElement;
  2. let dpr = window.devicePixelRatio || 1
  3. // https://github.com/amfe/lib-flexible/blob/2.0/index.js
  4. function setRemUnit(){
  5. // 750情况下 750px
  6. // Iphone 375px
  7. if(document.body){
  8. document.body.style.fontSize = (12 * dpr) + 'px'
  9. }
  10. let rem = docEl.clientWidth / 10;
  11. docEl.style.fontSize = rem + 'px'
  12. }
  13. setRemUnit()
  14. window.addEventListener('resize', setRemUnit)
  • 第三方库以及做了适配 在使用原声的px2rem-loader会造成样式很小,原声库无法改变这个问题,只能自己实现一个

4、第三方框架产生的样式问题

原因是第三方做了适配移动端,又被处理了一次导致了问题
image.png

解决方案

方案一:

手写px2rem2LoaderPath , 增加一个exclude: /antd.css/
webpack.config.js

  1. {
  2. test: /\.css$/,
  3. use: [
  4. "style-loader",
  5. "css-loader",
  6. {
  7. // loader: "px2rem-loader",
  8. // loader: "px2rem-loader", // 这种找不到 要设置 resolveLoader 的别名
  9. loader: px2rem2LoaderPath, // 可以直接放一个绝对路径
  10. options: {
  11. // 1rem 设计为设计稿 / 10
  12. remUnit: 75,
  13. remPrecision: 8, // 8位小数
  14. exclude: /antd.css/
  15. },
  16. },
  17. ],
  18. },
  1. // px2rem2-loader.js
  2. /**
  3. * parser转化成AST语法树
  4. * 1. 分词
  5. * 2. 语法分析、
  6. *
  7. */
  8. // let Px2rem = require("px2rem");
  9. let Px2rem = require("./pm2rem");
  10. function loader(source) {
  11. // 安装了webpack后 就会有一个这个工具类 通过它可以获取loader,里面配置的参数对象
  12. // 通过getOptions方法可以获得webpack.config.js中设置的options
  13. let loaderUtils = require("loader-utils");
  14. let options = loaderUtils.getOptions(this);
  15. console.log(this.resource,'resource+++');
  16. if(options.exclude&&options.exclude.test(this.resource)){
  17. return source; // 不转换,直接返回
  18. }
  19. let px2rem = new Px2rem(options);
  20. let targetSource = px2rem.generateRem(source); // 生成rem
  21. console.log(targetSource);
  22. return targetSource;
  23. }
  24. module.exports = loader;
  25. // let source = `
  26. // #root{
  27. // width: 750px;
  28. // height: 750px;
  29. // }
  30. // `;
  31. // loader(source);

方案二、css和antd.css分别去处理

  1. {
  2. test: /\.css$/,
  3. exclude:/antd\.css$/,
  4. use: [
  5. "style-loader",
  6. "css-loader",
  7. {
  8. loader: "px2rem-loader",
  9. options: {
  10. // 1rem 设计为设计稿 / 10
  11. remUnit: 75,
  12. remPrecision: 8
  13. },
  14. },
  15. ],
  16. },
  17. {
  18. test: /antd\.css$/,
  19. use: [
  20. "style-loader",
  21. "css-loader"
  22. ],
  23. },

方案三 内联loader

index.js

  1. // import 'px2rem-loader!./index.css' // 使用内联loader 单独设置这