一. 视口 viewport
1 viewport 基础
viewport 解释为中文就是‘视口’的意思,也就是浏览器中用于显示网页的区域。在 PC 端,其大小也就是浏览器可视区域的大小,所以我们也不会太关注此概念;而在移动端,绝大多数情况下 viewport 都大于浏览器可视区,保证 PC 页面在移动浏览器上面的可视性。为提升可视性体验,针对移动端有了对 viewport 的深入研究。
2 viewport 详解
在移动端有三种类型的 viewport: layoutviewport、visualviewport、idealviewport。具体解释如下:
layoutviewport: 大于实际屏幕, 元素的宽度继承于 layoutviewport,用于保证网站的外观特性与桌面浏览器一样。layoutviewport 到底多宽,每个浏览器不同。iPhone 的 safari 为 980px,通过 document.documentElement.clientWidth 获取。 visualviewport: 当前显示在屏幕上的页面,即浏览器可视区域的宽度。 idealviewport: 为浏览器定义的可完美适配移动端的理想 viewport,固定不变,可以认为是设备视口宽度。比如 iphone 7 为 375px, iphone 7p 为 414px。
3 viewport 设置
我们通过对几种 viewport 设置可以对网页的展示进行有效的控制,在移动端我们经常会在 head 标签中看到这段代码:
<meta name='viewport' content='width=device-width,initial-scale=1,user-scale=no' />
复制代码通过对 meta 标签三个 viewport 的设置,最终使页面完美展示。下面详细的阐释其具体含义:
- width 设置的是 layoutviewport 的宽度
- initial-scale 设置页面的初始缩放值,并且这个初始缩放值是相对于 idealviewport 缩放的,最终得到的结果不仅会决定 visualviewport,还会影响到 layoutviewport
- user-scalable 是否允许用户进行缩放的设置
对上面的说明通过公式推导进行进一步的解释:
// 设定两个变量:
viewport_1 = width;
viewport_2 = idealviewport / initial-scale;
// 则:
layoutviewport = max{viewport_1, viewport_2};
visualviewport = viewport_2;
2
3
4
5
6
7
只要 layoutviewport === visualviewport,页面下面不会出现滚动条,默认只是把页面放大或缩小。
4 viewport 举例
以下是通过改变 meta viewport 的几个参数的值来算取不同的 viewport:
width | initial-scale | layoutviewport | visualviewport | idealviewport | 是否滚动 |
---|---|---|---|---|---|
- | - | 980px | 980px | 375px | 否 |
device-width | 1 | 375px | 375px | 375px | 否 |
device-width | 2 | 375px | 188px | 375px | 是 |
device-width | 0.5 | 750px | 750px | 375px | 否 |
480px | 1 | 480px | 375px | 375px | 是 |
480px | 2 | 480px | 188px | 375px | 是 |
480px | 0.5 | 750px | 750px | 375px | 否 |
以上是针对 iphone 6/7/8 的测试数据,且无论怎么设置 viewport 都具有临界值,即:75 <= layoutviewport <= 10000,75 <= visualviewport <= 1500。
5 为什么要设置 viewport
viewport 的设置不会对 PC 页面产生影响,但对于移动页面却很重要。下面我们举例来说明:
媒体查询 @media 响应式布局中,会根据媒体查询功能来适配多端布局,必须对 viewport 进行设置,否则根据查询到的尺寸无法正确匹配视觉宽度而导致布局混乱。如不设置 viewport 参数,多说移动端媒体查询的结果将是 980px 这个节点布局的参数,而非我们通常设置的 768px 范围内的这个布局参数 由于目前多数手机的 dpr 都不再是 1,为了产出高保真页面,我们一般会给出 750px 的设计稿,那么就需要通过设置 viewport 的参数来进行整体换算,而不是在每次设置尺寸时进行长度的换算。
设备像素比 dpr 与 1px 物理像素 2.1 物理像素(physical pixel) 手机屏幕上显示的最小单元,该最小单元具有颜色及亮度的属性可供设置,iphone6、7、8 为:750 * 1334,iphone6+、7+、8+ 为 1242 * 2208 2.2 设备独立像素(density-indenpendent pixel) 此为逻辑像素,计算机设备中的一个点,css 中设置的像素指的就是该像素。老早在没有 retina 屏之前,设备独立像素与物理像素是相等的。 2.3 设备像素比(device pixel ratio) 设备像素比(dpr) = 物理像素/设备独立像素。如 iphone 6、7、8 的 dpr 为 2,那么一个设备独立像素便为 4 个物理像素,因此在 css 上设置的 1px 在其屏幕上占据的是 2个物理像素,0.5px 对应的才是其所能展示的最小单位。这就是 1px 在 retina 屏上变粗的原因,目前有很多办法来解决这一问题。
在JavaScript中,可以通过window.devicePixelRatio获取到当前设备的dpr。而在CSS中,可以通过-webkit-device-pixel-ratio,-webkit-min-device-pixel-ratio和 -webkit-max-device-pixel-ratio进行媒体查询,对不同dpr的设备,做一些样式适配(这里只针对webkit内核的浏览器和webview)
二. 设备像素比 dpr 与 1px 物理像素
1 物理像素(physical pixel)
手机屏幕上显示的最小单元,该最小单元具有颜色及亮度的属性可供设置,iphone6、7、8 为:750 * 1334,iphone6+、7+、8+ 为 1242 * 2208
2 设备独立像素(density-indenpendent pixel)
此为逻辑像素,计算机设备中的一个点,css 中设置的像素指的就是该像素。老早在没有 retina 屏之前,设备独立像素与物理像素是相等的。
3 设备像素比(device pixel ratio)
设备像素比(dpr) = 物理像素/设备独立像素。如 iphone 6、7、8 的 dpr 为 2,那么一个设备独立像素便为 4 个物理像素,因此在 css 上设置的 1px 在其屏幕上占据的是 2个物理像素,0.5px 对应的才是其所能展示的最小单位。这就是 1px 在 retina 屏上变粗的原因,目前有很多办法来解决这一问题。
4 1px的物理像素的解决方案
从第一部分的讨论可知 viewport 的 initial-scale 具有缩放页面的效果。对于 dpr=2 的屏幕,1px压缩一半便可与1px的设备像素比匹配,这就可以通过将缩放比 initial-scale 设置为 0.5=1/2 而实现。以此类推 dpr=3的屏幕可以将 initial-scale设置为 0.33=1/3 来实现。
三. 设备像素比 dpr 与 rem 的适配方案
结合 2、3 部分可以实现 1px 的物理像素这一最小屏幕单位,那在此基础上如可让设计通常提供的 750px 设计稿来完美的适配到多种机型上,使用 rem 是一种解决方式。
1 rem 如何设置
rem 是相对于根元素 html 的 font-size 来做计算。通常在页面初始化时加载时通过对document.documentElement.style.fontSize 设置来实现。
2 rem 适配规则
通过对 initial-scale = 1/dpr 的设置,已将对屏幕的描述从物理像素转化到了物理像素上了,这将是后续推导的基础,且设计稿为 750px。
物理像素为 750 = 375 * 2,若屏幕等分为 10 份,那么 1rem = 75px,10rem = 750px;
物理像素为 1125 = 375 * 3,若屏幕等分为 10 份,那么 1rem = 112.5px, 10rem = 1125px;
物理像素为 1242 = 414 * 3, 若屏幕等分为 10 份,那么 1rem = 124.2px, 10rem = 1242px;
因此可推导出 rem 的设定方式:
document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px';
复制代码下面我们将 750px 下,1rem 代表的像素值用 baseFont 表示,则在 baseFont = 75 的情况下,是分成 10 等份的。因此可以将上面的公式通用话一些:
document.documentElement.style.fontSize = document.documentElement.clientWidth / ( 750 / 75 ) + 'px';
整体设置可参考如下代码:
(function (baseFontSize) {
const _baseFontSize = baseFontSize || 75;
const ua = navigator.userAgent;
const matches = ua.match(/Android[\S\s]+AppleWebkit\/(\d{3})/i);
const isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);
const dpr = window.devicePixelRatio || 1;
if (!isIos && !(matches && matches[1] > 534)) {
// 如果非iOS, 非Android4.3以上, dpr设为1;
dpr = 1;
}
const scale = 1 / dpr;
const metaEl = document.querySelector('meta[name="viewport"]');
if (!metaEl) {
metaEl = document.createElement('meta');
metaEl.setAttribute('name', 'viewport');
window.document.head.appendChild(metaEl);
}
metaEl.setAttribute('content', 'width=device-width,user-scalable=no,initial-scale=' + scale + ',maximum-scale=' + scale + ',minimum-scale=' + scale);
document.documentElement.style.fontSize = document.documentElement.clientWidth / (750 / _baseFontSize) + 'px';
})();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
同时为了书写方便可以直接通过 px 布局,然后在打包时利用 pxtorem 库转化为基于 rem 的布局。
四. 视口单位适配方案
将视口宽度 window.innerWidth 和视口高度 window.innerHeight 等分为 100 份,且将这里的视口理解成 idealviewport 更为贴切,并不会随着 viewport 的不同设置而改变。
vw : 1vw 为视口宽度的 1%
vh : 1vh 为视口高度的 1%
vmin : vw 和 vh 中的较小值
vmax : 选取 vw 和 vh 中的较大值
如果设计稿为 750px,那么 1vw = 7.5px,100vw = 750px。其实设计稿按照设么都没多大关系,最终转化过来的都是相对单位,上面讲的 rem 也是对它的模拟。
五.内容布局
目前对于移动端适配的内容布局效果是这样的:
百分比,所有需要动态调整的元素宽高采用百分比,字号固定像素。
rem,通过计算或者JavaScript获取到设备像素/CSS像素的比例,确定根元素的字体像素,然后所有单位根据根元素字体像素进行rem设置,确定大小。而基础rem会根据设备变化而变化。
vw,根据当前设备的Visual Viewport宽度作为100vw,然后得出单位vw的宽度,所有元素按照vw为单位进行样式排布。
Media Query:通过断点来进行不同宽度区间的设备样式适配。
以上几个方法各自都有各自的好处,我们可以看一下实际应用时候的效果:
百分比
使用百分比作为内容大小的标准,在大部分条件下是可行的,百分比可以很好地让元素乖乖呆在自己的位置,无论屏幕的宽度大小
但是文字就存在非常大的问题了,由于文字是固定大小,在屏幕dpr变化的时候,文字的CSS像素不变,就导致了文字在页面中的占位发生了变化。这样的结果就是,文字过多或者屏幕dpr过小的时候,会发生溢出;但是如果按照小屏幕为基准,又会发生字体太小这种情况。
百分比在当前移动端适配排版的时候,更多地会作为section级别元素的兼容排版。这个也要和设计稿中的效果相关,如果设计稿中要求一个元素定宽,那么就直接用px来保证宽度就可以了。
rem
rem这个单位和之前常用的em有点类似,唯一的区别在于rem及基于根元素的font-size来进行计算的一个相对值。em存在很多缺点,比如层层嵌套之后,可能就会忘记了上一层的font-size到底是多大。或者比如像现在的模块化开发,一个路由套在另一个路由里面,甚至找父元素都需要到其他文件中去找。
为了解决em存在的问题,标准中还有rem这个单位来帮助排版。所有的元素大小都用rem来作为单位,然后在页面的根元素中,我们为根元素的font-size进行确定化地赋值,这样所有的rem单位都是同一个明确的基准了。当屏幕进行适配的时候,只需要调整这个基准值,就可以保证每个元素的大小自动按照比例调整。
阿里的lib-flexible解决方案实际上就是利用了这个方式,通过给html标签绑定font-size以及data-dpr属性来进行整个页面的适配。
方案将整个页面宽度分成100份,分成100份的原因可以看下面的另一个方案。每10个单位宽度作为1rem,也就是整个视觉稿的宽度会被分成10rem的100份,假如拿到的视觉稿是750px的,那么1rem就代表75px。这样得到的比例系数就是75/750,也就是每次在进行设计稿到CSS的转换的时候,只需要对设计稿的像素值/10就可以得到对应的rem值。
通过一个预先加载的JavaScript脚本,计算根节点的字体大小,document.documentElement.style.fontSize = window.innerWidth / 10 + 'px';,然后我们在写页面代码的时候只需要将原始的像素值/基准值就可以得到对应的rem单位了。当然每次都要按计算器肯定是不行的。如果想方便使用的话,可以用less或者sass这种预处理器来处理页面。
@function px2rem($px)
// 这里将设计稿的px转换为rem,
@return ($px / $unit-px) * 1rem
// 根据设备的dpr进行字体适配
@mixin font-dpr($font-size)
font-size: $font-size
[data-dpr="2"] &
font-size: $font-size / 2
2
3
4
5
6
7
8
9
媒体查询改变根元素的字体大小
/* 媒体查询以@media开头,然后后面可以跟上 判断的条件,比如:screen表示屏幕设备, and是并且的意思, min-width是最小宽度的意思。 */
@media screen and (min-width: 320px) {
html {font-size: 50px;}
}
@media screen and (min-width: 360px) {
html {font-size: 56.25px;}
}
@media screen and (min-width: 400px) {
html {font-size: 62.5px;}
}
@media screen and (min-width: 440px) {
html {font-size: 68.75px;}
}
@media screen and (min-width: 480px) {
html {font-size: 75px;}
}
@media screen and (min-width: 640px) {
html {font-size: 100px;}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
其实以上讲的两种方式,一般都会混用,并不是用一种方案写到头,而是什么情况下适合就用什么方式。 比如: 头像在不同设备上一般都会设置固定像素的大小 宽度可能超过50%的盒子尽量用百分比或者弹性盒子布局 高度和宽度需要同时根据屏幕自动缩放的时候,推荐使用rem布局
除了元素宽高可以得到比较好的还原,对于文字大小的适配也比较重要,由于每个设备的dpr不同(这里尤其是iOS设备),直接导致了很多文字在iPhone6+上显示正常,而在iPhone5上面却文字过大,导致文字溢出,上面的less mixin就是进行字体样式适配的。 这种方法结合了sass的函数功能和rem的适配,再加上必要的百分比以及media query可以得到比较好的移动表现。 这是使用这种方法在iphone6上的显示效果,具体的整体显示效果可以戳这里,rem基准样式示例 如果对于文字在各种平台上的显示效果不满意,可以通过上面说的sass的mixin来让文字根据dpr进行断点渲染。 而这样存在的问题就是仅仅适用于移动端,并且不能够进行横屏适配,因为横屏之后,页面的宽度发生了变化,但是基准值却还保持着原本绑定到根节点上面的基准值。
vw
首先看看vw的浏览器支持情况吧,can i use vw支持情况,使用这个单位意味着你放弃了IE11以下的PC用户,在现在一个主要兼容移动端的世界里,并没有太大的副作用(这里吐槽一句,其实PC端的兼容远远要比移动端来的方便。移动端奇奇怪怪的分辨率以及2x,3x的屏幕,还有苦逼的ipad、横屏让我每次做兼容的时候想一跃解千愁)。 vw自身将整个可见视口横向分成了100份,每一个单位就是1vw,这个单位最大的优势就是在移动端的时候,无论是竖屏或者横屏,vw永远都是针对于横向的,比rem的方案好在当屏幕大小发生变化(顺便兼容了以后的可调节屏幕大小的移动设备[手动斜眼])的时候,不会让页面崩掉。 对于移动设备来说,比如iphone6+的375pxCSS像素宽度,1vw就等于3.75px,通过这个单位可以解决上面的依赖于脚本绑定根元素font-size的问题,在竖屏和横屏下面都有比较好的效果。 在通过vw解耦了CSS和JS之后,那么vw是否可以独立解决所有问题呢?
// 首先,我司的设计稿目前都是以750px为宽度,实际为iPhone6+的375px为基准
$w-base: 375px
$w-base-design: 750px
@function px2vw($px)
@return ($px / $w-base-design) * 100vw
2
3
4
5
6
首先,上面的sass代码可以根据你设计稿上面的px单位转换为vw单位值。当然最简单的办法还是直接按照视觉稿上面像素进行输入,然后直接输出对应的vw值啦。vscode和sublime当然会有已经做好的插件,但是个人并不是很喜欢这种方式,这样你就没办法得到这个视觉稿原本的像素值了。在后期进行维护的时候会增加很多很多很多麻烦。这就是写起来爽,改起来火葬场吧。。。vw的效果可以看下面的codepen。 所以说优势和劣势呢
目前来说,vw是肯定不适合单独使用的,毕竟页面中还是有很多元素需要绝对的大小定位的。px永远是必不可少的,视觉不可能让你所有的东西都自适应。 那么vw能够解决什么问题呢?首先是大部分取代%在CSS中的使用,百分比在CSS中存在很多歧义,对于宽度,上下边距,左右边距,内外边距的处理方式不尽相同。即使是老练的前端有时候也得思考一下当前的百分比到底是根据什么确定的。而vw则是一个绝对的数值,仅仅根据一个不太可能变化的屏幕宽度来确定。 而百分比主要解决的弹性问题,就是vw着力解决的问题。 vw不能够兼顾所有的情况,所以这个单位目前还并不是最终的解决方案,还是需要和其他单位合作来帮助页面能够更加优雅地显示出来。
总结
在移动端开发中,理解视口对适配至关重要。因此本文先从视口展开讨论,从而引出 1px、rem 及 vw/vh 这些和适配相关的主要话题。下面提供的参考文章会在某些点上更加细化,以供参考。
CSS的兼容性不在于解释器上,而是在于设备的屏幕上面。大部分时间不仅需要将页面展示在用户的面前,而是需要将页面稳定且优雅地展示给用户。 无论是百分比,rem还是vw,都是进行局部容器元素定位的,作为最底层的叶子元素或者单元元素来说,更多时间还是会使用px来尽量还原视觉稿。 长远考虑这个问题,vw在仅进行移动端访问的情况下效果拔群,因为不考虑兼容,只需要考虑适配问题。工程中到底使用哪个方法进行,取决于大部分业务需要兼容的环境。
鸣谢阿里巴巴TXD和LucasTwilight两位大佬的文章