19

写在前面

在移动网民规模不断扩大的今天,手机、平板等移动设备俨然已成了网民主要上网终端。迎着这个势头,我们这些前端汪们也接下了不少的移动web页面开发需求。当在感叹终于不需要兼容pc端低端浏览器时却面临了移动多终端屏幕适配这一问题。

本人经验尚浅,在大牛们得出的数种适配方案的基础上也来谈谈这一问题。 //其实是入职前太颓废了,找点事做

由于本文目的不在于普及基础,所以移动端相关概念(viewportcss像素,独立像素,物理像素,dprdpippi)的阐明不再赘述。

可以参考:

尚未有时间研究原理的同行们,可以跳过这些直接看下面的解决方案。

这里是之前记录的以 iPhone 6 例子的一个总结:

clipboard.png

何为多终端适配?viewport 有多重要?

多终端适配即使某一套网页方案能完美地展示在不同操作系统、尺寸、分辨率、dpr的设备上。适配的原因相比也不用多说了——就是为了使网站各终端的用户都不流失。

来看看浏览器产商为我们提供的最原始的解决方案。

在iphone诞生前,为了留住移动端用户,手机浏览器尝试通过调整内容来适应网页,取得了不同程度的成功。

iphone上的safari没有做丝毫的尝试,取而代之的是在各种各样的虚拟窗口上展示网页,这些虚拟窗口被称为“视图”。用户可以通过放大来查看网页的部分内容或缩小来查看网页的全部内容。为了给开发者提供一定程度的展现页面的控制权,苹果公司提供了viewportmeta元素,它可以指定虚拟窗口的大小。
——《HTML5触摸界面设计与开发》

iphone的viewport(视口)默认大小为980px。意味着会使得在pc端显示为980px的网页以一定比例缩小(浏览器会替我们完成这一工作)成恰好可以容纳在4.7英寸(以iphone6为例)屏幕内。这样一来,整个网页缩小得我们不容易直接观测到里面的信息,必须通过手势放大页面获取。

如图,这个页面我没设置viewport,设计稿基于iphone6,所以页面内容宽度定为750px。很明显看出左右两旁的空白,即宽度为达到视口宽980px所产生的。

clipboard.png

so——构建移动页面时,设置合理的viewport值是首要任务。

那么什么是合理的viewport值呢?其实每个浏览器的viewport都不尽相同:

  • Safari iPhone:980px

  • Opera:850px

  • Android WebKit:800px

  • IE:974px

面对众多的默认viewport,使其统一为一个值是业界的普遍做法。常见的设置有:

<meta name="viewport" content="target-densitydpi=device-dpi,width=设计稿尺寸,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no" />
<meta name="viewport" content="target-densitydpi=device-dpi,width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no" />

所以一般来说,viewport的宽度会设定为device-width(设备宽度,与css逻辑像素同等大小,可通过document.documentElement.clientWidth获取)或者为设计稿尺寸(比如设计稿基于iphone6,width=750;基于ip5,width=640)。

这里需要注意:initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5 这几个缩放数值通常是通过ua检测,再通过js自动设置的。一般不会固定为1或者某一特定数值。但也有特殊情况。稍后讨论。

解决方案

前提

设计师给出的视觉稿应该严格按照某一移动设备的屏幕物理像素制定宽度,高度视页面内容而定。业界一般是基于iphone5或者6给出相应尺寸。所以最常见的视觉稿有750px、640px的。而1080px的大多是基于6plus的。而其他的奇葩尺寸真心不建议了。所以应事先约定好。

对于多倍图处理可以看看 @南宮瑞揚 翻译的这张图:

clipboard.png

一、自适应适配方案

1、使用场景

布局简单,主要内容以列表形式展示:条目内容垂直分布。定位元素少,且不为主要内容元素。如知乎美团网微博一般结构为:

  • 顶栏:搜索框或网站logo等信息

  • banner

  • 横向导航:一般为若干个平分水平空间的tap

  • 内容:含有标题和描述或还包含略所图

2、解决方案

a.设置viewport:

<meta name="viewport" content="target-densitydpi=device-dpi,width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no" />;

b.水平方向宽度自适应,垂直方向高度固定

