增强 vw/rem 移动端适配,适配宽屏、桌面端、三折屏
vw 和 rem 是两个神奇的 CSS 长度单位,认识它们之前,我一度认为招聘广告上的“像素级还原”是一种超能力,我想具备这种能力的人,一定专业过硬、有一双高分辨率的深邃大眼睛。
时间一晃,入坑两年,我敏捷地移动有点僵硬不算过硬的小手,将一些固定的 px 尺寸复制到代码,等待编译阶段的 vw/rem 转换,刷新浏览器的功夫,完美还原的界面映入眼前,我推了推眼镜,会心一笑。多亏了 vw 和 rem。
TLDR:极简配置 postcss-mobile-forever 增强 vw 的宽屏可访问性,限制视图最大宽度。
用 vw 和 rem 适配移动端视图的结果是一致的,都会得到一个随屏幕宽度变化的等比例伸缩视图。一般使用 postcss-px-to-viewport 做 vw 适配,使用 postcss-px2rem 配合 amfe-flexible 做 rem 适配。由于 rem 适配的原理是模仿 vw,所以后面关于适配的增强,一律使用 vw 适配做对比。
vw 适配有一些优点(同样 rem):
- 加速开发效率;
- 像素级还原设计稿;
- 也许更容易被自动生成。
但是 vw 适配也不完美,它引出了下面的问题:
- 开发过程机械化,所有元素是固定的宽高,不考虑响应式设计,例如不使用
flex
、grid
布局; - 助长不规范化,不关注细节,只关注页面还原度和开发速度,例如在非按钮元素的
<div>
上添加点击事件; - 桌面端难以访问,包容性降低。
前两个问题,也许要抛弃 vw、回归响应式布局才能解决,在日常开发时,我们要约束自己以开发桌面端的标准来开发移动端页面,改善这两个问题。
马克·吐温在掌握通过密西西比河的方法之后,发现这条河已经失去了它的美丽——总会丢掉一些东西,但是在艺术中比较不受重视的东西同时也被创造出来了。让我们不要再注意丢掉了什么,而是注意获得了什么。 ——《禅与摩托车的维修艺术》
后面,我们将关注第三点,介绍如何在保持现状(vw 适配)的情况下,尽可能提高不同屏幕的包容性,至少让我们在三折屏的时代能得到从前 1 倍的体验,而不是 1/3。
移动端 | 桌面端 |
---|---|
上面是一个页面分别在手机和电脑上展示的截图,可以看到左图移动端的右上角没有隐藏分享按钮,所以用户是允许(也应该允许)被分享到桌面端访问的,可惜,当用户准备享受大屏震撼的时候,真的被震撼了:他不知道这个页面的技术细节是神奇的 vw,也不知道他只能用鼠标小心地拖动浏览器窗口边缘,直到窗口窄得和手机一样,最崩溃的是,当他得意地按下了浏览器的缩小按钮,页面像冰冷的机器纹丝不动,浇灭了他的最后一点自信。
限制最大宽度
由于 vw 是视口单位,因此当屏幕变宽,vw 元素也会变大,无限变宽,无限变大。
现在假设在一张宽度 600 像素的设计图上,有一个宽度 60px
的元素,最终通过工具,它会被转为 10vw
。这个 10vw
元素是任意伸缩的,但是现在我希望,当屏幕宽度扩大到 700px
后,停止元素的放大。
出现了一堆枯燥的数字,不用担心,后面还有一波,请保持耐心。
首先计算 10vw
在宽 700 像素的屏幕上,应该是多少像素:60 * 700 / 600 = 70。通过最大宽度(700px
)和标准宽度(600px
)的比例,乘以元素在标准宽度时的尺寸(60px
),得到了元素的最大尺寸 70px
。
接着结合 CSS 函数:min(10vw, 70px)
,这样元素的宽度将被限制在 70px
以内,小于这个宽度时会以 10vw
等比伸缩。
除了上面的作为正数的尺寸,可能还会有用于方位的负数,负数的情况则使用 CSS 函数 max()
,下面的代码块是一个具体实现:
/** * 限制大小的 vw 转换 * @param {number} n * @param {number} idealWidth 标准/设计稿/理想宽度,设计稿的宽度 * @param {number} maxWidth 表示伸缩视图的最大宽度 */ function maxVw(n, idealWidth = 600, maxWidth = 700) { if (n === 0) return n; const vwN = Math.round(n * 100 / idealWidth); const maxN = Math.round(n * maxWidth / idealWidth); const cssF = n > 0 ? "min" : "max"; return `${cssF}(${vwN}vw, ${maxN}px)`; }
矫正视图外的元素位置
上一节提供的方法,包容了限制最大宽度尺寸的大部分情况,但是如果不忘像素级还原的❤️初心,就会找到一些漏洞。
下面是一个图例,移动端页面提供了 Top 按钮用于帮助用户返回顶部,按照上一节的方法,Top 按钮会出现在中央移动端视图之外、右边的空白区域中,而不是矫正回中央移动端视图的右下角。
假设 Top 按钮的样式是这样的:
.top { position: fixed; right: 30px; bottom: 30px; /* ... */ }
按照标准宽度 600、最大宽度 700,上面的 30px
都被转换成了 min(5vw, 35px)
,bottom
没错,但 right
需要矫正。
对照上面右图矫正过的状态,right
的值 = 右半边的空白长度 + Top 按钮到居中视图右边框的长度 = 桌面端视图的一半 - 视图中线到 Top 按钮的右边框长度。
沿着第二个等号后面的思路,fixed
定位时桌面端视图一半的尺寸即为 50%
,中线到 Top 按钮右边框的长度,分两种情况:
- 在屏幕宽度大于最大宽度 700 时,为 700 / 2 - 30 * 700 / 600,即为
315px
(其中 700 / 2 是中线到移动端右边框长度,30 * 700 / 600 是屏宽 600 时的30px
在屏宽 700 时的尺寸); - 在屏幕宽度小于最大宽度 700 时,为 (600 / 2 - 30) / 600,即为
45%
。
结合 calc()
、min()
和上面得到的 50%
、315px
、45%
,参考第二个等式,可以得到 right
的新值为 calc(50% - min(315px, 45%))
。当尺寸大于移动端视图的一半时,会出现负数的情况,这时使用 max()
替换 min()
。
上面的计算方法是一种符合预期的稳定的方法,另一种方法是强制设置移动端视图的根元素成为包含块,设置之后,
right: min(5vw, 35px)
将不再基于浏览器边框,而是基于移动端视图的边框。
postcss-mobile-forever
上面介绍了增强 vw 以包容移动端视图在宽屏展示的两个方面,除了介绍的这些,还有一点点边角情况,例如:
- 逻辑属性的判断和转换;
- 矫正
fixed
定位时和包含块宽度有关的vw
和%
尺寸; - 矫正
fixed
定位时left
与right
的vw
和%
尺寸; - 为移动端视图添加居中样式;
- 各种情况的判断和转换方法选择。
postcss-mobile-forever 是一个 PostCSS 插件,利用 mobile-forever 这些工作可以在编译阶段完成,上面举了那么多例子,汇总成一份 mobile-forever 配置就是:
{ "viewportWidth": 700, "appSelector": "#app", "maxDisplayWidth": 600 }
上面是 mobile-forever 用户使用最多的模式,max-vw-mode,此外还提供:
- mq-mode,media-query 媒体查询模式,生成可访问性更高的样式,同样限制最大宽度,但是避免了
vw
带来的无法通过桌面端浏览器缩放按钮缩放页面的问题,也提供了更高的浏览器兼容性; - vw-mode,朴素地将固定尺寸转为 vw 伸缩页面,不限制最大宽度。
postcss-mobile-forever 相比 postcss-px-to-viewport 提供了更多的模式,包容了宽屏展示,相比 postcss-px2rem,无需加载 JavaScript,不为项目引入复杂度,即使用户禁用了 js,也能正常展示页面。
scale-view 提供运行时的转换方法。
优秀的模版
postcss-mobile-forever 的推广离不开开源模版的支持、尝试与反馈,下面是这些优秀的模版,它们为开发者提供了更多元的选项,为用户提供了更包容的产品:
- vue3-vant-mobile,一个基于 Vue 3 生态系统的移动 web 应用模板,帮助你快速完成业务开发。【查看在线演示】
- vue3-vant4-mobile,基于Vue3.4、Vite5、Vant4、Pinia、Typescript、UnoCSS等主流技术开发,集成 Dark Mode(暗黑)模式和系统主题色,且持久化保存,集成 Mock 数据,包括登录/注册/找回/keep-alive/Axios/useEcharts/IconSvg 等其他扩展。你可以在此之上直接开发你的业务代码!【查看在线演示】
- fantastic-mobile,一款自成一派的移动端 H5 框架,支持多款 UI 组件库,基于 Vue3。【查看在线演示】
增强后的 vw/rem 看起来已经完成了适配宽屏的任务,不过回想最初的另外两个问题,机械化的开发过程与不规范化的开发细节,没有解决。作为一名专业的前端开发工程师,请考虑使用响应式设计开发你的下一个项目,为三折屏带来 3 倍的用户体验吧。