前端处理用户设备缩放
随着硬件发展和前端技术进步,多数项目在开发之时并不需要考虑用户设备/浏览器默认缩放带来的视觉效果和设计稿不符合的情况。但是如果你的项目用户是笔记本电脑用户且页面内容比较多且紧凑,可能就需要考虑一下这个问题了。
首先笔记本电脑本身屏幕比较窄,当然这里说的是css逻辑像素,实际上现在新款笔记本电脑的物理像素/分辨率比一些台式机显示器还高,只是不同设备存在不同的 devicePixelRatio 导致设备逻辑像素实际并不那么大。同时,多数笔记本电脑出场默认自带缩放:
window:https://pic1.zhimg.com/80/v2-8eef670d8ca20d9de65d29e515853e9c_720w.jpg
mac:https://pic1.zhimg.com/80/v2-61f18906105a17729d9158626b0f6eb4_720w.jpg
这样会使得,css逻辑像素进一步减小。
因此,这类项目需要对这类情况做相应的处理。
当然这个要看实际情况,想知乎、淘宝、禅道等这类布局,两侧留白可以一定程度上缓解用户缩放问题,因此大可不必考虑相应处理。但是对于一些内容紧凑且多的项目页面,用户缩放简直就是灾难。同时产品UI都会咆哮,为什么设计稿能展示这么多内容,但是实际到用户电脑上只能展示一丁点,同时一些留白、间距等会特别显眼。
原因很简单:
用户设备实际可用css逻辑像素宽度和设计稿不符,大多都是比设计稿小的。
以我的13寸mac pro为例,默认缩放见上图,实际可用css逻辑像素只有:
当然,此时的浏览器缩放为 100%,如果浏览器缩放变化:https://pic1.zhimg.com/80/v2-8bcd21467a517db3a2e17841e38ac268_720w.jpg
此时实际可用css逻辑像素只有https://pic3.zhimg.com/80/v2-4cb8d5f2b841f845be204d2a7a54bdde_720w.jpg
那么问题来了?如何处理?
问题是用户设备实际可用css逻辑像素宽度和设计稿不符,而且通常是用户设备实际可用css逻辑像素宽度小于设计稿宽度,如果此时按照设计稿开发,那个必然导致上面的情况发生。
一种方法是设置较小的设计稿宽度,但是这样又会使标准宽度设备用户体验下降,而且笔记本电脑用户设备宽度也不一样,宽度无法确定,因此这种方式指标不治本。
因此这里采取了 js 实现缩放,css 解决副作用的解决方案。
以 vue 项目为例,实现解决缩放问题只需要一行代码:
// main.ts/js
document.body.style.zoom = String(document.body.offsetWidth / 1920)
如此,便实现了用户所见与设计稿一致的效果。
核心实现代码虽然简单,但是会带来一系列问题,如下:
除 webkit/blink 内核外的浏览器(如火狐等)不支持 zoom 属性
zoom 属性会影响 vw/vh 的最终结果
zoom 作用后会对一些 canvas 库(如 antv、echarts等)产生影响
我们一个一个来解决:
放弃火狐兼容,引导用户使用非火狐浏览器
保存 zoom,使用 vw/vh 时用 calc(100vh / zoom值) 即可恢复 vw/vh 效果
将 canvas 元素或图表容器元素 zoom 设置为 1 / zoom值 即可
1 和 3 比较简单,1就不说了,3 的实现通过,一个 css 类:
此时你一定会想问:var(--zoom) 是什么?
因为我们的 zoom 属性是加在 body 上,zoom 效果会通过继承所用到所有元素。此时,我们就可以通过 ScaleCSSViewport_unzoom 来给制定元素及其子元素取消 zoom 效果。至此,问题 3 也解决了。
还剩下问题 2。
其实解决方法已经给出了:
原理就是这么个原理,解决也就是这么解决的。但是问题是这种方法对代码侵入性太强,而且无法平滑应用于旧项目。怎么办?
如果是小型项目,用上面的 calc 就行。但是通常的中大型项目都是通过 webpack 封装的各种脚手架来构建的。因此我们可以通过实现一个 webpack 插件来帮我们实现将 vw/vh 转换为 calc(vw/vh / zoom值)。
方式也非常简单粗暴。
对 webpack 编译生成的 css/js 文件出现的 xxxvw/vh 进行替换。
source = source.replace(/(?<=(\s|{|(|;|'|"|:))\d+.?\d*v(w|h)(?=(\s|}|)|;|'|"))/g, v => calc(${v} / var(--zoom))
)
同时,我们也可以在 html 中注入 js 代码实现全局 css 变量。
以 vue 项目为例,完整 plugin 代码如下:
// vue.config.js
plugins: [{
apply (compiler) {
compiler.hooks.emit.tapAsync('ScaleCSSViewport', (compilation, callback) => {
Object.keys(compilation.assets).forEach(item => {
let source = compilation.assets[item].source()
if (item.match(/.html$/g)) {
source = source.replace('', `
`)
source = source.replace('', `
`)
}
if (item.match(/.css|js$/g)) {
source = source.replace(/(?<=(\s|{|(|;|'|"|:))\d+.?\dv(w|h)(?=(\s|}|)|;|'|"))/g, v => `calc(${v} / var(--zoom))`)
source = source.replace(/`\d+.?\dv(w|h)`/g, v => v.replace(/`/g, ''))
}
compilation.assets[item] = {
source: () => source,
size: () => source.length
}
})
callback();
}
);
}
}]
除了能够实现无差别将 vw/vh 转换为 calc 之外,ScaleCSSViewport(上面那个插件)也提供了方法来避免替换。如果你就想在页面上展示一个 100vh,需要写成 `100vh`,ScaleCSSViewport 会将 `100vh` 替换为 100vh。同理,如果你想要展示 `100vh,则要在源码中写成 `100vh``。
至此,一个相对完善的前端解决用户缩放问题的解决方案才算大概完成。初步效果已经实现,此方案仍存在部分问题,包括但不仅限于:
火狐浏览器不兼容(放弃火狐)
canvas zoom 导致图表效果异常(使用 class="ScaleCSSViewport_unzoom")
部分UI库组件的弹出浮层位置异常(指定浮层所渲染的父元素dom)
同时,因为这里采用的源代码替换的方式过于粗暴,还是有可能会伤及无辜,替换了不该替换的代码。使用起来要特别慎重。
当然,这也是我个人对于这个问题解决的一次探索。虽然不尽完美,但也是略有所得。
希望可以帮到大家。
谢谢!