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>