移动 web 开发最佳实践

0、问题的引出

提到移动设备开发,最先让人想起的是苹果和安卓,以及他们那些令人头疼的多屏幕适配。下面是腾讯分析统计的移动设备的各分辨率占有情况。

不光分辨率差别很大,移动设备的尺寸相差也很大,从3英寸的手机到12英寸的平板。

下图为各种品牌所存在的移动设备的尺寸。

还有,就算两个设备尺寸一样,也会存在不一样的屏幕密度(dpi或ppi,每英寸的屏幕包含多少个像素),同样大小的字体或者宽高,放到这两个设备上,屏幕密度大的字体就会显得小。下图是现有市场上的屏幕档次。

所以说,移动端web开发面临的最大问题就是就是多屏适配,这是一个设计师、开发和测试都要面临的问题,如何做到在不同分辨率,不同屏幕密度上的手机上,同样大小的UI元素,看起来是一样大的。说白了就是同一套代码在不同分辨率的手机上跑时,页面元素间的间距,留白,以及图片大小会随着变化,在比例上跟设计稿一致。

1、一些概念

在解决问题之前,先了解一些概念。

CSS像素

即我们css代码里写的像素(px),用于页面布局的单位,与设备无关。

物理像素(px,physical pixel)

一个物理像素是显示器(手机屏幕)上最小的物理显示单元,在操作系统的调度下,每一个设备像素都有自己的颜色值和亮度值。物理像素与分辨率有关,比如iphone 7的分辨率是1334x750,它表示这个设配有1334x750个物理像素。

设备独立像素(dp,density-independent pixel)

设备独立像素(也叫密度无关像素),可以认为是计算机坐标系统中得一个点,这个点代表一个可以由程序使用的虚拟像素(比如: css像素),然后由相关系统转换为物理像素。设配独立像素与尺寸有关,比如iphone7的宽高为375×667,可以理解为设备有375×667独立像素(或css像素)。所以说,物理像素和设备独立像素之间存在着一定的对应关系,这就是接下来要说的设备像素比。

设备像素比(device pixel ratio )

设备像素比(简称dpr)定义了物理像素和设备独立像素的对应关系,一个设备像素上有多少个物理像素,它的值可以按如下的公式的得到:

设备像素比 = 物理像素 / 设备独立像素

下图为同样设置css宽高为2px的矩形,在不同的设备上所占的物理像素。其中像素比为1的占用4个,像素比为2的占用16个。

视口(viewport):

视口指的是移动设备中的设备屏幕窗口。 在移动端浏览器当中,存在着两种视口,一种是视觉视口(也就是我们说的设备大小), 另一种是布局视口(我们要看的网页的宽度是多少)。

先说一下视口的起源,智能设备刚出现的时候,查看桌面端的页面时会出现一个问题:由于早期的页面很多采用固定宽度的布局,导致放在移动端的小窗口下出现横向的滚动条,不便于用户查看,所以浏览器厂商研究出了布局视口。布局视口的宽度一般在768px~1024px(由浏览器厂商设置),常见宽度980px,这样,小屏的移动设备能够一次性完全显示桌面端页面,避免了浏览器出现横向滚动条。下图是腾讯网(www.qq.com)在手机端的显示状态,如果不进行缩放操作的话,文字几乎是无法看清的.

举一个例子:如果我们的屏幕是375像素×667像素的大小(iPhone6),假设在浏览器中,375像素的屏幕宽度能够展示980像素宽度的内容。那么375像素的宽度就是可见视口的宽度,而能够显示的980像素的宽度就是视窗视口的宽度。说白了,就是把980px的东西装在了375px的屏幕里。用户不用缩放,就能看到整屏的的页面。但也产生了一个问题,移动端的浏览器同桌面端相比,就是字体过小,但是用户可以手动缩放。后期也产生了根据调整视口宽度(width)和缩放(scale)开发移动端的页面。

2、设计图

设计师出图的依据是移动设备的分辨率,与设备的宽高无关,单位是px。根据本文的第一张图显示,苹果1334750分辨率最多,而安卓则是19201080最多,虽然有些差别,但是宽高比都是16:9的,缩放后失真不会太多。

1倍图

在早期的手机,一个设备像素(dp)就是一个物理像素px,这时候的分辨率和尺寸有关。 比如480x320的手机一定会比640x480的小。这种图已成为历史,不再讨论。

2倍图

后来苹果发明了retina,就有了屏幕像素比这个概念,2倍图就是屏幕像素比为2的图,这种比例以iphone 4起为代表,iphone4它的尺寸是320x480,但是它的分辨率是640x960。 即:分辨率 = 屏幕宽高 x 像素比

3倍图

到了iphone plus又出了三倍图的概念,它的尺寸是是414x736 ,而分辨率达到了1242x2208。

