1.核心概念
1.1 设备物理像素
- 是一个物理概念,是显示器显示的最小物理单位,设备屏幕的物理像素,任何设备的物理像素的数量都是固定的
- iPhone6的像素分辨率是750*1334
1.2设备独立像素
-
是一个逻辑概念,是为web开发者创造的,在CSS和javascript中使用的一个抽象的层,用于向CSS中的宽度、高度等提供信息
-
iPhone6的逻辑分辨率是375*667
-
iPhone6:window.screen.width=375,window.screen.height=667
-
px是一个相对单位,相对的是设备像素(device pixel)
像素,又称画素,是图像显示的基本单位,译自英文“pixel”,pix是英语单词picture的常用简写,加上英语单词“元素”element,就得到pixel,故“像素”表示“图像元素”之意,有时亦被称为pel(picture element) 像素是网页布局的基础。一个像素就是计算机能够显示一种特定颜色的最小区域。当设备尺寸相同但像素变得更密集时,屏幕能显示的画面的过渡更细致,网站看起来更明快。
每一个CSS声明和几乎所有的javascript属性都使用CSS像素,因此实际上从来用不上设备像素 ,唯一的例外是screen.width/height
1.3 设备像素比
-
DPR(设备像素比) = 设备像素/CSS像素
-
设备像素比 window.devicePixelRatio
在早先的移动设备中,并没有DPR的概念。随着技术的发展,移动设备的屏幕像素密度越来越高。从iphone4开始,苹果公司推出了所谓的retina视网膜屏幕。之所以叫做视网膜屏幕,是因为屏幕的PPI(屏幕像素密度)太高,人的视网膜无法分辨出屏幕上的像素点。iphone4的分辨率提高了一倍,但屏幕尺寸却没有变化,这意味着同样大小的屏幕上,像素多了一倍,于是DPR = 2 实际上,此时的CSS像素对应着以后要提到的理想视口,其对应的javascript属性是screen.width/screen.height而对于设备像素比DPR也有对应的javascript属性window.devicePixelRatio 以iphone5为例,iphone5的CSS像素为
320px*568px
,DPR是2,所以其设备像素为640px*1136px
1.4 移动端适配
- 一般由设计师按照设备像素(device pixel)为单位制作设计稿
- 然后由前端工程师参照设备像素比(device pixel ratio)进行换算
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 install webpack webpack-cli html-webpack-plugin style-loader css-loader amfe-flexible px2rem-loader --save-dev
2.2 src\index.js
import './base.css'
2.3 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%);
}
2.4 src\index.html
<!DOCTYPE html>
<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>
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
}
),
]
};
2.6 package.json
{
"scripts": {
"build": "webpack"
}
}
3.loader
- loader 用于对模块的源代码进行转换
- loader 可以使你在 import 模块时预处理文件
- loader 可以将文件从不同的语言(如TypeScript)转换为 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;
4. 使用自定义loader
webpack.config.js 中的配置
resolveLoader: {
alias: {
"px2rem-plus-loader": path.resolve('./loaders/px2rem-plus-loader.js')
},
modules: [path.resolve('./loaders'), 'node_modules']
},
5.css
5.1 AST
-
astexplorer
-
JavaScript Parser可以把源代码转化为一颗抽象语法树(AST),这颗树定义了代码的结构
5.2 AST工作流
- Parse(解析) 将源代码转换成抽象语法树,树上有很多的estree节点
- Transform(转换) 对抽象语法树进行转换
- Generate(代码生成) 将上一步经过转换过的抽象语法树生成新的代码
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;
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;
// }
6. px2rem-loader.js
-
loader-utils是一个webpack工具类
-
px2rem-loader
-
直接写px,编译后会直接转化成rem
-
在px后面添加/no/,不会转化px,会原样输出 一般border需用这个
-
在px后面添加/px/,会根据dpr的不同,生成三套代码 一般字体需用这个
7. lib-flexible
import './base.css';
import 'amfe-flexible/index.js';
8. 第三方框架样式问题
- 如果第三方组件已经是为移动端做了适配,px2rem又转成了rem就导致其样式变的很小
8.1 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>
我是<Button type="primary">按钮</Button>
</div>, document.getElementById('root'));
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/
}
}
px2rem-plus-loader 增加 exclude参数排除不需要处理的css文件
仓库地址
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!