前言
做移动端开发经常会用到 lib-flexible + postcss-pxtorem
这两个插件,对于他的使用我们都已经比较熟悉了,这里就不在介绍使用方法了。本文内容主要是介绍 lib-flexible
的源码内容,以及他的替代方法viewport
的使用。
由于 viewport
单位得到众多浏览器的兼容,lib-flexible
这个过渡方案已经可以放弃使用,官方已经不再维护了,不管是现在的版本还是以前的版本,都存有一定的问题。建议大家开始使用vw的兼容方案来替代此方案。
flexible
先上一段 flexible2.0
版本的代码,解析我都写在代码的关键位置:
/**
* lib-flexible 2.0版本
* 此版本十分简约
*
* 添加注释 @lizhiyu
* 时间 2021-5-13
*
* 由于viewport单位得到众多浏览器的兼容,lib-flexible这个过渡方案已经可以放弃使用,不管是现在的版本还是以前的版本,都存有一定的问题。
* 建议大家开始使用vw的兼容方案来替代此方案。
*/
(function flexible (window, document) {
var docEl = document.documentElement
var dpr = window.devicePixelRatio || 1 // 当前显示设备的物理像素分辨率与 CSS 像素分辨率的比率
// adjust body font size
function setBodyFontSize () {
if (document.body) {
document.body.style.fontSize = (12 * dpr) + 'px'
}
else {
// 等页面的元素加载完之后再调用一下这个函数。因为浏览器执行代码是从上到下,当解析到script标签的时候,可能body还没构建好。所以需要考虑全面。
document.addEventListener('DOMContentLoaded', setBodyFontSize)
}
}
setBodyFontSize();
// set 1rem = viewWidth / 10
// 设置 html 元素的文字大小,因为 rem 是相对于根节点而定的
function setRemUnit () {
var rem = docEl.clientWidth / 10 // 除以10是为了方便计算
docEl.style.fontSize = rem + 'px' // 将html元素的fontsize设置为10分之1像素
}
setRemUnit()
// reset rem unit on page resize
// resize事件是 当页面尺寸大小发生变化的时候,要重新设置rem的大小
window.addEventListener('resize', setRemUnit)
// pageshow事件是 重新加载页面触发的事件
window.addEventListener('pageshow', function (e) {
// 如果是从缓存取过来的页面也重新设置一下rem的大小(主要是火狐浏览器有往返缓存机制,这个缓存保存了DOM和JS的状态)
if (e.persisted) {
setRemUnit()
}
})
// detect 0.5px supports
// 有些移动端的浏览器不支持0.5像素的写法
if (dpr >= 2) {
var fakeBody = document.createElement('body')
var testElement = document.createElement('div')
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
docEl.appendChild(fakeBody)
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines')
}
docEl.removeChild(fakeBody)
}
}(window, document))
代码块的注释基本上都写好了,这段代码比较简洁,我们现在用的基本上还是经典版 flexible0.3.2
。
下面我放上我已经写好注释的 flexible0.3.2
的源码:
/**
* lib-flexible 0.3.2经典版
* 目前都是使用这个版本开发
*
* 大部分页面 meta name="viewport" 的配置
* <meta name="viewport" content="width=device-width, viewport-fit=cover, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
*
* 添加注释 @lizhiyu
* 时间 2021-5-13
*/
(function(win, lib) {
var doc = win.document;
var docEl = doc.documentElement;
var metaEl = doc.querySelector('meta[name="viewport"]');
var flexibleEl = doc.querySelector('meta[name="flexible"]');
var dpr = 0;
var scale = 0;
var tid;
var flexible = lib.flexible || (lib.flexible = {});
// 如果有设置缩放比例,那么按照设置的比例显示
if (metaEl) {
console.warn('将根据已有的meta标签来设置缩放比例');
var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
if (match) {
scale = parseFloat(match[1]);
dpr = parseInt(1 / scale);
}
} else if (flexibleEl) {
var content = flexibleEl.getAttribute('content');
if (content) {
var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
if (initialDpr) {
dpr = parseFloat(initialDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
if (maximumDpr) {
dpr = parseFloat(maximumDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
}
}
// 默认的缩放比例
if (!dpr && !scale) {
var isAndroid = win.navigator.appVersion.match(/android/gi);
var isIPhone = win.navigator.appVersion.match(/iphone/gi);
var devicePixelRatio = win.devicePixelRatio;
if (isIPhone) {
// iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
dpr = 2;
} else {
dpr = 1;
}
} else {
// 其他设备下,仍旧使用1倍的方案
dpr = 1;
}
scale = 1 / dpr;
}
docEl.setAttribute('data-dpr', dpr); // html根标签上获取
// 没有设置<meta name="viewport">的会默认添加一个
if (!metaEl) {
metaEl = doc.createElement('meta');
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
if (docEl.firstElementChild) { // 添加到head
docEl.firstElementChild.appendChild(metaEl);
} else {
var wrap = doc.createElement('div');
wrap.appendChild(metaEl);
doc.write(wrap.innerHTML);
}
}
function refreshRem(){
var width = docEl.getBoundingClientRect().width; // getBoundingClientRect可以获取页面的宽高
if (width / dpr > 540) {
width = 540 * dpr; // 默认的适配最大宽度界限
}
var rem = width / 10; // 方便计算除以10
docEl.style.fontSize = rem + 'px'; // 设置根元素的fontsize,rem是基于根元素的fontsize来设定的
flexible.rem = win.rem = rem;
}
// 监听页面,窗口或框架被调整大小时修改页面的fontsize
win.addEventListener('resize', function() {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}, false);
// pageshow事件是 重新加载页面触发的事件
win.addEventListener('pageshow', function(e) {
// 如果是从缓存取过来的页面也重新设置一下rem的大小(主要是火狐浏览器有往返缓存机制,这个缓存保存了DOM和JS的状态)
if (e.persisted) {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
}, false);
if (doc.readyState === 'complete') { // 文档加载完成
doc.body.style.fontSize = 12 * dpr + 'px'; // 设置body里文字的基本fontsize
} else {
doc.addEventListener('DOMContentLoaded', function(e) { // 监听加载完成后调用
doc.body.style.fontSize = 12 * dpr + 'px';
}, false);
}
refreshRem();
// 提供的一些数据和方法
flexible.dpr = win.dpr = dpr;
flexible.refreshRem = refreshRem;
flexible.rem2px = function(d) {
var val = parseFloat(d) * this.rem; // 根据rem计算出px值
if (typeof d === 'string' && d.match(/rem$/)) {
val += 'px';
}
return val;
}
flexible.px2rem = function(d) {
var val = parseFloat(d) / this.rem; // 根据px计算出rem值
if (typeof d === 'string' && d.match(/px$/)) {
val += 'rem';
}
return val;
}
})(window, window['lib'] || (window['lib'] = {}));
与2.0版本相比,经典版的代码多一些,基本都是一些处理和默认赋值的方法,核心代码都是一样的。
viewport
这里我介绍一下viewport
的使用方法,viewport
配置起来很容易,只需要新增一个postcss.config.js
文件,然后下载yarn add -D # postcss-px-to-viewport-8-plugin
,这样在代码中写入的px
会自动转换成vw
单位,同样可以达到移动端适配的效果。
// postcss.config.js文件内容
module.exports = {
plugins: {
"# postcss-px-to-viewport-8-plugin": {
unitToConvert: "px", // 要转化的单位
viewportWidth: 375, // UI设计稿的宽度
unitPrecision: 6, // 转换后的精度,即小数点位数
propList: ["*"], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位,默认vw
selectorBlackList: ["wrap"], // 指定不转换为视窗单位的类名,
minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
replace: true, // 是否转换后直接更换属性值
exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配
landscape: false, // 是否处理横屏情况
},
},
};
px
会自动转换成vw
的效果:
<template>
<div class="about">
<h1>This is an about page</h1>
<div class="box">这是一个box</div>
</div>
</template>
<style scoped>
.box {
width: 200px;
height: 100px;
border: 1px solid black;
font-size: 16px;
}
</style>
总结
之前的项目我基本都是使用lib-flexible
开发,但是阅读其源码的时候看到作者的说明,所以我就去了解了viewport
适配方案,目前viewport
的浏览器兼容度已经达到92%以上,所以在日常开发使用基本已经没有什么问题,所以我后续的项目
应该都会使用viewport
适配方案来开发。
但是viewport
也有不足之处,vw
与vh
会在 pc 与移动端均产生效果,而不像flexible
只会转换一定宽度(dpr=1就是540px)以下设备的px
为rem
,因此若设计稿为移动端而生,全权使用vw
单位会使得页面在 pc 端出现字体过大等现象,需酌情处理,根据应用场景自行选择。