移动端处理多倍屏的问题
一、认识物理像素、设备独立像素、设备像素比
在css中我们一般使用px做单位,需要注意的是,CSS样式里面的px和物理像素并不是相等的。CSS中的像素只是一个抽象的单位,在不同设备或不同环境中,CSS的1px所代表的物理像素是不同的。在pc端,CSS的1px一般对应着电脑屏幕的1个物理像素,但是在移动端,css的1px等于几个物理像素和屏幕像素密度有关。
物理像素(physical pixel)
物理像素又被称为设别像素,设备物理像素,它是显示器(电脑、手机)最小的显示单位,每个物理像素由颜色值和亮度组成。所谓的一倍屏、二倍屏(Retina)、三倍屏,指的是设备以多少物理像素来显示一个css像素,也就是说,多倍屏以更多更精细的物理像素点来显示一个css像素点,在普通屏幕下的1个css像素对应一个物理像素,而在Retina屏幕下,1css像素对应的却是4个物理像素
设备独立像素(device-independent pixel)
设备独立像素又被称为css像素,是我们写css时所用的像素,他是一个抽象单位,主要使用在浏览器上,用来精确度量Web页面上的内容。
设备像素比(device pixel ratio)
设备像素比简称dpr,定义了物理像素和设备独立像素的对应关系,:设备像素比=物理像素、设备独立像素。
css的1px等于几个物理像素,除了和屏幕像素密度dpr有关,还和用户缩放有关。例如当用户把页面放大一倍,那么CSS的1px所代表的物理像素也会增大一倍;反之把页面缩小一倍,CSS的1px所代表的物理像素也会减少一倍。关于这点,后面的1px细线问题还会讲到。
二、viewport
viewport就是设备上用来显示页面的那块区域,但viewport又不局限于浏览器可视区域的大小,他可能比浏览器的可视区域要大,也可能比浏览器的可视区域要小。在默认情况下,一般来讲,移动设备上的viewport都是要大于浏览器可视区的,这是因为考虑到移动设备的分辨率相对于桌面电脑来说都比较小,所以为了能再移动设备上正常显示那些传统的为桌面浏览器设计的网站,移动设备上的浏览器会把自己默认的viewport设为980px或1024px,但带来的后果就是浏览器会出现横向滚动条,因为浏览器可视区域的宽度是比这个默认的viewport的宽度要小。
明确三种不同的viewport视口:
visual viewport 可见视口,指屏幕宽度
layout viewport 布局视口,指DOM宽度
ideal viewport 理想视口,使布局视口就是可见视口即为理想视口
获取屏幕宽度(viewport)的尺寸:
window.innerWidth/Height
获取DOM宽度(layout viewport)的尺寸:
document.documentElement.clientWidth/Height
设置理想视口ideal viewport:
<meta name="viewport" content="width=dvice-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
该meta标签的作用是让layout viewport的宽度等于visual viewport的宽度,同时不允许用户动手缩放,从而达到理想视口。
meta标签里面的参数含义:
width:设置layout viewport的宽度,为一个正整数,或字符串“width-device”;
initial-scale:设置页面初始缩放值,为一个数字,可以带小数;
minimum-scal:允许用户的最小缩放值,为一个数字,可以带小数;
maximum-scale:允许影虎的最大缩放值,为一个数字,可以带小数;
height设置layout viewport的高度,这个属性对我们并不重要,很少使用的。
user-scalable:是否允许用户进行缩放,值为no或yes
三、rem适配方案
适配是为了使页面在不同手机设备上,相对保持统一的效果。移动端自适应方案很多,有百分比布局,弹性盒模型布局等,但最好用的要数rem布局了。
rem是相对于根元素的字体大小的单位,我们可以根据设备宽度动态设置根元素的font-size,使得以rem为单位的元素在不同终端上以相对一致的视觉效果呈现。下面介绍2种根据屏幕宽度设置rem基准值的方法。
用JS设置rem基准值
/* 设计稿是750,采用1:100的比例,用1rem表示100px,font-size为100 * (clientWidth / 750) */
(function(doc, win) {
var docEl = doc.documentElement,
resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
recalc = function() {
var clientWidth = docEl.clientWidth;
if (!clientWidth) return;
docEl.style.fontSize = 100 * (clientWidth / 750) + 'px';
};
if (!doc.addEventListener) return;
win.addEventListener(resizeEvt, recalc, false);
doc.addEventListener('DOMContentLoaded', recalc, false);
})(document, window);
用单位vm设置font-size
1vm等于屏幕可视区宽度的百分之一
html{
font-size:13.3334vm;
}
了解了物理像素、设备独立像素、设备像素比和viewport这几个重要概念后,来看一下移动端开发中,由于屏幕分辨率导致的两个经典问题:图片模糊问题和1px细线问题(注:为了叙述简洁,以下多倍屏只叙述2倍retina屏,其他屏幕同理)
三、图片模糊问题
一个位图像是栅格图像(如:png,jpg)最小的数据单元。每个位图像素都包含着一些自身的显示信息(如:显示位置,颜色值,透明度等)。理论上,1个位图像素对应1个物理像素,图片才能得到完美清晰的展示。对于dpr=2的retina屏幕而言,1个位图像素对应4个物理像素。由于单个位图像素不可以再进一步分割,所以只能就近取色,导致图片看起来比较模糊,如下图:
对于图片模糊问题,比较好的方案就是用多倍图片,如一个200*300的img标签,对于dpr=2的屏幕,用400*600的图片,如此一来,位图像素点个数就是原来的4倍,在retina屏幕下,位图像素点个数就可以跟物理像素点形成1:1的比例,图片自然就清晰了。
如果在普通屏幕下,也用了2倍图片,会怎样呢?
在普通屏幕下,200*300的img标签,所对应的物理像素个数就是200*300个,而两倍图片的位图像素个数是200*300*4个,所以就会出现一个物理像素点对应4个位图像素点,但他的取色只能通过一定的算法取某个一个位图像素点上的色值,这个过程叫做downsampling,肉眼看上去虽然图片不会模糊,但是会觉得图片缺少一些锐度,或者是有些色差,如下图:
所以最好的解决办法是:不同的dpr下面,加载不同尺寸的图片。不管是通过css媒体查询,还是通过js条件判断都是可以的。
四、1px细线问题
在上文我们已经知道,css像素为1px宽的直线,对应的物理像素是不同的,可能是2px或3px,而设计师想要的1px宽的直线,其实就是1物理像素宽。如下图:
对于css而言,可以认为border:0.5px,这是多倍屏下能显示的最小单位,然而,并不是所有手机浏览器都能识别border:0.5px,有的系统里,0.5px会被当成0px处理,那么如何实现0.5px呢?我推荐2种方法:用媒体查询根据dpr用伪元素+transform对边框进行缩放;用js根据屏幕尺寸和dpr精确设置不同屏幕所应有的rem基准值和initial-scale缩放值。
伪元素+transform
构建1个伪元素,border为1px,再以transform缩放到50%。
.border-1px{
position:relative;
}
@media screen and (-webkie-min-device-pixel-ratio:2){
content:"";
position:absolute;
left:0;
top:0;
width:100%;
height:1px;
border-top:1px solid #000;
-webkit-transform-origin:0 0;
transform-origin:0 0;
-webkit-transform:scaleY(0.5);
transform:scaleY(0.5);
}
用js计算rem基准值和viewport缩放值
var dpr,rem,scale;
var docEl=docuement.documentElement;
var fontEl=document.createElement('style');
var metaEl=document.querySelector('meta[name="viewport"]')
dpr=window.devicePixelRatio||1;
rem=100*(docEl.clientWidth*dpr/750);
meta.setAttribute('content','width=+dpr*docEl.clientWidth+',initial-scal='+scale+',maximum-scale='+scale+'user-scalable=no');
docEl.setAttribute('data-dpr',dpr);
docEl.firstElementChild.appendChild(fontEl);
fontEl.innerHTML='html{font-size:'+rem+'px}'