TL;DR
近期在做一个移动端的web网页,当中选用了vw自适应适配方案。然而从设计图标注的px转换到vw是个麻烦事,作为程序员的我很抗拒人工计算,因为那样做CSS代码的可读性会变低,而且编码效率也很低。经过实践我找出了以下几种解决方案,这里列举出来希望对同样患懒癌的你有些许帮助:
0. Sass/Less
Sass/Less是通过mixin或者function进行计算,把计算工作交给mixin或者function。这个方法我曾在Web移动端适配你应该了解得更多一些一文中提到过。
$ui-width: 750px;
@function px2vw($px) {
@return $px / $ui-width * 100vw;
}
#sidebar { width: px2vw(50px); }
它虽然实现了我们的需求,可读性还行,但是我并不建议采用它,因为:
- 通常我们会把函数写到公用的scss文件中,那么如果您采用的是模块化开发,则需要在每个模块文件中导入这个公用的scss文件;
- 原来的
*px
必须由px2vw(*px)
代替,代码量增加; - 如果有一天我不想采纳这种方法了,修改起来很麻烦,扩展性低,不易维护;
- 不能转换内联样式单位。
1. Postcss plugin
这是当下比较流行的解决方案,npm上有很多转换CSS单位的postcss插件,如postcss-px-to-viewport, postcss-plugin-px2rem等。它们是通过遍历CSS属性,匹配到*px
时就进行换算和替换。最初我选的是postcss-px-to-viewport插件,我还对它进行了扩展支持转换rem,这里简单介绍一下:
// convert.js
module.exports = postcss.plugin('postcss-unit-convert', function (options) {
const opts = Object.assign({}, options)
const pxReplace = createPxReplace(opts.UIWidth, opts.minPixelValue, opts.unitPrecision, opts.targetUnit, opts.rem)
return function (root) {
// 如果目标转换单位是rem,则设置html跟节点的字体大小为options.rem
if (opts.targetUnit === 'rem' && opts.rem) {
css.append(`html{ font-size: ${opts.rem}px}`)
}
// 遍历css属性
root.walkDecls(function (decl) {
// 如果当前属性不包含px,直接跳过
if (decl.value.indexOf('px') === -1) return
// 如果options.fontUnit为px则font-size属性直接跳过,这是为了不转换font-size的单位
if (opts.fontUnit === 'px' && decl.prop === 'font-size') return
// 如果当前容器包含黑名单的容器名称,则直接跳过
if (blacklistedSelector(opts.selectorBlackList, decl.parent.selector)) return
// 转换及替换 关键!
decl.value = decl.value.replace(pxRegex, pxReplace)
})
}
})
/*
* createPxReplace 根据目标单位返回替换规则
* 如果px前没有数值,不替换
* 如果数值小于等于option.minPixelValue,不替换
*/
function createPxReplace (UIWidth, minPixelValue, unitPrecision, targetUnit, rem) {
return function (m, $1) {
if (!$1) return m
const pixels = parseFloat($1)
if (pixels <= minPixelValue) return m
if (targetUnit === 'vw') return px2vw(pixels, UIWidth).toFixed(unitPrecision) + targetUnit
if (targetUnit === 'rem') return px2rem(pixels