若元素无其他平分空间的兄弟元素,宽度可定为设计稿元素宽度/设计稿宽度*100%;若有宽度定为100%/平分空间元素个数,也可以用flex的方式定义;

c.小块的元素(如logo,装饰性图片)宽高都固定;

d.注意图片在retina屏幕下应使用多倍图,但仍以1倍图的大小确定图片的宽高。若是sprite图,可设background-size: x1图片宽 auto;background-position: 以x1 sprite图片定义

3、弊端

元素高度和图片大小以同一个尺寸在多种不同尺寸的设备下显示,没达到真正适配效果。无法适应结构复杂的页面布局。

二、固定viewport为设计稿大小

1、使用场景

页面只考虑移动设备,不考虑自适应,不需要做响应式布局。非h5专题活动页。如网易新闻、我之前做过的一个团购网站cocolife也用的是这种方式

2、解决方案

a.设置viewport

<meta name="viewport" content="target-densitydpi=device-dpi,width=设计稿宽度,user-scalable=no" />

或通过js写入meta

var scale= parseInt(window.screen.width)/设计稿宽度;
document.write('<meta name="viewport" content="width=设计稿宽度, initial-scale = '+scale+', maximum-scale = '+scale+', maximum-scale = '+scale+', target-densitydpi=device-dpi">');
}

注意这里的设计稿是指初始的设计稿大小。一般以iphone6:750px;iphone5:640px定宽。

b.页面所有元素的宽高都按设计稿元素本身大小直接定义。

3、弊端

乍眼看上去这种方法挺好的,全部按设计稿给出的元素大小写成固定px值,不需要各种单位转换。所看即所得,缩放交给浏览器,完全按视觉稿切图。但,为什么没有得到普片推广呢?

第一点是不再支持响应式布局。比如我们定义的`@media
(min-width:320px)`这里的width实际上指的是viewport的宽度值,而我们的viewport已经固定成统一宽度了(如640px),所以,无论在哪个尺寸的屏幕下,这句媒体查询也不再起作用了。
第二点是这种方式会让浏览器去缩放,那么就可能会造成字体模糊,图像失真。1px border在 不同分辨率下的显示也会有所不同。
第三点是如果页面要嵌入到App中时,App是以webview的形式渲染页面的。webview实际上也是webkit内核,而最新的webkit内核对定宽支持不是很好,默认是以device-width来渲染的。
第四点是可能存在缩放失效的问题,某些安卓机不能正常的根据 meta 标签中 width 的值来缩放 viewport,需要配合 initial-scale(即上述js方式) 。

三、淘宝的做法

1、使用场景

普遍网站都可以采用这种方式。但不包括h5专题活动页。

2、解决方案

这里首先推荐大漠前辈的一篇文章使用Flexible实现手淘H5页面的终端适配

淘宝做法的原理相信大家可能比较熟悉。简单地说就是动态设置html下的font-size值,然后为页面的元素采用rem的方式制定宽高。

我们知道rem的占据像素值是由根元素的font-size值大小决定的。打个比方,若html下设置了font-size:16px;那么1rem就相当于16px

淘宝的方案大致如下:

clipboard.png

把页面分成10等份,若视觉稿初始宽度为750px,那么每份大小为75px,将值给予font-size,那么1rem=75px。加上初始的缩放值

clipboard.png

使得宽度为10rem的盒子正好容纳在iphone6视口(375px)上。若此时视口的大小变成320px,那么js就会将htmlfont-size值动态改变为64px

来看看js是怎么动态改变font-size

//UA检测
var isAndroid = win.navigator.appVersion.match(/android/gi),
    isIPhone = win.navigator.appVersion.match(/iphone/gi);

//对ios设备的dpr进行判断,安卓统一设定为1
var dpr= isIPhone ? Math.min(win.devicePixelRatio, 3) : 1;
    scale = 1 / dpr;
    
var docEl = document.documentElement;
var metaEl = doc.createElement('meta');

//设定dpr和meta
docEl.dataset.dpr = dpr;
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
docEl.documentElement.firstElementChild.appendChild(metaEl);

//动态设定根元素下的font-size大小
var width = docEl.getBoundingClientRect().width;
//pc等大屏幕下
if (width / dpr > 540) {
     width = 540 * dpr;
}
//将页面分为10等份,易得出750px视觉稿下根元素font-size为75px
var rem = width / 10;
docEl.style.fontSize = rem + 'px';

