国际惯例,________。
rem 要说起来也不是啥新东西了,面试的时候总是被问 vw/em/rem 的区别,但仔细想想我似乎一次 rem 都没用过……趁着最近刚把公司项目里的样式方案整了下,赶紧来水一篇(bushi(今年一定做高产的博主
打了一套组合拳……然后开发体验极差
公司项目最初是以 720px 出稿的,为了能在不同的机型上有个相对正常的表现,用了大家的老朋友 postcss-px-to-viewport。
它能在编译时将 px 按照配置的设计稿基础款转换为 vw ,以 320px 的设计稿为例(你问我为啥是 320px,因为我抄的 postcss-px-to-viewport官网的例子……),书写的代码如下:
1 2 3 4 5 6 7 8
| .class { margin: -10px 0.5vh; padding: 5vmin 9.5px 1px; border: 3px solid black; border-bottom-width: 1px; font-size: 14px; line-height: 20px; }
|
在编译编译后的结果为:
1 2 3 4 5 6 7 8
| .class { margin: -3.125vw 0.5vh; padding: 5vmin 2.96875vw 1px; border: 0.9375vw solid black; border-bottom-width: 1px; font-size: 4.375vw; line-height: 6.25vw; }
|
这个方案的缺点是在 IPad 等设备中样式显的偏大,屏效低,被我们戏称为大号手机。
后来有一天,PM 们一拉数据,发现我们有不少用户都是平板设备,于是单独出了一期需求来适配。设计给出的整体方案思路其实就是定宽适配,体现在设计稿上的元素尺寸几乎就没啥区别,只是针对不同设备给定不同的设计稿基础尺寸,再有就是不同设备上少数几个地方的边距、背景图做了些调整。
| 设备类型 | 设计稿基础尺寸 |
|---|
| Phone | 720px |
| Pad 竖屏 | 1536px |
| Pad 横屏 | 2048px |
| 某个有侧边栏的履约产品 | 1888px |
众所周知 postcss-px-to-viewport 的转换是编译时进行的,尽管有 114514 个用户都希望维护者增加上支持配置多个基准值的 feature ,但结果却是事与愿违,该项目目前几乎处于停止维护的状态。因而之前的需求实际上以一种非常蹩脚的方式完成了适配,即使用 less 预处理器,在 postcss-px-to-viewport 转换过后再生成一套缩放的样式。
为了实现不同规格设备之间的尺寸换算,项目里封装了一堆 less mixin:
.attr: 屏宽大于 431px 时将目标尺寸缩放至原来的 0.6 倍以展示更多的内容;.attr-pad: 将 vw * 720px / 2048px 转换为基于 2048px 设计稿的尺寸;.attr-pad-tab: 将 vw * 720px / 1888px 转换为基于 1888px 设计稿的尺寸;.attr-pad-portrait: 将 vw * 720px / 1536px 转换为基于 1536px 设计稿的尺寸;
看起来有些绕,上面的 vw 实际为设计稿上的元素尺寸 / 720px,实际上换算的思路就是先复原设计稿上的尺寸,然后计算与对应设备的基准宽度的比例,之后使用 vw 作为单位即可。
而基于上述 less Mixin 方案编写一个多端适配的页面则需要至多书写 4 份代码,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| @import "~@/assets/less/common.less";
.article-wrapper { font-size: 40px; padding: 0 32px 148px; line-height: 72px; }
.article-wrapper-tablet-portrait { .attr-pad-portrait(--var-container-pad-pading, 624px); width: calc(100% - var(--var-container-pad-pading)); .attr-pad-portrait(font-size, 40px); .attr-pad-portrait(line-height, 72px); .attr-pad-portrait(padding, 0, 12px, 198px); }
.article-wrapper-tablet-landscape { .attr-pad(--var-container-pad-pading, 624px); width: calc(100% - var(--var-container-pad-pading)); .attr-pad(font-size, 40px); .attr-pad(line-height, 72px); .attr-pad-3(padding, 0, 12px, 198px); }
.article-wrapper-tablet-landscape-with-sidebar { .attr-pad-tab(--var-container-pad-pading, 624px); width: calc(100% - var(--var-container-pad-pading)); .attr-pad-tab(font-size, 40px); .attr-pad-tab(line-height, 72px); .attr-pad-tab-3(padding, 0, 12px, 198px); }
|
别觉得荒诞……就是从项目代码里摘抄出来的,实际上比这个长不知道多少倍
其中 article-wrapper-tablet-portrait/article-wrapper-tablet-landscape/reading-article-wrapper-tablet-landscape-with-sidebar 三个 css 类名仅仅是把 .article-wrapper 复制以后,将 px 替换为了使用对应 .attr Mixin 转化后的数值而已。另外眼见的朋友可能已经看出来了,受限于 less Mixin 灵活性的限制,里面还用 css 变量来做了一些曲线救国的操作……
之后在页面结构中需要通过客户端公参判断设备类型,并挂载上对应的类名来实现样式的兼容。
一套组合拳打下来我都快忘了自己在干啥了……
总而言之这个方案只能用辣眼睛来形容,经常出现一个类名里加了条样式其他的里面没加,或者有个数值修改了但某个类名忘改了之类的,维护成本极高。
终于用上 rem 了……
rem 是相对于 html 根元素 font-size 的大小来计算的,本身就具备运行时调整的能力,不依赖类似 postcss-px-to-viewport 这样编译时的行为。只需要在页面打开时根据设备类型在 html 根元素上按照比例换算 font-size 即可。
动态调整 font-size 的部分也没啥复杂的,随便糊一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| export enum EDeviceType { PHONE = "phone", TABLE_LANDSCAPE = "tabletLandscape", TABLE_LANDSCAPE_WITH_SIDEBAR = "tabletLandscapeWithSidebar", TABLE_PORTRAIT = "tabletPortrait", }
const sizes: Record<string, number> = { [EDeviceType.PHONE]: 720, [EDeviceType.TABLE_LANDSCAPE]: 2048, [EDeviceType.TABLE_LANDSCAPE_WITH_SIDEBAR]: 1888, [EDeviceType.TABLE_PORTRAIT]: 1536, };
export const autoRem = (device: EDeviceType): (() => void) => { const baseScreenWidth = sizes[device]; const docEl: HTMLElement = window.document.documentElement;
const resizeCall = () => { const clientWidth: number = docEl.clientWidth;
if (!clientWidth) { docEl.style.fontSize = 100 + "px"; } else { docEl.style.fontSize = 100 * (clientWidth / baseScreenWidth) + "px"; } }; let dpr: number = window.devicePixelRatio || 1;
resizeCall(); dpr = dpr >= 3 ? 3 : dpr >= 2 ? 2 : 1; docEl.setAttribute("data-dpr", `${dpr}`);
return resizeCall; };
|
之后在页面中,只需要按照设计稿的尺寸,将 px / 100 替换为 rem 即可(因为默认的 font-size 是 100px)。
1 2 3 4 5 6
| .article-wrapper { font-size: 0.4rem; padding: 0 0.32rem 1.48rem; line-height: 0.72rem; // ... other style }
|
由于页面挂载时已经根据比例修改了 font-size 了,rem 能够呈现出基于 720px/1536px/1888px/2048px 基础宽的尺寸以实现自适应。同时 rem 不会受到项目已有的 postcss-px-to-viewport 插件的影响,亦不需要担心对已有的代码造成影响。
之后如果需要针对设计稿中不同设备上的样式区别进行处理,直接按照之前的方式覆写即可,如:
1 2 3 4 5 6 7 8 9 10 11
| .article-wrapper { font-size: 0.4rem; padding: 0 0.32rem 1.48rem; line-height: 0.72rem; background: url(~@assets/images/bg@1x.png) no-repeat; // ... other style }
.article-wrapper-tablet { background: url(~@assets/images/bg@2x.png) no-repeat; }
|