下面是iphone系列各个宽高及像素比:

安卓的屏幕尺寸更加多样,分辨率有很多种,相应地,设备像素比也不一致,有1、1.5、2、2.25、3等等。也有1.5倍图等概念。

那设计师出多大分辨率(px)的图呢?

第一种:

以iphone5(640x1136) 为基准设计稿,向上适配。 这种方案在两年前比较流行,小尺寸的页面放在大尺寸的手机上,会自动等比放大,铺满新手机,效果还可行。现在还有很多H5在做适配的时候设置成都直接设置了 viewport:width=320,简单可行。但因为拉伸,整体看起来有点虚,也不能更好利用大屏空间。等到后来在plus出现,效果就更差了一些。

第二种:

以iphone6 为基准设计稿,向上、向下适配。 这个尺寸在H5上非常流行,iphone 6 6s 7的尺寸大小相同,分辨率相同,都为750x1334,向上拉伸,向下压缩,失真的比例不会太大。

第三种:

以iphone plus 为基准设计稿,向下适配。 这两年安卓发展突飞猛进,分辨率越来越高,1080x1920分辨率已成为普及,而2k、4k屏也即将到来,小的设计稿已无法满足超清的要求,很多App的设计都已步入3倍图的时代,那就是以iphone plus 的尺寸(414×3=1242)为基准,生成的3倍图,1242×2208px分辨率。

综合来看,在移动web开发时,第二种方式当前最合适。既满足了retina用户的显示需求,又能降低2G、3G用户加载图片需要的带宽。不过,你若有更高质量的追求,第三种设计稿也是一个不错的选择。

不管在手机浏览器还是在微信客户端或者腾讯新闻客户端开发,内容大都不能全屏显示的。在底部或者顶部多多少少会有一个状态栏的占位。一些手机浏览器底部会有导航,也有些会在顶部和底部都有占位,比如导航栏、状态栏。顶部的占位会把内容往下挤,底部的占位会把内容遮盖住。如果做只有一屏的H5,高度要注意一下。

下图为腾讯新闻客户端和微信内置浏览器的占位高度,在750x1334(iphone6)上他们的高度是一样的。如果你的页面高度超过1208px,页面就会出现滚动功能。

3、适配

1、viewport固定

viewport <meta>用于指定用户是否可以缩放Web页面,表示文档针对移动设备进行了优化。viewport <meta>的content值是由指令及其值组成的以逗号分隔的列表。

下面是viewport的一个示例:

<meta name="viewport" content="width=240, height=320, user-scalable=yes,
 initial-scale=2.5, maximum-scale=5.0, minimun-scale=1.0">

width和height的值可以使用具体的数值,或者使用device-width/device-height可以指示视区宽度应为设备的屏幕宽度/高度。

user-scalable为yes/no,或者1/0表示是否允许用户缩放。

initial-scale用于设置Web页面的初始缩放比例,设为1.0则将显示未经缩放的Web文档。>1将放大, <1将缩小。

maximum-scale和minimum-scale用于设置用户对Web页面缩放比例的限制。值的范围为0.25至10.0之间。

既然viewport可以自动缩放页面,那么为什么不制作固定尺寸的页面,让浏览器自己去缩放呢?开发和设计都省事了!

固定尺寸的页面的实现:

<meta name="viewport" content="width=750,user-scalable=no">

这种模式最为简单,它的意思是不管何种设备,都按照750的宽度等比例缩放。所以,按照宽度为750的界面设计和开发最省事。user-scalable=no的意思是不允许用户缩放。若是开启用户缩放的话,当你的页面超过750px时,浏览器会自动进一步缩放,至你的页面宽度。

另外,也有js的写法

var phoneScale = parseInt(window.screen.width)/750;
var viewport = document.querySelector("meta[name=viewport]")
viewport.setAttribute('content',"width=750, initial-scale = '+phoneScale+',
 maximum-scale = '+phoneScale+', maximum-scale = '+phoneScale+'">');
}

这种模式适合用来做PC页面的简单适配,但是这有个缺点,这是缩放。既然是缩放,那么就会失真,大屏设备上的字体会大一些(字体变的模糊),1px的直线看起来不一样粗。还有,设备由竖屏切换到横屏的时候,界面会变大的很多。在一些手机上,如果用了一些非自带的字体,甚至会发虚,如果用了部分CSS3的属性,发糊的现象可能会更严重,缩放同时会带来浏览器的卡顿。

这种模式,情急之下够用,但不完美。

2、设备宽度

在开发移动网页时,你一定会遇到前辈留下的这段代码:

<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.5,
 maximum-scale=2.0, user-scalable=yes" />

在网页的中增加以上这句话,可以让网页的宽度自动适应手机屏幕的宽度。其中:

