今天看到一道前端面试题,是别人面试自己很喜欢的前端团队时出的题目:一张高清图片,怎么保证其在不同移动端设备上的显示效果?
看到这个问题,我就一脸懵逼,实际上我连移动端那些像素都搞不清楚,这道题给我的话肯定会gg。以前虽然做过移动端的公司官网,但实际上都是胡乱调样式加上响应式布局做的,这样怎么能学好前端呢?索性,借着这道题目把css像素中那些事、viewport、响应式布局、移动端适配问题一起来了解学习一下吧。
像素
像素单位有设备像素、逻辑像素和CSS像素3种。
设备像素/物理像素
- 设备像素指的是显示器上的真实像素,每个像素的大小是屏幕固有的属性,屏幕出厂以后就不会改变了。
- 设备分辨率描述的就是这个显示器的宽和高分别是多少个设备像素。
- 设备像素和设备分辨率交给操作系统来管理,浏览器不知道、也不需要知道设备分辨率的大小,浏览器只需要知道逻辑分辨率就可以了。
设备独立像素/逻辑像素
- 设备独立像素(dips)是操作系统定义的一种像素单位,应用程序将设备独立像素告诉操作系统,操作系统再将设备独立像素转化为设备像素,从而控制屏幕上真正的物理像素点。
- 为什么需要在应用程序与设备像素之间定义这么一种单位呢?为什么应用程序不应该直接使用设备像素?
- 早期的移动设备,只有物理像素,没有设备独立像素,在不缩放的前提下,CSS 中的 1px 就代表着 1 个物理像素。比如 iphone3 的物理像素是 320 * 480,那么 width: 320px; 的元素将会占满 iphone3 的屏幕宽度。
- 从 iphone4 开始,苹果公司便推出了所谓的 Retina 屏,物理像素变成 640 * 960,但是屏幕尺寸没有变化,也就是说单位面积上的物理像素的数量增加了,或者说屏幕密度增加了。如果还按照原来那样,1px CSS 像素由 1 个物理像素来渲染, 那么 width: 320px; 的元素只会占据半个屏幕的宽度。原来在 iphone3 上能够占满屏的网页,在 iphone4 上只会占一半的屏幕,同时 font-size: 20px; 的字体在 iphone4 上的尺寸也会缩小。
- 为了让同一个网页在 iphone4 和 iphone3 上的有相同的显示效果,引入了独立像素的概念。iphone4 的独立像素为 320 * 480,是 iphone4 的物理像素的一半,和 iphone3 的物理像素一样。也就是说,iphone4 上的 1 个独立像素 == 2 个物理像素。在 iphone4 上,在不缩放的前提下,CSS 中的 1px 便由 1 个独立像素来渲染,相当于 2 个物理像素。这样,width: 320px; 的元素由 320 个独立像素来渲染,也就是由 640 个物理像素来渲染,正好占满 iphopn4 的屏幕宽度。font-size: 20px; 的字体在 iphone3 和 iphone4 上的尺寸也相同,只不过在 iphone4 上的字体更清楚。
- 至于将多少设备像素划分为一个逻辑像素,这由操作系统决定
- Android设备,虽然屏幕分辨率不同,但是通常相同大小屏幕的设备具有相同的逻辑分辨率。
- 设备独立像素与设备像素之间的比例是多少,显示器厂商和操作系统厂商会通过调查研究来得出最利于观看的比例。普遍规律是,屏幕的像素密度越高,就需要更多的设备像素来显示一个设备独立像素。
一般开发者只需要会用css像素就好了,设备像素和设备独立像素是为了后面将viewport
CSS 像素
- CSS 中的长度单位,在 CSS 中使用的 px 都是指 CSS 像素。
- 缩放会导致CSS像素边长的改变,从而导致window.devicePixelRatio的改变
- 当我们缩放页面的时候,元素的css像素数量不会改变,改变的只是每个css像素的大小。也就是说width: 128px的元素在缩放200%以后,宽度依然是128个css像素,只不过每个css像素的宽度和高度变为原来的两倍。如果原本元素宽度为128个设备独立像素,那么缩放200%以后元素宽度为256个设备独立像素(css像素宽度始终是128)。
- 由devicePixelRatio = 物理像素 / 独立像素
(设备像素的数量 / CSS像素的数量。)
这个比例也等价于CSS像素边长/设备像素边长)可的此时devicePixelRatio为2
CSS中的其他长度单位
通过screen.width/height得到的数值就是整个屏幕(不仅仅是浏览器的区域)的宽度和高度(单位:设备独立像素)。这个数值不随页面缩放、浏览器窗口大小而改变。
px:绝对单位,页面按精确像素展示
em:相对单位,基准点为父节点字体的大小,如果自身定义了font-size按自身来计算(浏览器默认字体是16px),整个页面内1em不是一个固定的值。
rem:相对单位,可理解为”root em”, 相对根节点html的字体大小来计算,CSS3新加属性,chrome/firefox/IE9+支持。
(另外需注意chrome强制最小字体为12号,即使设置成 10px 最终都会显示成 12px,当把html的font-size设置成10px,子节点rem的计算还是以12px为基准,所以网上很多文章提到的将html的font-size设为10方便计算不是那么可取)。
vw:viewpoint width,视窗宽度,1vw等于视窗宽度的1%。
vh:viewpoint height,视窗高度,1vh等于视窗高度的1%。
vmin:vw和vh中较小的那个。
vmax:vw和vh中较大的那个。
vw, vh, vmin, vmax:IE9+局部支持,chrome/firefox/safari/opera支持,iOS safari 8+支持,Androidbrowser4.4+支持,chrome for android39支持
其它的单位还有:
%:百分比
in:寸
cm:厘米
mm:毫米
pt:point,大约1/72寸
pc:pica,大约6pt,1/6寸
ex:取当前作用效果的字体的x的高度,在无法确定x高度的情况下以0.5em计算(IE11及以下均不支持,firefox/chrome/safari/opera/ios safari/android browser4.4+等均需属性加么有前缀)
ch:以节点所使用字体中的“0”字符为基准,找不到时为0.5em(ie10+,chrome31+,safair7.1+,opera26+,ios safari 7.1+,android browser4.4+支持)
怎么计算rem?
html {
font-size:100px;(也可以设置百分比)
}
body {
font-size:14px;(可以改变)
}
p {
//假如屏幕宽度是320px,那么字号是7px;
font-size:7px;
}
动态计算:
(function(){var a=document.documentElement.clientWidth||document.body.clientWidth;if(a>460){a=460}else{if(a<320){a=320}}document.documentElement.style.fontSize=(a/7.5)*1+"px"})();
计算过程:
标准 640px font-size:100px 设置字号 font-size:14px;则rem为0.14,即1rem=100px
屏幕宽度 改变为320px 那么font-size:变成50px rem为0.14 则设置字号 font-size:50*0.14=7px
viewport
viewport分为layout viewport和visual viewport
layout viewport
- layout viewport是网页布局的区域,它是元素的父容器。只要你不在css中修改元素的宽度,元素的宽度就会撑满layout viewport的宽度。
很多时候浏览器窗口没有办法显示出layout viewport的全貌,但是它确实是已经被加载出来了,这个时候滚动条就出现了,你需要通过滚动条来浏览layout viewport其他的部分。 - layout viewport用css像素来衡量尺寸,在缩放、调整浏览器窗口的时候不会改变。缩放、调整浏览器窗口改变的只是visual viewport。
- 在桌面浏览器中,缩放为100%的时候,Layout Viewport宽度等于内容窗口的宽度。(你几乎不会在电脑上见过横向滚动条,除非你调整缩放)
但是在移动端,缩放为100%的时候,Layout Viewport不一定等于内容窗口的大小。当你用手机浏览浏览宽大的网页(这些网页没有采用响应式设计)的时候,你只能一次浏览网页的一个部分,然后通过手指滑动浏览其他部分。这就说明整个网页(Layout Viewport)已经加载出来了,且此时内容窗口宽度小于Layout Viewport。
visual viewport
visual viewport就是显示在屏幕上的网页区域。通过前面的说明你应该已经知道visual viewport了:它往往只显示layout viewport的一部分。visual viewport就像一台摄像机,layout viewport就像一张纸,摄像机对准纸的哪个部分,你就能看见哪个部分。你可以改变摄像机的拍摄区域大小(调整浏览器窗口大小),也可以调整摄像机的距离(调整缩放比例),这些方法都可以改变visual viewport,但是layout viewport始终不变。
visual viewport用css像素来衡量尺寸,表示有多少个css像素能够被用户看到。
为了让小小的手机屏幕也能够浏览宽大的网页(这些网页没有采用响应式设计),就是让手机屏幕显示的css像素与网页的css像素一样多了,也就是visual viewport = layout viewport。通过缩放比例来实现(缩放比例=css像素边长/逻辑像素边长)。
为了进行适配,前端需要在html中加入
<meta name="viewport" content="width=device-width, initial-scale=1.0">
这个标签只对移动端浏览器生效,这里的意思是说:它将layout viewport的横向css像素数量设为屏幕的横向dips(定值,也就是设备独立像素),然后将初始缩放的值设为1.0,也就是让css像素的大小=dips的大小。这样,即实现了移动端也能访问电脑网页一样的内容也可以实现舒适的像素的大小。网页宽度也就等于屏幕宽度了,也不会出现横向滚动条。
至于为什么设置了上面的字段后,电脑端和移动端显示的内容不一样,这可能是因为用了媒体查询。
initial-scale=1.0 css和逻辑像素/设备独立像素相等
相关属性
1、screen.width/height
整个屏幕的宽度和高度。这两个数值的单位是设备独立像素。这两个数值不随页面缩放、浏览器窗口大小而改变,在前端开发的过程中可以认为是固定不变的(除非你通过操作系统改变屏幕的分辨率)。
逻辑分辨率就是screen.width/height。为什么iphone3GS以后的iphone都要把这个值设为实际屏幕分辨率的1/2或1/3呢?因为随着屏幕上塞进越来越多的真实像素,屏幕大小的变化却不那么明显,因此像素密度也越来越高。如果还让逻辑分辨率:真实屏幕分辨率=1:1,那么12px的字体就会越来越小,影响阅读体验。因此,后续的iphone用4个真实像素(甚至9个像素)组合成一个“逻辑像素”。这样,即使真实像素越来越小,每一个“逻辑像素”的大小变化不大。浏览器可以放心地使用逻辑像素来衡量大小,而不用担心真实大小在不同的显示器上出现严重偏差。
2、window.innerWidth/Height
visual viewport的大小,也就是浏览器内容窗口的大小,不包括菜单栏、地址栏、状态栏等,但是包括滚动条。**单位是CSS像素。通过这个属性你可以知道,当前的浏览器窗口可以容纳多少个css像素。**当用户放大的时候这个数值会减少(因为css像素变大了),当用户缩小的时候这个数值增大。缩放改变浏览器窗口都会改变这个属性的值。
与之对应的,window.outerWidth/outerHeight给出整个浏览器窗口的大小(包括各种栏),但是单位是设备独立像素。
3、document.documentElement.clientWidth/Height
Layout Viewport的尺寸(Layout Viewport是元素的父容器),单位是CSS像素。
它与window.innerWidth/Height的唯一区别是,document.documentElement.clientWidth/Height不包含滚动条。
4、document.documentElement.offsetWidth/Height
元素的尺寸,除非你在css中改变html的大小(很少有人这么做),否则html的宽度始终与Layout Viewport宽度相同。单位是CSS像素。5、window.pageX/YOffset
滚动距离,描述用户已经向右、向下滚动了多少个像素,也可以理解为visual viewport相对于layout viewport的偏移值。单位是CSS像素。
参考
响应式设计——理解设备像素、设备独立像素和css像素
设备像素、设备独立像素、CSS 像素
viewport
高清图片在不同移动端设备上的显示效果
位图像素:一个位图像素是栅格图像最小的数据单元。每个位图像素都包含自身的显示信息,如色值,透明度等
理论上,当位图像素对应物理像素时,图片才能清晰展示。
例如:img标签,css像素为200300,普通屏幕下对应物理像素为200300,retina屏幕对应物理像素为400600:
(1)当在retina屏中,使用200300@1x图片时,由于每个单位的位图像素不可分割,只能就近取色,从而导致retina屏图片模糊;使用两倍图片@2x,如200300的img标签,使用400600的图片,这样retina屏幕下,物理像素:位图像素 = 1:1,图片清晰
(2)如果在普通屏下使用@2x图片,一个物理像素对应4个位图像素,它的取色需要一定的算法,显示结果就只有原图像素总数的4分之一(这个过程叫downsampling),图片虽然不会模糊,但是会少一些锐利度或者色差;同样会造成资源浪费
最好的解决方案:不同的dpr下,使用不同尺寸的图片。
(1)可以通过媒体查询或者js进行控制,拼接不同的url
(2)img标签,设置srcset属性
//srcrest以最合适的src去匹配不同屏幕(高分屏低分屏如Retina;大屏小屏)
// 2x、3x 表示目标屏幕的像素密度
<img src="source.jpg" srcset="source_2x.jpg 2x, source_3x.jpg 3x">
//400w、600w表示目标浏览器的宽度的限度,如浏览器宽度550w时,匹配600w的src
<img src="source.jpg" width="100%" srcset="source_400.jpg 400w, source_600.jpg 600w, source_1280.jpg 1280w">
所以如果需要保证高清图片的话,在retina屏中,两倍图片(2x),然后图片容器缩小50%
重点还是针对不同的dpr选择不同的图片