web开发中移动端适配
这个话题有些复杂,说起来有些琐碎,因为和移动端适配相关的问题太多了。
1. 概念
1.1 设备像素
设备像素被称为物理像素,它是显示设备中一个最小的物理部件。每个像素可以根据操作系统设置自己的颜色和亮度。这些设备像素的微小距离欺骗了我们肉眼看到的图像效果。
1.2 屏幕密度(ppi)
屏幕密度是指一个设备表面上存在的像素数量,它通常以每1英寸上排列有多少像素来计算(ppi:Pixels Per Inch)。苹果公司声称人类的肉眼无法区分单个像素,当一个显示器像素密度超过300ppi的时候,肉眼就无法区分出单独的像素。也就是说设备清晰度超过人的视网膜可以分辨像素的极限。因此手机显示器的像素密度达到或超过300ppi就不会出现颗粒感,平板电脑像素密度达到或超过260ppi就不会再出现颗粒感。苹果公司给这类设备起了一个科技感十足的名字“Retina”屏,即视网膜屏。
1.3 css像素
css是一个相对的单位,主要使用在浏览器上,用来精确地度量web页面上的内容。一般情况,css像素被称为与设备无关的像素(device-independent像素),简称dips,也叫设备独立像素,名字多,容易混淆。在一个标准显示密度下,一个css像素对应着一个设备像素。
<div height="200" width="300"></div>
上面的代码重定义了一个为200px,高为300px的div盒子。但是在Retina屏下,相同的div却使用了400X600的设备像素保持相同的物理尺寸显示,导致每个css像素点实际上有4倍的普通像素点。
如果我们把这个宽,高属性设置在一张图片上,如下css设置
img { width: 200px; height: 200px; }
在Retina屏中,这样一个css像素点实际上分成了4个设备像素点,造成颜色会近似选取,于是造成我们看上去变得模糊。
因此在Retina屏上要想清晰地还原图片,需要一个原始尺寸为400X400css像素的图片,而css像素仍然是200X200。如下:
此外还可以使用device-pixel-ratio来处理,下面内容将会介绍。
1.4 dips(叫dip或者dp或者divice independent pixels)
设备独立像素(也叫密度无关像素),可以认为是计算机屏幕坐标系统中的一个点,这个点代表一个可以由程序使用的虚拟像素(比如:css像素)然后由系统转换为物理像素。dips可以用来辅助区分视网膜设备。视网膜设备是指分辨率达到300ppi的屏幕。
1.5 dpr(window.divicePixelRatio)
divicePixelRatio是设备上物理像素和设备独立像素(dips)的比例,即window.divicePixelRatio = 物理像素/dips。以iphone4s为例说明,它是“Retina屏”,分辨率为960x640,以二者较小的屏幕宽度来计算,物理像素640px,设备独立像素320px,那么window.devicePixelRatio = 640px / 320px = 2
1.6 Retina屏高清图片模糊问题
为了提升用户体验,节省移动端流量,针对不同的显示屏,采用不同的方案,保证图片在不同显示屏幕下正常显示。这里采用的方法和原生app中针对不同分辨率采用不同图片的原理类似。有以下两点:
a. 普通显示屏(devicePixelRation = 1.0, 1.3)加载一张1倍的图片
b. Retina显示屏(divicePixelRation >= 1.5, 2.0, 3.0 )加载一张2倍大小的图片
利用媒体查询结合devicePixelRatio可以区分普通显示屏和高清显示屏
.css{/* 普通显示屏(设备像素比例小于等于1.3)使用1倍的图 */ background-image: url(img_1x.png); } @media only screen and (-webkit-min-device-pixel-ratio:1.5){ .css{/* 高清显示屏(设备像素比例大于等于1.5)使用2倍图 */ background-image: url(img_2x.png); } }
也可以使用image-set加载不同图片,image-set,是webkit的私有属性,也是css3的一个属性,目前已经有部分浏览器支持。它是为了解决Retina屏幕下的图像显示而生。
.css{ background: url(../img/bank_ico.png) no-repeat;/* 不支持image-set的显示屏 */ background: -webkit-image-set( url(../img/bank_ico.png) 1x,/* 支持image-set的浏览器的[普通屏幕]下 */ url(../img/bank_ico_retina.png) 2x);/* 支持image-set的浏览器的[Retina屏幕] */ }
上面两种方式都会使用两套或者多套图片,增加了工作量。
1.7 dip&dp(设备独立像素&密度无关像素)
dip:divice independentpixels,设备独立像素,一个基于density的抽象单位。不同设备下有不同的显示效果,这个和设备硬件有关,在Android上开发的程序将会在不同分辨率的手机上运行。为了让程序外观不至于相差太大,引入了dip的概念。例如定义一个10 X 10dip的矩形,在分辨率为160dpi的屏幕上,刚好是10 x 10像素;而在240dpi的屏,则是15 x 15像素,换算公式为pixs = dips * (density / 160),density就是屏幕的分辨率。
这里注意dip与屏幕密度有关,而屏幕密度与具体的硬件有关,硬件设置不正确,有可能导致dip不能正产显示。在屏幕密度为160的显示屏上,1dip === 1px,有时候屏幕分辨率很大,例如400 * 800,但是屏幕密度没有正确的设置,比如还是设置成160,那么凡是使用dip的都会显示过小。
dp:与密度无关的像素,同dip一样是一种基于屏幕密度的抽象单位,在每英寸160点的显示器上,1dp=1px。
所有非视网膜屏幕的iPhone设备在垂直屏幕显示的时候,它的宽度为320物理像素,通过meta改变viewport时
<meta name="viewport" content="width=device-width">
这时视窗布局变成320px(不同于视觉区域宽度,不放大显示的情况下,两者大小一致) ,这样整个页面自然地覆盖在屏幕上。这样非视网膜屏幕的iPhone上,屏幕的物理像素320像素,独立像素也是320像素,因此window.devicePixelRatio等于1。
而对于视网膜屏幕的iPhone,如iPhone4s,纵向显示的时候屏幕物理像素640,同样当用户设置
<meta name="viewport" content="width=device-width">
这个时候,视区宽度并不是640像素,而是320像素,这时为了更好的阅读体验,更合适大小的文字。这样物理像素是640像素,独立像素还是320px,因此window.divicePixelratio等于2。
1.8 位图像素
位图像素(Pixel)组成的,像素是位图最小的信息单元,存储在图像栅格中(PNG, JPG, GIF)。每个像素都具有特定的位置和颜色值。从左到右,从上到下的顺序记录图像中的每一个像素信息,如:像素在屏幕上的位置,像素的颜色等信息。位图图像是有单位长度内像素的多少来决定的。单位长度内像素越多,分辨率越高,图像效果越好。
位图也称为“位图图像”,“点阵图像”,“数据图像”,“数码图像”。基于图像的格栅分辨率,图像在web中是由css像素定义了一个抽象的大小。浏览器挤压或者拉伸图像都是基于其css高度或者宽度来呈现的过程。
当一个光栅图像在标准设备下全屏显示时,一位图像素对应的就是一设备像素,导致一个完全保真的显示。因为一个像素不能进一步分裂,在Retina屏幕下,要放大四倍来保持相同的物理像素大小,这样就会丢失很多的细节,造成失真的情况。换句话说,每一位图像素被乘以四填补相同的物理标签在Retina屏幕下显示。
1.9 高分辨率屏幕&高像素密度屏幕
在Retina视网膜屏幕面世之前很少有人关注像素密度,在windows系统下,提高屏幕分辨率一般都是通过提高屏幕尺寸。而随着屏幕分辨率的提升,图像和文字显示目标会相应缩小,造成观看不方便。因为系统不会自动根据屏幕尺寸和分辨率关系相应的调整文字和图标大小。即手动调整也会因为微软一直采用的点阵字体和大多数位图在提高分辨率后,因为多余的像素点没有填充渲染会出现拉伸,进而会产生锯齿,这是系统不会自动适配的原因之一。这也给我们造成一种假象:显示器尺寸越大,分辨率就会越大。
所以苹果的Retina视网膜屏幕让很多人困惑不已,为什么那么小的屏幕会有那么大的分辨率。为什么那么大的分辨率,非但没有使文字和图像变小,反而更加清晰。
1.10 高像素密度屏幕(高ppi)
严格来说,高像素密度屏幕也是属于高分辨率屏幕,不同的是高像素密度屏幕在提升了分辨率的同时也提高了像素密度,即相同的尺寸有着更大的分辨率。以苹果的Retina视网膜屏幕为例,它并不是普通屏幕那样通过增大尺寸来增加分辨率,而是靠提升单位面积屏幕的像素数量,即像密度来提升分辨率,这样就有了高像素度屏幕。
同时操作系统也会采取相应的模式,如Mac下的HiDPI进行适配,将缩小的字体(苹果一直采用矢量字体)和图标重新放大,这样苹果用了更多的像素来显示同样的内容,显示尺寸仍然不变,但是多了更多细节,因此会有非常明显的视觉效果提升。
iPhone/iPod Touch
普通屏幕分辨率 ====> 320px X 480px
Retion分辨率 ====> 640px X 960px
iPad,iPad2/New iPad
普通屏幕分辨率 ====> 768px X 1024px
Retion分辨率 ====> 1536px X 2048px
换算关系
普通屏幕分辨率 ====> 1点=1像素
Retion分辨率 ====> 1点=2像素
1.11 Retina设备
Retina是苹果提出来的,根据苹果的定义,PPI高于210(笔记本电脑) ,260(平板电脑),300(手机)的屏幕就成为Retina屏
上图是部分苹果手机屏幕参数。
1.12 HTML和CSS的标准
尽管现在的显示设备大部分还是非Retina屏,但是如何使用Retina来优化web显示的方法如雨后春笋,越来越多的处理方案被提出。
最直接的方式就是通过手动制图或者编程的方式制作两种不同的图形,一张是普通屏幕的图片,另一种是Retina的图形,并且Retina屏幕下的图片像素是普通屏幕下的两倍。如有一张200X300像素的图片(css像素),然后制作一张400X600像素的图片,通过css或者html属性将其属性压缩50%,这时在一个标准分辨率的显示器上,其呈现的图像是位图的四分之一像素。简单的说,普通屏幕下的图像被压缩,减少像素取样(只是位图含像素的四分之一),同时,这样会造成资源浪费。把这个过程称为“Downsampled”。
在Retina屏幕下,相同的图像会使用四倍的物理像素显示,这样一来,每个物理像素最终完全配备一位图像素,使图像得到完全的呈现。
有几种方法可以实现这样的效果。
1. HTML属性
最简单的方式是通过img标签,设置width,height属性:
<img src="example@2x.png" width="200" height="300" />
注意,即使指定的图片高度是可选的,但是在加载图片前,浏览器已经预留了所需的空间。这样可以防止页面布局更改图片时,再加载一次。
2. 使用Javascript
同样的效果,可以通过Javascript脚本对图像(为Retina屏幕准备的图像)进行减半。
$(window).load(function() { var images = $('img'); images.each(function(i) { $(this).width($(this).width() / 2); }); });
3. 使用css
另外一种方法是通过css来实现。常见方式就是给元素设置一个背景图像,比如给div元素设置一个背景图像,最关键的是给这个背景图像设置background-size,来指定背景图像的大小,也可以给元素设置一个大小,然后给background-size设置一个contain属性值。不过IE7,IE8不支持这个属性的运用。
.image { background-image: url(example@2x.png); background-size: 200px 300px; /*或者设置background-size: contain; */ height: 300px; width: 200px; }
还可以使用元素的伪元素来代替
.image-container:before { background-image: url(example@2x.png); background-size: 200px 300px; content:''; display: block; height: 300px; width: 200px; }
HTML和CSS方法的
优点是:
很容易实现;
能跨浏览器兼容;
缺点是:
非Retina屏下必须下载更大的图片资源;
Downsampled图像在不同的分辨率下可能会失去一定的清晰度;
background-size在IE9下浏览器下支撑不友好;
1.13 查询图像密度
为Retina屏幕下查询web图像的像素密度,然后调用对应的图像。目前常用的方式是通过css或者Javascript来实现。
1. 使用CSS Media Queries
通过divice-pixel-ratio属性或者其扩展属性min-device-pixel-ratio和max-device-pixel-ratio。这几个Media Queries可以使用background-image为Retina准备高精度像素图片。
通过window.devicePixelRatio的比例1.5或2为不同的苹果设备做相应的查询。
.icon { background-image: url(example.png); background-size: 200px 300px; height: 300px; width: 200px; } @media only screen and (-Webkit-min-device-pixel-ratio: 1.5), only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (min-device-pixel-ratio: 1.5) { .icon { background-image: url(example@2x.png); } }
iPhone4
@media only screen and (-webkit-min-device-pixel-ratio : 1.5),only screen and (min-device-pixel-ratio : 1.5) { /* Styles */ }
Retina屏幕和普通屏幕
@media only screen and (min-width: 320px) { /* Small screen, non-retina */ } @media only screen and (-webkit-min-device-pixel-ratio: 2) and (min-width: 320px), only screen and ( min--moz-device-pixel-ratio: 2) and (min-width: 320px), only screen and ( -o-min-device-pixel-ratio: 2/1) and (min-width: 320px), only screen and ( min-device-pixel-ratio: 2) and (min-width: 320px), only screen and ( min-resolution: 192dpi) and (min-width: 320px), only screen and ( min-resolution: 2dppx) and (min-width: 320px) { /* Small screen, retina, stuff to override above media query */ } @media only screen and (min-width: 700px) { /* Medium screen, non-retina */ } @media only screen and (-webkit-min-device-pixel-ratio: 2) and (min-width: 700px), only screen and ( min--moz-device-pixel-ratio: 2) and (min-width: 700px), only screen and ( -o-min-device-pixel-ratio: 2/1) and (min-width: 700px), only screen and ( min-device-pixel-ratio: 2) and (min-width: 700px), only screen and ( min-resolution: 192dpi) and (min-width: 700px), only screen and ( min-resolution: 2dppx) and (min-width: 700px) { /* Medium screen, retina, stuff to override above media query */ } @media only screen and (min-width: 1300px) { /* Large screen, non-retina */ } @media only screen and (-webkit-min-device-pixel-ratio: 2) and (min-width: 1300px), only screen and ( min--moz-device-pixel-ratio: 2) and (min-width: 1300px), only screen and ( -o-min-device-pixel-ratio: 2/1) and (min-width: 1300px), only screen and ( min-device-pixel-ratio: 2) and (min-width: 1300px), only screen and ( min-resolution: 192dpi) and (min-width: 1300px), only screen and ( min-resolution: 2dppx) and (min-width: 1300px) { /* Large screen, retina, stuff to override above media query */ }
CSS Media Queries的优点:
只有对应的目标元素才会下载图片资源;
跨浏览器兼容;
像素可以精确控制;
CSS Media Queries的缺点:
单调无味的实现过程,代码冗长;
只能通过HTML元素的背景图片来实现,无语义化
2. 使用Javascript
使用Javascript中的window.devicePixelRatio进行判断,然后根据对应的值给Retina屏选择图像。
$(document).ready(function(){ if (window.devicePixelRatio > 1) { var lowresImages = $('img'); images.each(function(i) { var lowres = $(this).attr('src'); var highres = lowres.replace(".", "@2x."); $(this).attr('src', highres); }); } });
开源的Retina.js是为Retina而生,基本实现了上面所有功能。目前支持window.devicePixelRatio的浏览器不多,将来会有更多的浏览器添加这个api。
Javascript查询的优点:
易于实施;
非Retina屏幕不用下载额外的资源;
像素精确控制;
Javascript查询的缺点:
Retina屏幕下必须下载标准准备和高密度的两个资源;
Retina屏幕下图像交互可见;
浏览器兼容性不强;
1.14 可缩放矢量图形
不管用什么方式,光栅图像仍然有自己固定的图像分辨率,也就是其缩放始终受限于其像素,也绝对无法限制伸缩。但是矢量图就不一样,可以随意伸缩,而且无任何影响。在Retina屏幕下的web图形,矢量图具有先天优势。
目前,基于XML的svg格式制作的矢量图得到大部分浏览器的支持,可以使用svg绘制各种图形,并且在Retina屏幕下可以任意伸缩。开发过程中使用svg最简单的方法是通过HTML的img标签,CSS的background属性或者微元素的content中的url()。
HTML的img标签加载svg
<img src="example.svg" width="200" height="300" />
在这里是一个svg凸显更可以为普通屏幕和Retina屏幕的通用图像,可以做任何伸缩,而不会像光栅位图一样失真,而且资源统一,节省带宽,方便维护。
CSS中使用svg图像
svg图像可以像普通图像一眼个,当作元素的背景图片来使用:
.image { background-image: url(example.svg); background-size: 200px 300px; height: 200px; width: 300px; }
除了当作背景图片之外,还可以通过伪元素的“content”来调用:
.image-container:before { content: url(example.svg); }
如果想在IE7,IE8和Android2.x上使用,要用png图片来代替svg图像
.image { background-image: url(example.png); background-size: 200px 300px; } .svg { .image { background-image: url(example.svg); } }
在HTML标签中,给img标签定义一个属性,给这个自定义属性设置一个png图片,以做备用,不过这种方式需要使用Javascript配合使用。
$(document).ready(function(){ if(!Modernizr.svg) { var images = $('img[data-png-fallback]'); images.each(function(i) { $(this).attr('src', $(this).data('png-fallback')); }); } });
SVG矢量图的优点:
一个资源适合所有的设备
易于维护
面向未来,可伸缩向量图形
SVG矢量图的缺点:
没有像素那样有精度
由于文件大小,不适合复杂的图形
不支持IE7-8和部分安卓版本
最后开发人员如何让网站适应Retina屏幕呢,看下面的总结:
2. H5页面适配的一些基本概念
2.1 视窗viewport
viewport的功能在于控制网站最顶层容器,即html元素。假设我们定义一个可变尺寸的布局,且定义了一个侧边栏的宽度为10%,当改变浏览器窗口大小时,改侧边栏会自动扩展个收缩。这是什么原理呢?侧边栏宽度是父元素宽度的10%,假设父元素是<body>,那么问题就变成了<body>的宽度到底是多少?通常一个块元素占父元素100%的宽度,所以body元素的宽度就是其父元素<html>的宽度。
那么<html>的宽度又是多少呢?它的宽度是浏览器的宽度。所以侧边栏宽度10%会占用10%的浏览器宽度。<html>的宽度受viewport所限制,<html>元素为viewport宽度的100%。
反过来,viewport是严格的等于浏览器的窗口。viewport不是一个html概念,所以它不会被CSS属性修改。它就是为浏览器窗口的宽度高度,在桌面浏览器上是如此,在移动端有些复杂。
简单的理解,viewport是严格等于浏览器窗口。在桌面浏览器中,viewport就是浏览器窗口的宽度高度。但是在移动端有些复杂,移动端viewport太窄,为了更好的伪css布局服务,所以提供了两个viewport:虚拟的,visual viewport和布局的layout viewport。
设备的pixels和css的pixels
相对css像素来说,设备像素的概念比较单一。我们的网页在什么样的设备上运行呢?设备像素给出了这个答案,可以从window.screnn.width/height(大多数情况下)读到。
设备的pixels和设备本身有关,简单来说就是window.screen.width/window.screen.height,可以认为设备的pixels是正确,标准的宽和高,这些pixels决定了设备上正式的分辨率。
假设设置页面中一个元素的宽度是128px,显示器的宽度是1024px,且没有拖拉显示器的右上角是的显示器变小,那么可以在浏览器中放置8个这样的元素(忽略掉margin,padding等这些概念)。
如果用户缩放(zoom)了浏览器,情况就改变了。如果浏览器放大到200%,结果是只能在浏览器中放置4个这样的元素来占满显示器1024的宽度。
现代浏览器都实的“缩放”(zoom)功能,除了可以“拉伸”像素之外没有什么作用,结果是html元素的宽度没有因为缩放200%而由128pix变成256px,而是实际的的pixels被计算成了双倍。html元素在形式上依然是128css的pixels,但是它占用了256设备pixels。换言之,缩放200%将一个单位的css的pixels变成了4倍的设备的pixels那么大,即宽度*2,高度*2,面积*4。
用一些图片来说明这个概念,下面图片中有4个正常缩放(100%缩放)的像素,缩放为100%的html元素,css的pixels完整和设备的pixels重叠。
当我们缩小浏览器时,css的pixels开始收缩,导致1单位的设备pixels上重叠了多个css的pixels,如下:
同理,放大浏览器的时候,相反的事情发生了,CSS的pixels开始放大,导致1单位的CSS的pixels上重叠了多个设备的pixels。
问题在于,设备的pixels几乎毫无用处,开发者只关注css的pixels,这些pixels指定了html元素如何渲染。但是对用户来说是另外一回事,用户会缩放界面,直至可以舒适的阅读网页内容,但是开发者不需要关注这些缩放级别,浏览器会自动的保证css的pixels会被伸展还是收缩。
100%缩放
在缩放级别是100%的时候,1单位的CSS的pixels是严格等于1单位的设备pixels。
100%缩放的概念非常有利于表达下面的问题,在日常开发中不必过分担忧这个问题。在桌面系统中,通常会在100%缩放级别下测试网站,即使用户缩放,css的pixels的魔法依然可以保证网站外观保持相同的比例,开发者不必过分担心。
屏幕尺寸 Screen size
screen.width/height
含义:用户的屏幕的完整大小
度量:设备的pixels
兼容性:IE8里,不管使用IE8模式还是IE7模式,都是以CSS的pixels来度量
这两个属性包含用户屏幕的完整高度和宽度。这些尺寸使用设备的pixels来定义,他们的值不会因为缩放而改变,他们是显示器特征,而不是浏览器。我们可以拿着个来做什么呢?除了获取用户屏幕信息之外用到的地方很少。
浏览器尺寸window size
window.innerWidth/window.innerHeight
含义:包含滚动条尺寸的浏览器完整尺寸
度量:CSS的pixels
兼容性问题:IE不支持,Opera用设备pixels来度量
浏览器内部尺寸,很明显它定义了当前用户有多大区域可供CSS布局使用。可以用window.innerWidth和window.innderHeight来获取。窗口的内部宽度使用CSS的pixels,开发时需要知道自己定义的元素是否能塞入浏览器窗口,这两个尺寸会随着用户放大(缩放)浏览器而减少。所以当用户放大显示时,能获取的浏览器窗口可用空间会减少。window.innerWidth/window.innerHeight就是缩小的比例。
(在Opera浏览器上有些异常,window.innerWidth/innerHeight不会随着用户放大(缩放)浏览器而减小,它以物理像素衡量。这在桌面端非常讨厌,但是在移动端会带来致命的结果,稍后讨论)
注意,窗口内部宽度和高度的尺寸,包含了滚动条的尺寸,这是历史原因。
滚动移位 Scrolling offset
含义:页面的移位
度量:CSS的pixels
兼容性问题:window.pageXOffset和window.pageYOffset,在IE8及之前的IE浏览器不支持。使用document.body.scroolLeft和document.body.scrollTop来取代
window.pageXOffset和window.pageYOffset定义了页面(document)相对于窗口原点的水平,垂直位移。因此可以定位用户滚动了多少滚动条距离。注意因为页面中的内容是可以无限添加的,页面滚动的距离可能比页面实际可见的高度要大得多。
理论上,如果用户滚动并放大(缩放)浏览器,window.pageXOffset/pageYOffset会改变,但是,现代浏览器会尽量保持在屏幕上方被卷入的元素有相同的尺寸。实际情况是,这不一定都会起作用,实际上,通常情况下,window.pageXOffeset/pageYOffset并不会改变。
该属性也以CSS的pixels来度量,同上面的问题,当用户放大页面时,浏览器会尝试着保存用户当前可见的页面的元素依然在可见位置。虽然这个特性表现的不如预期,但是它意味着,理论情况下window.pageXOffset/window.pageYOffset并没有改变,被用户滚出屏幕的CSS的pixels几乎保持不变。
概念:视窗viewport
viewport的功能在于控制网站的最高级别的块容器,<html>元素。举个例子,定义个一个可变尺寸的布局,且定义了一个侧边栏尺寸为10%,当改变浏览器窗口大小的时候,该侧边栏会自动扩张和收缩。侧边栏是父元素body宽度的10%,那么body的宽度又是多少呢?通常一个块级元素占有父元素的100%的宽度,所以body的宽度就是其父元素html的宽度。那么html元素到底有多宽?因为它的宽度恰好是浏览器的宽度。所以侧边栏的宽度就是浏览器宽度的10%。通常是这么个道理,实际上html宽度受viewpoint所限制,html元素为view宽度的100%。
viewpoint被定义成严格等于浏览器的窗口,它不是一个html的概念,不能通过css定义修改它。在PC端他的宽度和高度严格等于浏览器的高度,在移动端,情况有些复杂。
结果
viewport的定义会带来一些奇怪的后果。缩小(拖动浏览器右上角)浏览器,按住Ctrl键,向上滚动鼠标滚轮2至3格,放大(缩放)浏览器,拖动滚动条到网页顶部,会发现蓝色的页眉有些错乱,没有占满整个浏览器。如下图:
这是由于viewport的局限带来的后果。顶部蓝色页眉的宽度是这样定义的width:100%; 它的宽度应该是html元素的100%,viewport的宽度又是真个浏览器宽度的100%。
关键在于,在浏览器100%大小(缩放)情况下完全没有问题,现在放大(缩放)了。viewport变得比网站宽度小(这时css像素比物理像素要小,浏览器的宽度已经变窄了(拖放),页眉的宽度要比浏览器小),,overflow:visible;元素默认超出父元素时正常显示。
页眉并没有溢出,是因为页眉的宽度css定义为width:100%,即为viewport宽度的100%,它没有意识到因为这时viewport的宽度已经变窄了。
document width
网页文档的宽度,包含溢出的那部分是多少呢?现在没有一个api能够告诉我们,除非计算出每个元素的宽度,包含margin,padding,border等诸多因素,这是非常容易出错的。
如果能有一个javascript属性能够告诉我们这个document width就好了。如果有一个文档宽度,就可以设置蓝色页眉的宽度为文档宽度的100%。
度量viewport
含义:viewport的尺寸
度量:css的pixels
兼容性问题:无
viewport的尺寸可以用document.documentElement.clientWidth/clientHeight来获取。document.documentElement实际代表整个网页的根元素:html元素。实际这对属性描述的是viewport的尺寸,它描述的是viewport的尺寸,而不管html元素的尺寸如何改变。
描述viewport的两种方式
上文中已经说了,window.innerWidth/innerHeight是viewport的宽度和高度吗?嗯, 是, 也不是。严格说这两对属性有细微的差别。
document.documentElement.clientWidth/clientHeight是viewport的不包含滚动条长度的宽度和高度
window.innerWidht/innerHeight是viewport的包含滚动条长度的宽度和高度
之所以会出现两套属性来描述viewport的宽度和高度是浏览器大战留下的遗物。那个时候网景公司支持window.innerWidth/innerHeight,IE只支持document.documentElement.clientWidth/clientHeight,尽管其他浏览器都开始支持clientWidth/clientHeight,但是IE仍然没有支持,仅仅支持innerHeight/innerWidth。
桌面端有两种方式获取viewport的尺寸是一个困惑,但是在移动端确是一个福利。
度量html元素
document.documentElement.offsetWidth/offsetHeight用来表示html元素的尺寸,包含看不见的部分。
含义:<html>的尺寸
度量:css的pixels
兼容性问题:IE用这个描述viewport的尺寸而不是<html>
用这一对属性可以获取html元素的宽度和高度,可以通过设置html元素的css属性来改变它。
事件坐标
含义:当发生鼠标事件的时候,至少会有5个属性来暴露当前鼠标的位置信息,下面来看其中的个属性
度量:见下文
兼容性问题:IE不支持pageX/pageY,IE使用screenX/screenY,css的pixels
定义:1. pageX/pageY描述事件相对于html元素的坐标,css的pixels
2. clientY/clientY描述事件相对于viewport的坐标,css的piexles
3. screenX/screenY描述事件相对于屏幕的坐标,css的pixels
通常我们会有90%的可能来使用pageX/pageY,即相对于html元素的尺寸,其余10%的可能会使用clientX/clientY,即相对于viewport的尺寸,screenX/screenY这对属性通常没有用。
媒体查询
含义:见下文
度量:见下文
兼容性:IE不支持媒体查询,在firefox中,使用screen.width/height,而不是divice-width/divice-height,使用css的pixels;对于width/height,Safari和chrome使用document.documentElement.clientWidth/clientHeight的值(如果他们是以设备像素为单位的话)
媒体查询的思路是很简单的,定义在不同页面尺寸下的css的规则,比如大于,小于,等于某一个尺寸,看下面的例子:
div.sidebar { width: 300px; } @media all and (max-width: 400px) { // styles assigned when width is smaller than 400px; div.sidebar { width: 100px; } }
当前sidebar是300px,当宽度小于400px的时候变成100px。
问题是以哪个宽度为依据的呢?有两类媒体查询,widht/height,divice-width/divice-height。
1. width/height使用的是document.documentElement.clientWidth(viewport,不带滚动条),使用css的pixels
2. divice-width/divice-height使用的是screen.width/screen.height(显示器的宽度和高度),使用物理像素
毫无疑问,我们要使用with/height,显示器宽度和高度对开发者来说意义不大,浏览器开发商才会关心这个。在移动端,情况会稍微简单点,接下来讨论这些问题。
移动端浏览器的问题
和pc端相比,移动端最大的不同是屏幕尺寸。移动端尺寸要小,显示的内容也要比pc端少,要么缩小导致网页内容难以辨认,要么只显示网页中的一小部分。
移动端屏幕要比pc端小很多,通常最大也就400px,当前虽然有些制造商声称他们的屏幕很大,但是他们往往夸大其词。一些折中的方案,比如ipad或者其他的平板电脑介于手机和pc之间,但是这些也没有根本解决问题。移动端大潮不可避免,网页必须能在移动端工作,所以想方设法是得网页在移动端一样表现良好。
大多数问题集中在css上,特别是viewport的度量上。如果我们简单地把pc端网页拷贝到移动端,样式将变得奇丑无比。
这里我们继续用sidebar的宽度10%这个问题来讨论。如果移动端行为和pc端一样,那么这个sidebar最多也只有40px,这个尺寸就太小了。一种解决方式是为移动端再做一个网站。暂且不说这个网站要怎么做,通常只有少部分公司才会投入成本来做这件事,为移动端单独做一个网站。移动浏览器厂商则希望他们的软件提供更好的体验,“尽量像pc端一样的体验”,所以他们就会无所不尽其能。
两种viewports
移动端的viewport对于css布局来说非常小,一种解决方案是把viewport变得大一点。这种需求就把viewport分成两种:虚拟viewport,布局viewport。
George Cummins(乔治.康明斯,不知道是谁,应该是前端大牛把)对着两种viewport给出了这样的解释:
可以把布局viewport想象成一个不能改变大小和形状的图片。现在用一个小一点的框来观察这个大图片。这个小框有被不透明的材料包裹,因此通过这个框只能观察到这个图片的一部分。通过这个框能够观察到的部分图片就是虚拟viewport,如果我们后移这个框,站远一点,就能看到整张图片了,相当于放大(缩放)了这个框。你也可以改变这个框(可视viewport)的方向,但是后面的图片(布局viewport)是不会改变的。
虚拟viewport是我们当前能够在移动端显示器上看到的部分。用户可能会滚动来改变可见部分,放大,缩小,都可以修改这个可视viewport。
但是,css布局中,尤其是百分比宽度,是按照布局layout来计算的,这样一来就要比按照可视viewport计算要宽得多了。
所以,默认情况下,html元素继承了布局viewport的宽度,css布局或按照这个宽度来计算,这使得网站在移动端尽量贴近pc端样式。
布局viewport有多宽呢?根据浏览器不同而不同,Safari iPhone是980px,Opera是850px,AndroidWebkit是800px,IE是974px。部分浏览器行不同:
- Symbian Webkit的布局viewport和可是viewport是一致的,这意味着按照百分比来定义的元素的尺寸会很小,很难看。如果页面中有个别元素有特定的尺寸而不适合虚拟viewport,那么宽度的最大值将会被设置成850px
- Sumsung Webkit中的布局layout以网页中最宽的元素为准
- 黑莓的布局viewport在缩放比例为100%的时候和虚拟viewport一样。
缩放
两种布局都是以css的pixels来度量的,但是当缩放比例变化的时候可视viewport也会改变(缩放比例变大的时候,一个css的pixels会占据多个物理的pixels,可视范围内css的pixels变少),布局viewport的度量保持不变。这是页面会重绘,元素的百分比尺寸会被重新计算。
理解布局viewport
为了理解布局layout的尺寸,我们先看看当王爷缩小到时候回有什么情况发生。许多移动端浏览器默认以最小缩放比例打开王爷,这样可以看到王爷所有内容。关键点在于,浏览器选择最小缩放比例模式,以便屏幕能够显示所有的网页。
因此,布局视图的宽度和高度和最小缩放模式下在屏幕中能够显示的内容的宽和高。当用户放大(缩放)的时候,保持不变。
布局视图不变,但是在旋转(横放)模式下可视viewport改变,浏览器通过稍微放大以适应这一新方向,这样布局viewport再次与可视viewport再次保持一致。
这对布局viewport有一些影响,现在的高度比纵向模式要小得多。但是开发人员不会关心屏幕高度,只关心宽度。
度量视图viewport
document.documentElement.clientWidth/clientHeight
含义:布局viewport的尺寸
度量:css的pixels
完整支持:Opera,iPhone,Android,Symbian,Bolt,MicroB,Skyfire,Obigo
问题:
- 在Iris上它标示可视视图;
- 三星的webkit核心浏览器,仅当在页面上写入<meta viewport>标签,才能正确标示。否则就代表html元素的尺寸;
- Firefox中用设备的pixels来衡量;IE浏览器中返回的值是1024*768,正确的获取方式是document.body.clientWidth/clientHeight;
- NetFront仅当100%缩放的时候才正确;
- 塞班班的weibkit1不支持这些属性;
- 黑莓不支持
很幸运,虽然有两种viewport,浏览器大战留下了两对属性来衡量他们。document.documentElement.clientWidth/clientHeight代表可视viewport的度量
旋转模式下只影响到高度,不影响宽度。
度量可视viewport
window.innerWidth/innerHeight
含义:可视viewport的度量
度量:css的pixels
完整支持:iPhone,Symbian,BlackBerry
问题:
- FireFox和Opera返回的是物理的pixels;
- Android,Bolt,MicroB,NetFront返回的是可视viewport的css的pixels度量;
- IE不支持这个属性,但是IE给出了document.documentElement.offsetWidth/offsetHeight来代替;
- 三星的Webkit浏览器,仅当在页面上写入<meta viewport>标签,才能正确标示。否则代表<html>元素的尺寸;
- 在Iris,Skyfire,Objgo上很混乱;
可视viewport使用window.innerWidth/innderHeight来衡量。显然,随着用户缩放浏览器,这个值会改变,屏幕上的css的pixels会改变。
不幸的是,这对属性兼容性很差,很多浏览器没有支持,但愿将来有更多浏览器实现它,使它成为一个标准。
屏幕尺寸度量
screen.width/height
含义:屏幕的尺寸;
度量:物理的pixels;
完全支持:Opera,Android,Symbian,Iris,Firefox,MicroB,IE,BlackBerry;
问题:
- Opera在Window Mobile下只给出了横向尺寸,在S60还是错误的;
- 三星的Webkit核心浏览器,仅当在页面上写入<meta viewport>标签,才能正确表示,否则代表html元素的尺寸;
- iPhone和Obigo仅给出纵向尺寸;
- Android,Bolt,MicroB,NetFront以CSS的pixels返回该数值,且为布局的viewport的值;
- Bolt,Skyfire下很混乱;
pc端中screen.width/height给出的是屏幕尺寸,用物理pixels衡量,作为web开发者,这个不常用。同样在移动端,你不用屏幕上有多少物理pixels,只关心有多少css的pixels。
缩放等级zool level
无法直接获取缩放等级,但是可以通过计算获取:screen/width/window.innerWidth,当然只有在这两个属性都被支持的情况下才能这样获取。幸运的是,缩放等级并不重要,只需要知道当前有多少css的pixels能供使用就可以了。在浏览器支持的情况下,使用window.innerWidth获取这一信息。
滚动scrolling offset
window.pageXOffset/pageYOffset
含义:见下文;
度量:css的pixels;
完整支持:iPhone,Android,Symbian,Iris,MicroB,Skyfire,Obigo;
问题:
- Operia,Bolt,Firefox,NetFront总是返回0;
- 三星的webkit核心浏览器,仅当在页面上写入<meta viewport>标签才起能获取;
- IE,BlackBerry不支持,在IE中使用document.documentElement.scrollLeft/scrollTop来获取;
通常还要知道当前可视viewport相对于布局viewport的位置,这就是滚动,和pc端一样,可以通过window.pageXOffset/pageYOffset获取。
<html>元素的度量
document.documentElement.offsetWidth/offsetHeight
含义:真个html元素的尺寸
度量:css的pixels
完整支持:Opera, iPhone, Android, Symbian, Samsung, Iris, Bolt, Firefox, MicroB, Skyfire, BlackBerry, Obigo
问题:只有在100%缩放下NetFront浏览器返回的值才是正确的;IE使用这个属性来输出可视viewport,html元素尺寸使用document.body.clientWidth/clientHeight来输出;
和桌面端一样,移动端也使用document.documentElement.offsetWidth/offsetHeight来表示html元素的尺寸,用css的pixels度量。
媒体查询
含义:已css的pixels度量html元素或者已设备的pixels来度量设备;
完整支持:Opera, iPhone, Android, Symbian, Samsung, Iris, Bolt, Firefox, MicroB;
不支持:Skyfire, IE, BlackBerry, NetFront, Obigo;
注意:这里测试了浏览器是否明确地出了这些值,这些值的准确性没有测试;
移动端媒体查询和桌面端类似,width/height使用以css的pixels度量的布局viewport,divice-width/divice-height使用物理pixels来度量。换句话说width/height就是document.clientWidth/clientHeight,divice-width/divice-height就是screen.width/height(所有浏览器都是这样,即使属性的值是错误的)
开始我认为divice-width/divice-height比较重要,因为它给出了我们所使用设备的屏幕的尺寸信息。例如,可以根据设备尺寸来设置不同的网页尺寸。但是同样可以定义<meta viewport>来达到这个目的。所以divice-width/divice-height不是不可替代的。
浏览器厂商自认为他们通过width/height给出了一些对网站有用的尺寸细节,但是这些内容含糊,混乱。所以媒体查询在区分当前设备是移动设备(手机,pad,pda)还是pc谁被时很有用,但是在区分是当前手机(或pad,pda)的尺寸上用处没有那么大。
事件坐标event coordinates
含义:见下文
度量:见下文
完整支持:Symbian,Iris
问题:
Opera移动端给出了pageX/pageY,但是当屏幕滚动查出范围时,这个值是错误的;
在iPhone,firefox,blackberry上,clientX/clientY和pageX/pageY相等的;
在Android和MicroB上screenX/screenY和clientX/clientY是相同的,css的pixels度量;
在Firefox上screenX/screenY是错误的;
IE,blackberry,Obigo不支持pageX/pageY;
在NetFront上三种尺寸的值都用screenX/screenY;
在Obigo上clientX/clientY是用screenX/screenY来表示;
在三星webkit浏览器上总是返回pageX/pageY;
时间坐标在pc端上大致是支持的,不幸的是移动端上只有symbian,Irias完全支持,其他的移动端浏览器或多或少都有些问题。
pageX/pageY依然是基于css的pixels度量,和pc端浏览器删狗一样,它是三个特性里面最有用的。
clientX/clientY是聚义可视viewport的,以css的pixels为度量的,这样比较可靠。screenX/screenY基于屏幕的物理的pixels为度量的。同样,它也是没有什么用的。
meta viewport标签
含义:设置可视viewport的宽度
度量:css的pixels
完整支持:Opera Mobile, iPhone, Android, Iris, IE, BlackBerry, Obigo
不支持:Opera Mini, Symbian, Bolt, Firefox, MicroB, NetFront
问题:
- Skyfire不支持;
- 三星浏览器,如果在页面中设置<meta viewport>标签,会有其他的属性受到影响;
- opera,iPhone,三星,blackberry不允许用户缩放;
最后,我们讨论<meta name="viewport" content="width=320">这个标签。起初,它是apple的一个扩展,后来很多浏览器都实现了这个扩展。它意图重置可视viewport,下面我们来讨论为什么会出现这个扩展。
假设我们有一个简单的网页,所有元素都没有设置css宽度。现在设置缩放比例为100%,大多数浏览器会在屏幕上显示所有布局viewport,如下:
当我们放大(缩放)浏览器的时候,大多数浏览器都保存元素完整的宽度(保持元素位置不变)导致阅读困难(文字超过屏幕)。
(Android浏览器有些特殊,它会减小包含文字的元素的尺寸,保证他们仍然完全显示在浏览器上,我觉得这个特性很酷,其他浏览器应该学习)
现在设置html元素的样式{ width: 320px },现在html元素收缩,其他元素通过100%继承320px。在用户放大(缩放)的情况下正常,但是在最初期状态下缩小(缩放)的时候页面几乎没有什么内容,体验很糟糕。
为了解决这个问题,apple发明了meta视图标签,当设置标签<meta name="viewport" content="width=320">,可以将布局viewport设置为320px,现在初始状态下页面的显示就变得正常了。
通过这个标签可以将布局viewport的宽度设置为你需要的任意尺寸,甚至是divice-width。通过参考screen.width(物理的pixels)可以设置布局viewport尺寸。
可以使用钩子,有时候严格的screen.width一点意义都没有,因为pixels的值太大。例如Nexus One上的严格宽度是480px,但是google的工程师觉得在用户谁知device-width的时候,设置为480太大了,他们将这个值减小为原来的2/3,为320px,和iPhone上一致。
3. 使用Flexible实现H5适配
3.1 痛点
虽然h5的页面比pc端简单不少,但是要想匹配所有的设备是一件非常痛苦的事情,因为移动端设备太多了。
3.2 flexible适配方案
为了适应这么多的移动端谁被,设计师和前端开发之前应该采用什么样的协作模式呢?手淘团队曾经的协作思路是:
- 选择一种尺寸作为设计和开发的基准
- 定义一套适配规则,自动适配剩下的尺寸
- 特殊适配效果单独处理
看下图:
手淘设计师通常会选择iPhone6作为基准设计尺寸,交付给前端开发的设计尺寸是按照750px/1334px为准,当然高度会随内容增多和改变。前端开发通过一套适配规则自动适配到其他的尺寸。经过多年的摸索实践,总结了一套移动端适配方案—flexible方案。
3.3 使用方法
lib-flexible库的使用方法非常简单,只需要在web页面的<head></head>之间添加对应的flexible_css.js,flexible.js文件,可以直接下载文件,然后放在项目目录中:
<script src="build/flexible_css.debug.js"></script> <script src="build/flexible.debug.js"></script>
可以使用CND
<script src="http://g.tbcdn.cn/mtb/lib-flexible/0.3.4/??flexible_css.js,flexible.js"></script>
强烈建议对js做内联处理,在所有资源加载之前执行这个js。执行之后会在<html>元素上增加一个data-dpr属性,以及一个font-size样式。JS会根据不同的设备添加不同的data-dpr值,比如2或者3,同时给html加上对应的font-size的值,比如说75px。
如此一来,页面的元素都可以通过rem单位来设置,他们会根据html的font-size值做相应的计算,从而实现屏幕的适配效果。
除此之外,在引入lib-flexible需要执行的js之前,可以手动设置mea来控制dpr的值。例如:
<meta name="flexible" content="initial-dpr=2" />
其中initial-dpr强制设置为给定的值。如果手动设置了dpr之后,不过设备的dpr是多少,都会强制认为其dpr是我们设置的这个值。在此不建议手动强制设置dpr,因为在flexible中只对ios设备进行dpr的判断,android系列的,始终认为dpr=1
if (!dpr && !scale) { var isAndroid = win.navigator.appVersion.match(/android/gi); var isIPhone = win.navigator.appVersion.match(/iphone/gi); var devicePixelRatio = win.devicePixelRatio; if (isIPhone) { // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案 if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) { dpr = 3; } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){ dpr = 2; } else { dpr = 1; } } else { // 其他设备下,仍旧使用1倍的方案 dpr = 1; } scale = 1 / dpr; }
3.4 flexible的实质
flexible实质是通过js来动态改写meta标签,代码类似这样:
var metaEl = doc.createElement('meta'); var scale = isRetina ? 0.5:1; metaEl.setAttribute('name', 'viewport'); metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no'); if (docEl.firstElementChild) { document.documentElement.firstElementChild.appendChild(metaEl); } else { var wrap = doc.createElement('div'); wrap.appendChild(metaEl); documen.write(wrap.innerHTML); }
事实上它做了这样几件事情:
- 动态改写<meta>标签
- 给<html>元素添加data-dpr属性,并动态改写data-dpr的值
- 给<html>元素添加font-size属性,并且动态改写font-size的值
3.5 px转化成rem
接下来就需要把视觉稿中的px转换成rem。首先,目前日常工作中,视觉设计给到前端开发人员的视觉稿尺寸一般都是640px,750px,以及1125px宽度为准(考虑Retina屏),如何转换成rem呢?
这里拿一个推广页做例子,视觉稿的宽度是750px,下面看看如何将其中的个元素px转换成rem。
手淘的视觉设计师把相关的信息在视觉稿上标注出来。flexible会将视觉稿分成100等份(为了更好兼容vh,vw),每一份被称为一个单位a,同时1rem单位被认定为10a。针对这样的视觉稿可以算出:
1a = 7.5px
1rem = 75px
这个例子的稿子就分成了10a,也就是整个宽度为10rem,<html> 对应的font-size为75px。这样视觉稿上的元素,只需要原始的px值除以rem基准即可。例如,此比例视觉稿中的图片,尺寸是176px/176px,转换为2.346667rem/2.346667rem
3.6 如何快速计算
实际生产中,每次都要计算px转rem,会非常麻烦。阿里开源了一些插件来解决,例如cssrem是一个css转rem的Sublime Text3的插件。
css处理器
除了使用编辑器插件,还可以使用css处理器来帮助我们处理,比如sass,less,postcss这样的处理器。
sass
熟悉sass的开发人员,可以使用sass的函数,混合宏来实现这个功能。
sass函数大致如下:
@function px2em($px, $base-font-size: 16px) { @if (unitless($px)) { @warn "Assuming #{$px} to be in pixels, attempting to convert it into pixels for you"; @return px2em($px + 0px); // That may fail. } @else if (unit($px) == em) { @return $px; } @return ($px / $base-font-size) * 1em; }
Sass的混合宏代码如下:
@mixin px2rem($property,$px-values,$baseline-px:16px,$support-for-ie:false){ //Conver the baseline into rems $baseline-rem: $baseline-px / 1rem * 1; //Print the first line in pixel values @if $support-for-ie { #{$property}: $px-values; } //if there is only one (numeric) value, return the property/value line for it. @if type-of($px-values) == "number" { #{$property}: $px-values / $baseline-rem; } @else { //Create an empty list that we can dump values into $rem-values:(); @each $value in $px-values{ // If the value is zero or not a number, return it @if $value == 0 or type-of($value) != "number" { $rem-values: append($rem-values, $value / $baseline-rem); } } // Return the property and its list of converted values #{$property}: $rem-values; } }
PostCss
除了Sass这样的CSS处理器之外,还可以使用px2rem工具,使用gulp任务来处理:
var gulp = require('gulp'); var postcss = require('gulp-postcss'); var px2rem = require('postcss-px2rem'); gulp.task('default', function() { var processors = [px2rem({remUnit: 75})]; return gulp.src('./src/*.css') .pipe(postcss(processors)) .pipe(gulp.dest('./dest')); });
3.7 字号不用rem
上面介绍使用rem来完成h5适配,文本又要怎么处理呢?是不是也通过rem来处理呢?显然,我们在iPhone3G和iPhone4的retina屏下,希望看到的文本字号是相同的,也就是说,我们不希望文本在Retina屏幕下变小,另外,大屏幕手机上希望看到更多的文本。现在绝大多数的字体文件都自带一些点阵尺寸,通常是16px和24px,所以我们不希望出现13px和15px这样奇葩的尺寸。
如此一来,就决定了在制作h5页面中,rem不适合用到段落文本上。在flexible整个适配方案中个,考虑文本还是使用px作为单位。只不过使用[data-dpr]属性来区分不同的dpr下的大小:
div { width: 1rem; height: 0.4rem; font-size: 12px; // 默认写上dpr为1的fontSize } [data-dpr="2"] div { font-size: 24px; } [data-dpr="3"] div { font-size: 36px; }
为了更好的利于开发,实际开发中定制一个font-dpr()这样的sass混合宏:
@mixin font-dpr($font-size){ font-size: $font-size; [data-dpr="2"] & { font-size: $font-size * 2; } [data-dpr="3"] & { font-size: $font-size * 3; } }
有了这样的混合宏之后,在开发中可以直接这样使用:
@include font-dpr(16px);
这只是针对描述性的文本,比如段落文本,有时候文本字号也是要分场景的,比如项目中有个slogan,产品希望这个slogan能根据不同终端适配。针对这样的场景,完全可以使用rem给这个slogan做计量单位。
H5适配方案有很多,flexible只是其中一种。
4. 使用vm,vh实现移动端布局
4.1 Flexible回顾
flexible解决了针对H5页面布局的适配问题,这是一套成熟的方案。为了适配不同的终端,通过Hack手段根据设备的dpr设置viewport的值:
<!-- dpr = 1--> <meta name="viewport" content="initial-scale=scale,maximum-scale=scale,minimum-scale=scale,user-scalable=no"> <!-- dpr = 2--> <meta name="viewport" content="initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no"> <!-- dpr = 3--> <meta name="viewport" content="initial-scale=0.3333333333,maximum-scale=0.3333333333,minimum-scale=0.3333333333,user-scalable=no">
从而让页面达到缩放的效果,同时也实现了页面的适配功能,主要思想有三点:
1. 根据dpr的值来修改viewport实现1px的线
2. 根据dpr的值来修改html的font-size,从而使用rem实现等比缩放
3.使用Hack手段用rem模拟vw特性
4.2 适配方案
随着前端技术不断变化,flexible不再是最佳方案。移动端布局有两个最重要的问题:
1.各终端下的适配问题
2.Retina屏的细节处理问题
不同终端,面对的屏幕分辨率,DPR,1px,2px图等一些列的问题。这个布局方案也是针对性的解决这些问题,只不过不再使用Hack手段来处理,而是直接使用原生的CSS技术来处理。
4.3 适配终端
首先要解决的是适配终端,flexible方案是通过javascript来模拟vm的特性,到今天为止,vw已经得到众多浏览器的支持,也就是说,可以直接考虑将vw用到我们的适配布局中。
众所周知,vw是基于Viewport视窗的长度单位,这里视窗(Viewport)指浏览器可视化区域,而这个可视化区域是window.innerWidth/window.innerHeight的大小,如下图:
在CSS Values and Units Module Level 3这篇文章中介绍了和Viewport相关的4个单位,分别是vw,vh,vmin,vmax:
vw:是Viewport's width的简写,1vw代表window.innerWidth的1%
vh:和vw类似,是Viewport's height的简写,1vh代表window.innerHeight的1%
vmin:是当前vw和vh中的较小的值
vmax:是当前vw和vh中的较大的值
vmin和vmax是根据Viewport中长度偏大的那个唯独值来计算出来的,如果window.innerHeight>window.innerWidth则vmin取window.innerWidth的百分之一,vmax取window.innerHeight的百分之一。如下图:
所以在这个方案中,大胆使用vw来替换之前flexible中rem缩放方案。先来回归到实际业务中,目前出现的视觉设计稿,大部分是使用750px宽度的,从上面的定义中可以看出100vw = 750px,即1vw=75px,可以根据设计图上的px值直接转换成对应的vw。看到这里也需要问,每次都要计算,很麻烦,能不能简单一点呢?其实我们可以使用PostCSS插件postcss-px-to-viewport,让我们可以在代码中直接使用px,如下:
[w-369]{ width: 369px; } [w-369] h2 span { background: #FF5000; color: #fff; display: inline-block; border-radius: 4px; font-size: 20px; text-shadow: 0 2px 2px #FF5000; padding: 2px 5px; margin-right: 5px; }
经过PostCSS编译之后就是我们所需要的vw代码:
[w-369] { width: 49.2vw; } [w-369] h2 span { background: #ff5000; color: #fff; display: inline-block; border-radius: .53333vw; text-shadow: 0 0.26667vw 0.26667vw #ff5000; padding: .26667vw .66667vw; } [w-369] h2 span, [w-369] i { font-size: 2.66667vw; margin-right: .66667vw; }
实际使用的时候,可以对插件进行相关的参数配置,如下:
"postcss-px-to-viewport": { viewportWidth: 750, viewportHeight: 1334, unitPrecision: 5, viewportUnit: 'vw', selectorBlackList: [], minPixelValue: 1, mediaQuery: false }
假设你的设计稿不是750px而是1125px,那么就可以修改viewportWidth的值为1125px。更多的介绍可以参考使用文档。
4.4 使用场合
上面解决了px到vw的转换计算,那么在那些地方可以使用vm来适配我们的页面呢,根据相关的测试:
1. 容器适配,可以使用vw
2. 文本适配,可以使用vw
3. 大于1px的边框,圆角,阴影都可以使用vw
4. 内边距和外边距可以使用vw
长宽比
另外还有一个细节需要提出,例如我们有一个这样的设计:
如果直接使用:
[w-188-246] { width: 188px; } [w-187-246]{ width: 187px }
最终效果会造成[w-187-246]容器的高度小于[w-188-246]。这个时候就要考虑到容器的长宽比缩放。这方面的方案很多,但我们还是推荐使用工具来处理。这里推荐一个PostCSS插件。这个插件的使用很简单,不需要做任何配置,只需要本地安装一下就好了,使用如下:
[aspectratio] { position: relative; } [aspectratio]::before { content: ''; display: block; width: 1px; margin-left: -1px; height: 0; } [aspectratio-content] { position: absolute; top: 0; left: 0; right: 0; bottom: 0; width: 100%; height: 100%; } [aspectratio][aspect-ratio="188/246"]{ aspect-ratio: '188:246'; }
翻译出来:
[aspectratio][aspect-ratio="188/246"]:before { padding-top: 130.85106382978725%;
这样就可以实现长宽比的效果。
目前采用PostCSS插件只是一个过渡阶段,在将来可以直接在CSS中使用aspect-ratio属性来实现长宽比。
解决1px方案
前面提到过,对于1px不建议直接转换成vw单位,在Retina下,我们时钟需要面对如何解决1px的问题。这里推荐使用一种解决1px的方案,依旧是使用PossCSS插件postcss-write-svg
使用postcss-write-svg可以通过border-img或者background-image两种方式来处理,比如:
@svg 1px-border { height: 2px; @rect { fill: var(--color, black); width: 100%; height: 50%; } } .example { border: 1px solid transparent; border-image: svg(1px-border param(--color #00b1ff)) 2 2 stretch; }
这样PostCSS自动帮你翻译过来:
.example { border: 1px solid transparent; border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='2px'%3E%3Crect fill='%2300b1ff' width='100%25' height='50%25'/%3E%3C/svg%3E") 2 2 stretch; }
使用PostCSS插件比我们修改图片要来得简单与方便。
上面延时的是使用border-image方式,除此之外,还可以使用background-image来实现。如下:
@svg square { @rect { fill: var(--color, black); width: 100%; height: 100%; } } #example { background: white svg(square param(--color #00b1ff)); }
编译出来就是:
#example { background: white url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Crect fill='%2300b1ff' width='100%25' height='100%25'/%3E%3C/svg%3E"); }
这个方案简单易用,是我们所需要的,也基本能达到所有需求,但是一定要记得在<head>标签中添加下面这一句:
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no" />
3.5 总结一下
这个适配方案中所用到的技术点,简单总结一下:
使用vw来实现页面的适配,并且通过PostCSS的插件postcss-px-to-viewport把px转换成vw,这样我们在写代码不需要做任何的计算,只需要按照原型设计稿中的尺寸来写代码,但是要配置一下设计稿的尺寸。
为了更好的实现长宽比,特别是针对img,vedio和iframe元素,通过postcss-aspect-ratio-mini来实现,在实际使用中,只需要吧对应的宽和高写进去即可。
为了解决1px问题,使用PostCSS插件postcss-write-svg自动生成border-image或者background-img的图片。
这里使用了多个PostCSS插件,其实很多优秀的PostCSS插件能帮助我们解决很多问题,可以在这里关于PostCSS相关的文章查阅相关的文章。如果要系统的学些PostCSS,可以购买《深入PostCSS Web设计》这本书。
3.6 降级处理
本节最开始提到,目前为止T30的机型中还有几款机型不支持vw的适配方案。如果业务需要,应该怎么处理呢?有两种方式可以进行降级处理:
CSS Houdini:通过CSS Houdini针对vw做处理,调用CSS Typed OM Level1提供的CSSUnitValue API.
CSS Polyfill:通过对应的Polyfill做相应的处理,目前针对vw单位的Polyfill主要有:vminpoly,Viewport Units Buggyfill,vunits.js和Modernizr,推荐使用Viewport Units Buggyfill
3.7 Viewport不足之处
采用vw做适配并不是没有任何缺陷,有一些细节还是存在不足之处的。比如容器使用vw单位,margin采用px单位,很容易造成整体宽度超过100vw,从而印象布局效果。对于这种情况我们可以使用相关的技术进行避免。比如将margin换成padding,并且配合box-sizing。只不过这不是最佳方案,随着浏览器或者应用自身的Viewport对calc()函数支持以后,碰到vw和px混合使用的时候,可以结合calc()函数一起使用,这样可以完美解决。
另外,px转换成vw单位,多少还是存在一些像素差,毕竟很多时候不能完全整除。
3.8 如何判断自己的应用是否支持
虽然很多浏览器都支持vw,在使用的时候还是会担心自己的app应用是否支持vw,可以打开下面的页面查看
页面测试完成后,找到对应的Valus and units列表项:
如果vw栏是绿色的代表你的设备支持,反之不支持。另外你可以经常关注css3test相关的更新,后面会根据相关的规范更新测试代码,让你快速掌握那些属性可以大胆使用。
参考:
https://www.w3cplus.com/css/towards-retina-web.html
https://www.w3cplus.com/css/viewports.html
https://www.quirksmode.org/mobile/viewports.html
https://www.zhangxinxu.com/wordpress/2012/10/new-pad-retina-devicepixelratio-css-page/
作者:Tyler Ning
出处:http://www.cnblogs.com/tylerdonet/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,如有问题,请微信联系冬天里的一把火