width=device-width :表示宽度是设备屏幕的宽度 initial-scale=1.0:表示初始的缩放比例

它的意思是说,页面宽度就是设备宽度,缩放比例100%,这时,无论你是多么高清的屏一个CSS像素完全等于一个设备的独立像素。其实这两行代码的作用是一样的。他们的的作用都是不对当前的页面进行缩放,也就是页面本该是多大就是多大。因为这里的缩放值是1,也就是没缩放,屏幕的宽度自然是实际能展示的宽度了。

但如果width 和 initial-scale=1同时出现,并且还出现了冲突呢?比如:

<meta name="viewport" content="width=400, initial-scale=1">

width=400表示把当前viewport的宽度设为400px,initial-scale=1则表示把当前viewport的宽度设为设备的的宽度,那么浏览器到底该服从哪个命令呢?是书写顺序在后面的那个吗?不是。当遇到这种情况时,浏览器会取它们两个中较大的那个值。

但为什么要写两个?这里有个兼容性的bug,当横竖屏切换的时候,iphone、ipad的device-width会始终为竖屏的width,而winphone的initial-scale始终依据的是竖屏的width。最完美的写法应该是,两者都写上去,这样就 initial-scale=1 解决了 iphone、ipad的毛病,width=device-width则解决了IE的毛病。

如果不设置width或scale,浏览器则会默认width为980,然后计算scale = 页面宽度/980。

3、媒体查询

媒体查询可以让我们根据设备显示器的特性为其设定CSS样式,配合rem,就可以让宽屏的设备显示大号字体和宽的内容。rem(font size of the root element)是指相对于根元素(html)的字体大小的单位。是CSS3新增的一种单位,移动端基本都支持。

html{font-size:20px}
@media screen and (min-width:321px) and (max-width:375px){html{font-size:22px}}
@media screen and (min-width:376px) and (max-width:414px){html{font-size:24px}}
@media screen and (min-width:415px) and (max-width:639px){html{font-size:30px}}
@media screen and (min-width:640px) and (max-width:719px){html{font-size:40px}}
@media screen and (min-width:720px) and (max-width:749px){html{font-size:45px}}
@media screen and (min-width:750px) and (max-width:799px){html{font-size:47px}}
@media screen and (min-width:800px){html{font-size:50px}}

把与元素尺寸有关的css,如width,height,line-height,margin,padding等都以rem作为单位,这样页面在不同设备下就能保持一致的网页布局。

假设,html我们设置font-size:20px; 也就是说20px相对于1rem,那么18px也就是 18/20 = 0.9rem。

那么我们以375px的设计布局为基准,将html设置为font-size:100px,即100px = 1rem。(设置100px是为了方便计算)那么可以将大部分px单位除以100就可以直接改成rem单位了。比如,70px宽的元素就是0.7rem。

通过设备宽度范围区间这样的媒体查询来动态改变rem基准值,其实不够精确,比如:宽度为375px 和 宽度为321px的手机,因为屏宽在同一范围区间内(320< width <375px),所以会被同等对待(rem基准值相同),而事实上他们的屏幕宽度并不相等,它们的布局也应该有所不同。可以通过JS来实现媒体查询的功能。

(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 / 375) + 'px';
        };
    win.addEventListener(resizeEvt, recalc, false);
    doc.addEventListener('DOMContentLoaded', recalc, false);
})(document, window);

到现在我们没有做到足够的精确,但是已经够用。

4、1像素问题

上述的方法还存在一个问题,先看下图

我们设置了width=device-width,这样css样式和设备像素无关了,1px在普通屏上占用了1行像素,在高清屏上占用了2行像素,在3倍屏上就占用了3行像素等等。

那我们怎么才能实现高清设备上的实实在在的1px呢?也就是0.5px呢?比如设计师要求实现设备上一条最细的边线,可并不是所有手机浏览器都能识别border: 0.5px,ios7以下,android等其他系统里,0.5px会被当成为0px处理的。

有两个方法

第一种:针对边框缩放

{
    border-bottom:1px solid #ddd;
    transform:scaleY(.5)
    transform-origin:0 0;
}

这个时候,不支持0.5px像素的设备会按1px实现,支持0.5px的设备会呈现0.5px(实际上是1px)。这里通过transform: scaleY(.5)缩小0.5倍来达到0.5px的效果,但是这样hack实在是不够通用(如:圆角等),与这个元素相关的其他属性也要跟着调整,写起来实在麻烦。

有没有别的方法?有!

第二种:整体缩放

思路是这样的,1倍图渲染1倍图的界面,2倍图渲染高清屏的界面,以此类推,然后再统一缩小倍数到设备界面上,完美!

var dpr, rem, scale;
var docEl = document.documentElement;
var fontEl = document.createElement('style');
var metaEl = document.querySelector('meta[name="viewport"]');