上面的js粗略地描述了一下淘宝的方案。即将font-size值设置成docWidth(视觉稿大小)/10,那么1rem大小同为docWidth/10。所以页面里的元素宽高为width/(docWidth/10)

举个栗子:

若我们拿到一个基于iphone6的视觉稿,对其运用该种方式进行适配。由于iphone6视觉稿宽度为750px,那么只需要将视觉稿上的元素大小初始宽高/75写入css。

如宽度为750pxwrap,则width=10rem;宽度为375pxarticle,则width=5rem
scss$ppr(pixel per rem) 变量写法:$ppr: 750px/10/1rem
元素尺寸写法:html { font-size: $ppr*1rem; } body { width: 750px/$ppr; }

四、专题活动 h5

1、使用场景

针对专题活动的滑屏h5页面,特点是主题内容图片居多,并且基本采用绝对定位方式。

2、解决方案

原理:给每屏下的主要内容加一个container包裹元素,并设置margin: 0 auto使其水平居中。(保证内容在视线中央)使用jscontainer整体进行transform:scale()缩放,scale值根据屏幕窗口大小动态设置。

2.1 推荐一个框架以快速搭建适配的h5:pageResponsive

2.2 不使用框架的具体做法:

a.设定viewport

<meta name="viewport" content="target-densitydpi=device-dpi,width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no" />;

b.页面结构

一般为

<ul>
    /*---第一屏---*/
    <li><div class="container"><div><li>
    /*---第二屏---*/
    <li><div class="container"><div><li>
    /*---第三屏---*/
    <li><div class="container"><div><li>
    ...
<ul>

一般来说h5有contain和cover两种适配模式。

contain即将页面固定在一个特定大小的盒子中。背景一般以与h5背景颜色相近的纯色替代。像这样:

clipboard.png

cover即将h5每一屏的背景或者从紧贴边缘的图像通过调整background-size使之恰好容纳在窗口中。像这样:

clipboard.png

下面说说两种模式的h5写法(默认遵循上述的html结构):

contain:

对于每一屏下背景或者从边缘开始显示的大图定义在li下,采用background-size:100% 图像高度的方式。

cover:

把所有元素放置在.container下统一进行缩放。背景定义在.container下。同时给.container设置固定的宽高,一般320*520的居多。

c.进行动态缩放的js

// 响应式缩放
var autoScale = function() {
var ratio = 320/508,
    winW = document.documentElement.clientWidth,
    winH = document.documentElement.clientHeight,
    ratio2 = winW/winH,
    scale;
//判断宽和高以哪个为缩放基准    
if (ratio < ratio2) {
        scale = (winH/508).toString().substring(0, 6);
    } else {
        scale = (winW/320).toString().substring(0, 6);
    }
var cssText = '-webkit-transform: scale('+ scale +'); -webkit-transform-origin:top center; opacity:1;';
$(".container").attr('style', cssText);

};

//这里使用setTimeout是由于获取文档宽高时有时候不能立刻得到,造成缩放失效
setTimeout(function() {
    if (document.documentElement.clientWidth/document.documentElement.clientHeight !== 320/508) {
        autoScale();
    } else {
        $('.container').css({'opacity': 1});
    }
}, 300);

总结

通过上述分析,得出以下总结供大家对适配方案进行选择。

拿到视觉稿
      ↓
判断布局是哪种类型
      ↓
1.内容少,绝对定位图片少,布局简单,一般为上下结构
      ↓
是否需要适配PC?
是 → 自适应方式  
否 → 自适应、rem、viewport方式
      ↓
是否要嵌入APP或运用响应式布局?
是 → 自适应、rem方式
否 → 自适应、rem、viewport方式  //推荐使用自适应方式

2.内容适中,绝对定位元素少
      ↓
是否要嵌入APP或运用响应式布局?
      ↓
是 → rem方式
否 → rem方式或viewport方式  //推荐使用rem方式

3.整屏专题H5;绝对定位元素多  
      ↓
是否需要适配PC?
      ↓
是 → H5专题页面的cover方式
否 → H5专题页面的contain方式

^^感谢阅读!


yvonne
1.3k 声望93 粉丝