移动端适配之vw
移动端兼容解决方案 —— vw
1vw 即值为视口宽度的 1%
meta可配置视窗大小和初始缩放比例,目前仅作用于移动设备;见meta标签作用
安装依赖
- npm install postcss-px-to-viewport-update -D
最新版postcss-px-to-viewport未发布到npm库,
include
配置不生效,见issue
TIP
如果只需要转换某一个文件夹下的css为vw,只需要配置include属性就ok
如果include&exclude都不配置,则转换所有文件为vw
⚠需要注意windows/linux下path路径中的反斜杠符号
nuxt.config.js 配置(nuxt2,nuxt3暂未测试)
WARN⚠ Please use build.postcss in your nuxt.config.js instead of an external config file. Support for such files will be removed in Nuxt 3 as they remove all defaults set by Nuxt and can cause severe problems with features like alias resolving inside your CSS.
nuxt2项目配置
nuxt2项目中postcss.config.js配置在项目根目录未生效,需配置在nuxt.config.js中build属性中
build: {
// ...
postcss: {
plugins: {
'postcss-px-to-viewport-update': {
unitToConvert: 'px', // 需要转换的单位,默认为"px"
viewportWidth: 375, // 视窗的宽度,对应的是我们设计稿的宽度
viewportHeight: 667, // 视窗的高度,根据375设备的宽度来指定,一般指定667,也可以不配置
unitPrecision: 13, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
propList: ['*'], // 能转化为vw的属性列表
viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw
fontViewportUnit: 'vw', // 字体使用的视口单位
selectorBlackList: ['.ignore-', 'van'], // 指定不转换为视窗单位的类,可以自定义,可以无限添加
minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
mediaQuery: false, // 允许在媒体查询中转换`px`
replace: true, // 是否直接更换属性值,而不添加备用属性
include: [/pages\/?webapp/], // 兼容windows,反斜杠需考虑不匹配
<!-- 👆正则转义问题:https://chaoszhu.com/front-end/regexp/%E5%8C%B9%E9%85%8D%E5%AD%97%E7%AC%A6.html#%E8%BD%AC%E4%B9%89 -->
landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
landscapeUnit: 'vw', // 横屏时使用的单位
landscapeWidth: 1440 // 横屏时使用的视口宽度
}
}
}
}
vue-cli项目配置
// 安装依赖后在根目录新增postcss.config.js即可
module.exports = {
plugins: {
'postcss-px-to-viewport-update': {
unitToConvert: 'px', // 需要转换的单位,默认为"px"
viewportWidth: 375, // 视窗的宽度,对应的是我们设计稿的宽度
viewportHeight: 667, // 视窗的高度,根据375设备的宽度来指定,一般指定667,也可以不配置
unitPrecision: 13, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
propList: ['*'], // 能转化为vw的属性列表
viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw
fontViewportUnit: 'vw', // 字体使用的视口单位
selectorBlackList: ['.ignore-', 'van'], // 指定不转换为视窗单位的类,可以自定义,可以无限添加
minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
mediaQuery: false, // 允许在媒体查询中转换`px`
replace: true, // 是否直接更换属性值,而不添加备用属性
include: /home.vue/, // 单个文件,或者配置成多个:[/home.vue/, /detail.vue/]
landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
landscapeUnit: 'vw', // 横屏时使用的单位
landscapeWidth: 1440 // 横屏时使用的视口宽度
}
}
}
内联样式px转换为vw
TIP
postcss-px-to-viewport只会转换SFC(单文件组件)中的style中的px单位,内联(动态)样式需要自己计算.
计算方法
现设计稿为375*812,即375px=100vw, 则内联样式长度单位px需除以3.75转换为vw单位
假如设计稿一元素width为37.5px,则计算:{'width: 37.5/375*100 + 'vw'} , 即width:10vw;
转换原理
默认值
// 默认值
var defaults = {
unitToConvert: 'px',
viewportWidth: 320,
viewportHeight: 568, // not now used; TODO: need for different units and math for different properties
unitPrecision: 5,
viewportUnit: 'vw',
fontViewportUnit: 'vw', // vmin is more suitable.
selectorBlackList: [],
propList: ['*'],
minPixelValue: 1,
mediaQuery: false,
replace: true,
landscape: false,
landscapeUnit: 'vw',
landscapeWidth: 568
}
include&exclude处理
// ...
checkRegExpOrArray(opts, 'exclude'); // 检查exclude&include参数是否符合合规
checkRegExpOrArray(opts, 'include');
// 使用postcss提供的api walkRules遍历文件
css.walkRules(function (rule) {
// 【在windows目录中path分隔为反斜杠,在传入include/exclude参数需注意】
var file = rule.source && rule.source.input.file; // 文件路径, 例如xxx.vue xxx.css
// 处理include
if (opts.include && file) {
if (Object.prototype.toString.call(opts.include) === '[object RegExp]') {
// (不符合)include正则条件的直接略过,所以一般情况只需传入include参数即可
if (!opts.include.test(file)) return;
} else if (Object.prototype.toString.call(opts.include) === '[object Array]') {
var flag = false;
// include为数组时,遍历每一条正则(有一条符合即可往下走)
for (var i = 0; i < opts.include.length; i++) {
if (opts.include[i].test(file)) {
console.log('已转换vw', file)
flag = true;
break;
}
}
if (!flag) return;
}
}
// 处理exclude
if (opts.exclude && file) {
if (Object.prototype.toString.call(opts.exclude) === '[object RegExp]') {
// (符合)exclude正则条件的直接略过,所以一般情况只需传入include参数即可
if (opts.exclude.test(file)) return;
} else if (Object.prototype.toString.call(opts.exclude) === '[object Array]') {
for (var i = 0; i < opts.exclude.length; i++) {
if (opts.exclude[i].test(file)) return;
}
}
}
// 黑名单前缀的class忽略
if (blacklistedSelector(opts.selectorBlackList, rule.selector)) return;
// ...
rule.walkDecls(function(decl, i) {
// ...
var unit = opts.landscapeUnit; // 需要转换的单位 默认:px
var size = opts.viewportWidth; // 设计稿宽度
// ...
// 计算value,并加上vw,赋值给css属性,完成替换
var value = decl.value.replace(pxRegex, createPxReplace(opts, unit, size));
if (opts.replace) {
decl.value = value;
} else {
decl.parent.insertAfter(i, decl.clone({ value: value }));
}
});
}
计算结果函数:createPxReplace
function createPxReplace(opts, viewportUnit, viewportSize) {
return function (m, $1) {
if (!$1) return m;
var pixels = parseFloat($1);
// px小于最小值(默认1px)的设定时,不进行转换
if (pixels <= opts.minPixelValue) return m;
// 设计稿某元素宽度像素pixels / 设计稿总宽度大小(例如375) * 100
var parsedVal = toFixed((pixels / viewportSize * 100), opts.unitPrecision);
return parsedVal === 0 ? '0' : parsedVal + viewportUnit;
};
}