最近撸项目时,需求只说了做pc端的自适应项目,结果最后验收时,领导还要在大屏看效果,而且大屏还好几种,有一种是地铁站的那种广告屏,还有一种是类似天猫双十一那种巨屏,可想而知,这种普通的pc端项目放上去是什么效果。熬夜撸代码,终于在头发没剩几根时,想出来了一种pc端的自适应解决方案,通杀pc端的自适应适配问题
首先大致说下,px,rem,em这几个单位。
CSS 像素(CSS Pixel):
又称为虚拟像素、设备独立像素或逻辑像素,也可以理解为直觉像素。CSS 像素是 Web 编程的概念,指的是 CSS 样式代码中使用的逻辑像素。比如 iPhone 6 的 CSS 像素数为 375 x 667px。
虚拟像素,可以理解为“直觉”像素,CSS和JS使用的抽象单位,浏览器内的一切长度都是以CSS像素为单位的,CSS像素的单位是px。
在CSS规范中,长度单位可以分为两类,绝对(absolute)单位以及相对(relative)单位。px是一个相对单位,相对的是设备像素(device pixel)。
在同样一个设备上,每1个CSS像素所代表的物理像素是可以变化的(即CSS像素的第一方面的相对性);
在不同的设备之间,每1个CSS像素所代表的物理像素是可以变化的(即CSS像素的第二方面的相对性);
px实际是pixel(像素)的缩写,它是图像显示的基本单元,既不是一个确定的物理量,也不是一个点或者小方块,而是一个抽象概念。所以在谈论像素时一定要清楚它的上下文!一定要清楚它的上下文!一定要清楚它的上下文!
举个例子来理解css像素的相对性
假设我们用PC浏览器打开一个页面,浏览器此时的宽度为800px,页面上同时有一个400px宽的块级元素容器。很明显此时块状容器应该占页面的一半。
但如果我们把页面放大(通过“Ctrl键”加上“+号键”),放大为200%,也就是原来的两倍。此时块状容器则横向占满了整个浏览器。
吊诡的是此时我们既没有调整浏览器窗口大小,也没有改变块状元素的css宽度,但是它看上去却变大了一倍——这是因为我们把CSS像素放大为了原来的两倍。
CSS像素与屏幕像素1:1同样大小时:
CSS像素(黑色边框)开始被拉伸,此时1个CSS像素大于1个屏幕像素
也就是说默认情况下一个CSS像素应该是等于一个物理像素的宽度的,但是浏览器的放大操作让一个CSS像素等于了两个设备像素宽度。在后面你会看到更复杂的情况,在高PPI的设备上,CSS像素甚至在默认状态下就相当于多个物理像素的尺寸。
从上面的例子可以看出,CSS像素从来都只是一个相对值。
相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸。
由于浏览器的默认字体大小是 16px,所以未经调整默认字体大小的浏览器都符合: 1em = 16px。
em 会继承父级元素的字体大小。由此,只需要改变父元素的字体大小,就可以同步放大或缩小子元素的字体。
但是也因此需要注意几点:
1、body 选择器中声明 Font-size=62.5% (10 ÷ 16 × 100% = 62.5%);
2、将你的原来的 px 数值除以 10,然后换上 em 作为单位;
3、重新计算那些被放大的字体的 em 数值。避免字体大小的重复声明。
由于 em 存在对父元素继承的问题,当改变字体大小时涉及的继承关系就变得复杂起来。
rem 是相对于根元素 字体尺寸的大小。如 文本大小设为 font-size: 10px,则 1rem = 10px。使用 rem 设置字体则简单了很多。
移动设备的宽度是各种各样的,每个设备的dpr也不同,换句话说就是不同设备每一行的物理像素数不同,能显示的css的px数也不同,如果我们写死px的话,那么后果就是同样的px,在不同设备中显示的行数不同,这样整个排版就乱了,想想有啥解决的思路没?
分析一下造成显示效果不同的原因就是设备宽度不同,你可能会问,那dpr呢,其实与dpr一点关系都没有,想象一下2个宽度为1000个物理像素的设备,一个dpr为1,一个dpr为2,那么在我们看来不过一个是1000px,一个是500px而已,在这里我们感知不到dpr。那么设备宽度不同怎么做适配呢,其实很容易的会想到,每个设备每行显示的px数不同,你写死px数的话,那肯定显示的效果不一样,所以,不能写死,要动态的计算。对,实际上也是这么解决的,那怎么计算呢,很简单,你把一个设备的样式写好了,其他的根据设备的宽度(px数)的比,来动态计算就行了。
rem就是解决这个问题的,rem不是具体的px,rem具体显示多少像素,是根据根元素的font-size来计算的,比如说你设置了1.2rem,根元素的font-size是100px,那么这个元素动态算出来的px数就是120px。不同宽度,设置不同px,这样就可以适配所有宽度的设备了。
如下图所示,假设这么一种情况,如果pc端的实际屏幕宽度是1920px,ui给的设计稿宽度也是1920px,那么就意味着你在设计稿内量的1px就等于pc端元素实际的1px,有刚好pc端默认1rem等于16px,所以ui设计稿内量出的px除以16就是我们要的rem单位了,但是1rem等于16px比较难于换算,所以我们人为规定下,pc端1rem等于100px,这个时候就可以得到如图下的等式。
pc端实际宽度 / 设计稿设计宽度 = pc端元素实际宽度 / 设计稿设计宽度 = 1rem / 100px
换成代码就是
html.style.fontSize = (document.documentElement || document.body / designWidth) * 100 + 'px'
这样就可以设置好根节点的font-size啦,但是这样也有一个问题,pc端基本上都有一个最小宽度,我们这么写就会出现根节点继续缩小的情况。 何况万一pc端在限制了最大宽度呢?所以这也是有一丢丢问题的。
有了上面的铺垫,我们就来写通杀的解决方案。这个是vue3.x的autoRem的插件,有需要的同学直接使用就好,别忘了use使用哦。
import {
App } from 'vue';
interface Options {
readonly designWidth?: number;
readonly maxWidth?: number;
readonly minWidth?: number;
readonly base?: number;
}
export default {
install(app: App<Element>, options: Options = {
}) {
function autoComputed({
designWidth = 1920,
maxWidth = 2 ** 64,
minWidth = 1366,
base = 100
}) {
const html = document.documentElement || document.body;
const limitMax = maxWidth / designWidth;
const limitMin = minWidth / designWidth;
const scale = document.body.offsetWidth / designWidth;
html.style.fontSize =
(
(scale < limitMin ? limitMin : scale > limitMax ? limitMax : scale) *
base
).toFixed(2) + 'px';
}
autoComputed(options);
window.addEventListener('resize', () => {
autoComputed(options);
});
}
};
vue3.x毕竟还没普及,所以依然有vue2.x的使用方式
/**
* @desc 自适应宽度
* @param {number} designWidth 设计稿宽带 默认 1920
* @param {number} maxWidth 最大宽度,缩大到一定程度不在缩大 默认 无限制
* @param {number} minWidth 最小宽度,缩小到一定程度不在缩小 默认 1366
* @param {number} base 基准值 默认 100(100px === 1rem)
*/
(({
designWidth = 1920, maxWidth = 2 ** 64, minWidth = 1366, base = 100 }) => {
autoComputed();
window.addEventListener('resize', autoComputed);
function autoComputed() {
const html = document.documentElement || document.body;
const limitMax = maxWidth / designWidth;
const limitMin = minWidth / designWidth;
const scale = document.body.offsetWidth / designWidth;
html.style.fontSize =
(
(scale < limitMin ? limitMin : scale > limitMax ? limitMax : scale) *
base
).toFixed(2) + 'px';
}
})({
minWidth: 1600 });
在入口文件那里使用一下,就可以动态的设置根节点的font-size了,而且只对rem单位有效,对px单位是无效的。满足了有的想使用px有的使用rem的需求。但是万一有ui库什么也需要等比呢?所以接下来推荐一个插件postcss-plugin-px2rem
,px转rem利器。
首先是安装
npm install postcss-plugin-px2rem -D
接着在vue.config.js
里面配置一下
css: {
loaderOptions: {
postcss: {
plugins: [
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('postcss-plugin-px2rem')({
// rootValue: 100, //换算基数, 默认100 ,这样的话把根标签的字体规定为1rem为50px,这样就可以从设计稿上量出多少个px直接在代码中写多上px了。
// unitPrecision: 5, //允许REM单位增长到的十进制数字。
// propWhiteList: [], //默认值是一个空数组,这意味着禁用白名单并启用所有属性。
// propBlackList: [], //黑名单
// exclude: /(node_module)/, //默认false,可以(reg)利用正则表达式排除某些文件夹的方法,例如/(node_module)/ 。如果想把前端UI框架内的px也转换成rem,请把此属性设为默认值
// selectorBlackList: [], //要忽略并保留为px的选择器
// ignoreIdentifier: false, //(boolean/string)忽略单个属性的方法,启用ignoreidentifier后,replace将自动设置为true。
// replace: true, // (布尔值)替换包含REM的规则,而不是添加回退。
mediaQuery: false, //(布尔值)允许在媒体查询中转换px。
minPixelValue: 3 //设置要替换的最小像素值(3px会被转rem)。 默认 0
})
]
}
}
}
配置好换算基数100px等于1rem,那么就可以直接量设计稿的px单位值愉快的画页面了。
CSS像素(px)、物理像素(pt)、rem、em、rpx