dpr = window.devicePixelRatio || 1;
rem = docEl.clientWidth * dpr / 10;
scale = 1 / dpr;

// 设置viewport,进行缩放,达到高清效果
metaEl.setAttribute('content', 'width=' + dpr * docEl.clientWidth 
+ ',initial-scale=' + scale + ',maximum-scale=' + scale 
+ ', minimum-scale=' + scale + ',user-scalable=no');

// 设置data-dpr属性,留作的css hack之用
docEl.setAttribute('data-dpr', dpr);

// 动态写入样式
docEl.firstElementChild.appendChild(fontEl);
fontEl.innerHTML = 'html{font-size:' + rem + 'px!important;}';

举例来说:

1倍屏400宽的设备rem是40px,渲染1rem的宽度实际上就是40px,渲染1px就是1px。

2倍屏400宽的设备rem是80px,渲染1rem的宽度实际上就是是80px,缩放为0.5倍为40px,渲染1px就是1px。

2倍屏600宽的设备rem是120px,渲染1rem的宽度实际上就是是120px,缩放为0.5倍为60px,渲染1px就是1px。

这样就在,通过屏幕像素比进行缩放,不改变原来rem大小的前提下,实现了1px功能。

5、一切从简

上述的例子一步一步引出了动态设置rem的方法,但是每次都要计算rem很麻烦。

对于一些资讯流的页面,比如腾讯新闻(xw.qq.com)iphone5的界面和iphone plus界面

对于图片,采取了等比例缩放,对于文字,大小是一样的,超出后自动截断。每个列表的高度是一样的,设备越高,展示的新闻条数越多。

这是一种典型的弹性布局:关键元素高宽和位置都不变,只有容器元素在做伸缩变换。对于这类页面,记住一个开发原则就好:文字流式,控件弹性,图片等比缩放

1、文字字体大小不变,多了就折行,自动撑开。

2、空间在宽屏的设备上左右浮动,flex或者float。

3、图片通过scale或百分比自动缩放。

对于这种页面,拿到设计图后,直接按px开发,无需用rem增加页面的复杂度。

4、小结

上面对移动端H5高清和多屏适配的一些方案总结,没有固定的套路及方法,请根据自身业务的特点,选择其中的一些方法组合使用,不对的地方,多多指教。

接下来第二部分会分析移动web开发的过程中的细节问题和最优的解决方法。

敬请期待……

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

刘春鹏的专栏

1 篇文章1 人订阅

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大史住在大前端

一统江湖的大前端(1)——PPT制作库impress.js

impress.js 是一款基于 css-3D 和 css动画 、受到高逼格PPT原型工具 prezi 启发而开发的演示文稿制作库,github上星星高达33k...

823
来自专栏Android 开发者

Android P 中的新文本特性

在 “What’s new in Android P Beta” 中我们已经谈到 Android 的两个新文本特性。现在既然 Android P Beta 3 ...

682
来自专栏应用案例

如何用Python提取中文关键词?

本文一步步为你演示,如何用Python从中文文本中提取关键词。如果你需要对长文“观其大略”,不妨尝试一下。 ? 需求 好友最近对自然语言处理感兴趣,因为他打算利...

2348
来自专栏hrscy

Unity地形基础

![Uploading 2016-05-02_20-09-56_318489.png . . .]](http://upload-images.jianshu....

942
来自专栏数据科学与人工智能

【Python环境】Python可视化工具综述

简介 在Python的世界里,可视化你的数据有多种选择。由于这种多样性,决定何时使用哪一个确实是种挑战。这篇文章包含由更受欢迎的包中的一部分制作的示例,并说明如...

26710
来自专栏腾讯社交用户体验设计

如何打造一个高效适配的H5

1174
来自专栏ATYUN订阅号

初学者福利!无需编码,使用KNIME构建你的第一个机器学习模型

对初学者来说,有太多的东西需要同时学习是机器学习面临的最大挑战之一,特别在你不知道如何编码的情况下。如果你没有过编写代码的经验,那么你可以使用GUI驱动的工具开...

4976
来自专栏FreeBuf

ModSecurity技巧:使用ssdeep检测Webshell

最新版本的ModSecurity增加了ssdeep检测webshell的接口,于是猛地回忆起搞客户端安全(游戏安全)的时候买过一本书《恶意软件分析诀窍与工具箱-...

2458
来自专栏向治洪

策略模式

策略(Strategy)模式 策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使...

1609
来自专栏花叔的专栏

教你做个手绘板小程序码

话说上一条群发,我发了一个旅行青蛙相关的图,底部有个手绘的小程序码 ? 有些细心的同学惊奇第发现那个“”手绘“”的小程序码竟然是可以长按识别的,觉得特别神奇。 ...

2504

扫码关注云+社区