多端适配终极指南
发布于 4 年前 作者 gulei 1436 次浏览 来自 分享

viewport的作用

viewport翻译成中文是“视窗”的意思,也就是字面上的意思,页面展示的窗口。知道这个viewport有哪些作用呢?我举例一下:

计算百分比值

当我们设置一个块级元素的宽度为百分比的时候,在HTML的规则中,最终的大小取决于它的父元素。如果元素嵌套情况如下:

html > body > div

最终要计算出div的实际大小,就要知道body的大小,而body的大小又依赖于html。所以最终的问题是,如何计算html的大小。而这个html是根元素,没有父元素。此时就是viewport登场了。由它来约束html的大小。

用户缩放

当用户进行缩放时,对于我们的页面是如何变化的,或者用户当前是否存在缩放状态,这些都是通过viewport来获取的。

用户进行缩放时,页面的CSS像素是不发生变化的(如果有变化,必然触发重新渲染),变化的是视窗的大小。当用户放大页面是,视窗应该是变小,但物理像素是不会变化的,因此相同的CSS像素占用更多的设备像素,因此此时dpr会变大。

dpr(device pixel ratio) 设备像素比 = 物理像素 / 设备独立像素

JavaScript里,我们可以通过screen.width获取物理像素的宽度,通过window.innerWidth获取当前页面的独立设备像素,因此可以这样计算设备像素比:screen.width / window.innerWidth

当然,也可以直接获得设备像素比:window.devicePixelRatio

在桌面端,设备像素比(dpr) 通常情况下都是等于1,当不等于1的时候,通常是用户进行缩放了。

移动端适配

在移动端,会有许多各种尺寸的屏幕。如何在不同的屏幕呈现相同的效果,这就是移动端适配的工作,要想弄清楚如何兼容,就要先理解viewport的概念。

基础概念

屏幕尺寸

可通过screen.width/height获得。一般是通过 设备像素(device pixel) 来计算。

窗口尺寸

可通过window.innerWidth/innerHeight获得。一般是通过 CSS像素 来计算。窗口是浏览器的窗口,包含了滚动条的尺寸,不包含顶部菜单:

视窗尺寸

可通过document.documentElement.clientWidth/clientHeight获得。一般是通过 CSS像素 来计算。视窗与窗口差别在于不包含滚动条的尺寸:

移动端适配

REM方案

视觉稿750px,设置根元素即(html)的大小为75px,所有元素均这样计算:
230px举例:

width: 350px / 75px = 5rem

此时页面上的所有元素均以根元素的font-size计算,因此要做到动态支持不同尺寸的手机,只需要动态修改根元素的font-size即可。由于我们是以750px的十分之一为基准的,所以只需将当前 视觉窗口(visual viewport) 同样除以10即可:

let docEl = document.documentElement
let rem = docEl.clientWidth / 10

docEl.style.fontSize = rem + 'px'

另外,由于font-size是继承性属性,上述代码将font-size修改了,为了不影响到默认的字体大小,可以在上重置font-size:

body {
  font-size: inital;
}

VW方案

VW方案是和REM方案的原理是一致的:以当前宽度按比例动态调整。

VW方案的优势是 不需要动态调整基数。vw单元原生支持与当前视觉窗口按比例动态变化。缺点是会有一定的兼容性,可以看下vw的兼容情况:

具体实现方案与rem类似,以视觉稿的宽度(一般为750px)为基准,将px单位转换成vw单位,以75px举例:(基于vw的定义,全宽等于100vw):

.banner {
  width: calc(75 / 750 * 100) vw
}

transfrom方案

该方案不需要转换单位,正常以px为单位。只需要以屏幕宽度与视觉稿的宽度的比作为基数,在根节点上进行缩变即可:

let docEl = document.documentElement
let ratio = 750 / docEl.clientWidth

document.body.style.transform = `scale(${ratio})`

目前发现此方案存在兼容性问题,在iOS上,fixed定位的元素会失去固定的效果,会随着滚动改变位置。另外在某些Android上,fixed的元素会滑动之后消失。因此,不建议使用此方案

viewport meta方案

此方案与transform方案类似,通过缩变,以将页面宽度适应屏幕宽度。

不同的地方在于,需要移除现有的viewport meta,在head增加一个脚本实时生成viewport meta

<script>
  let meta = document.createElement('meta')
  meta.setAttribute('name', 'viewport')
  meta.setAttribute('content', `width=750, initial-scale=${screen.width/750}, user-scalable=no`)
  document.documentElement.firstElementChild.appendChild(meta)
script>

参考资料

3 回复

html 和 viewport中间缺了个关联 初始包含块

这截图有内味儿…

回到顶部