移动web开发初级攻略

这几天在整理之前的内容,把散落在各地的东西都整理在一起,一篇14年的文章,收录到这里;主要是纪录在移动端web开发的时候最基本的一些入门知识

这是一篇普及性质的文章,内容浅显明了。也算是对移动web开发的入门做了一个总结式的简介。已经有移动web开发经验的同学可以绕道,还没有入门的同学可以留意下,也许你能从此跨入移动web开发的大门,希望你能有所收获。

整篇的内容会分为5个部分,第一部分是关于移动端的常用布局思路,第二部关于图片和文字,第三部分是动画,第四部分是事件,最后一部分是关于单页应用。

关于布局

攻略推荐:flexbox。flexbox弹性布局,背后的思想就是使得容器中的项目块能够改变宽度和高度来最佳地填充可用的空间(为了适应不同类型的设备和屏幕宽度)。在移动端利用flexbox可以简单方便的满足我们的很多需求。我们先看看它在移动端的支持情况吧。

flexbox的适用性

图1 flexbox的适用性

图来自caniuse网站, 大家可以通过此网站查询关于css3和html5的浏览器支持情况。图1只是部分的支持情况,完整的图可以自行在caniuse网站上查看。从图1可以看出,flexbox在移动端的支持情况还是很乐观的,另外Android2.3也是支持的,不过是支持老版本的规范而已。

简单说明下,flexbox的语法到目前为止有三种,最老的是09年的老语法(display:box),其次是11年的语法(display:flexbox),然后才是最新的语法(display:flex)。不同的浏览器使用flexbox的时候,css的前缀也不相同。有工具flexbox兼容语法工具方便大家能够写出兼容的flexbox代码。

flexbox的大致情况如上所述,它的主要用途在于组件级的布局,下面列举一个最常见的上中下三列布局。如下图2所示,在实际的开发过程中,我们会遇到这样的需求。在内容不满一屏的时候,主内容区域也需要撑满整个空白的区域,尾部位于最低端。

主内容撑满整个空白区域

图2 主内容撑满整个空白区域

在PC上,我们会很自然的想到利用position:fixed的属性来解决固定的问题,可是在移动端position:fixed因为支持程度的问题(Android2.X和IOS5之下)不得不抛弃,取而代之的利用flexbox的属性来解决。

1
2
3
4
5
6
7
8
HTML代码片断:
<div class="container">
     <div class="wrap">
          <section class="header">header</section>
          <section class="main"></section>
          <section class="footer"></section>
     </div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
css代码片断:
.container{
       position:absolute;top:0px;bottom: 0;left: 0;right: 0px;
       height: 100%;width: 100%;
       overflow:hidden
}

.wrap{
    width: 100%;
    min-height:100%;

    display: -webkit-box;
    display: -moz-box;
    display: -ms-flexbox;
    display: -webkit-flex;
    display: flex;
    -webkit-box-direction: normal;
    -moz-box-direction: normal;
    -webkit-box-orient: vertical;
    -moz-box-orient: vertical;
    -webkit-flex-direction: column;
    -ms-flex-direction: column;
     flex-direction: column;
     -webkit-flex-wrap: nowrap;
     -ms-flex-wrap: nowrap;
     flex-wrap: nowrap;
}

.header{
    height: 30px;width: 100%;
}

.main{
   width: 100%;height:100%;
   flex-grow:1;
   -moz-box-flex: 1;
   -webkit-box-flex: 1;
   box-flex: 1;
   overflow:auto
}

.footer{
   width:100%;height:30px
}

这段css里面包括了很多兼容性的代码。再比如一个最常见的问题:完美居中。用flexbox的属性进行布局就再简单优美不过了。如下面css代码段。

1
2
3
4
5
6
7
8
9
.parent{
display: flex;
height : 300px /*或者其他*/
}

.child{
width: 100px; /*或者其他*/
  height: 100px; /*或者其他*/
  margin: auto; /*这里就是神奇的地方*/
}

完美居中的原理就是依赖于margin值设置为auto来吸收到额外的空间。所以设置一个margin为auto 的值能够使得内容完美的居中于两个轴之间。

flexbox的各种属性还有很多有趣又实用的用法,大家平时可以多多积累一些。最后列举一些w3c定义的最新flexbox的属性。

在父元素上使用的属性:

  • display:flex 定义弹性容器
  • flex-direction 定义弹性内容主轴的方向
  • justify-content 定义弹性内容主轴的空白内容区域
  • align-items 定义弹性内容交叉轴的方向
  • align-content 定义弹性内容交叉轴的空白内容区域

在子元素上使用的属性:

  • order 定义弹性内容的顺序
  • flex-grow 定义弹性内容增长
  • flex-shrink 定义弹性内容收缩
  • flex-basis 定义弹性内容基准伸缩值
  • align-self 覆盖默认的对齐方式

关于图片和文字

图片

要彻底的讲清楚移动端的图片设置其实还是一个巨大的工作量,需要从设备分辨率说起。简单来说有下面几条:

(1) 设备像素比 = 物理像素/设备独立像素

(2) 常用的针对移动端的优化设置

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

(3) 如果设备像素比计算出来是1,根据(2)的设置那么页面也是1:1显示在屏幕上;如果设备像素比计算出来是2,那么页面会2:1的显示在屏幕上,此时图片就会被拉大到成原来的2倍。

上面的3条会引发出三个问题:第2条的设置有什么作用?计算出来的设备像素比是什么?设备像素比为什么会影响页面的显示?

回答第一个问题:width=device-width 表示页面宽度等于设备宽度,也就是说页面会以设备宽度的大小显示出来。这个时候,如果页面宽度与设备宽度匹配,假设都是320px,那么页面就是1:1的显示在屏幕上。如果页面宽度是320px,而设备宽度是640px,那么页面就会被拉大一倍显示在屏幕上。

回答第二个问题:物理像素就是设备实际的显示像素,在市面上看到的设备指标中的像素,比如某设备是640×480的像素,他的宽的物理像素就是640px;设备独立像素(dip或dp),1dp表示在屏幕点密度为160ppi时1px长度。可以用公式px=dp( ppi/160) 计算,公式可以变成px/dp = ppi/160,所以其实设备像素比,可以直接用ppi/160来计算。ppi是每英寸像素数,计算方法就是长宽各自平方之和开方,除以对角线长度(单位英寸)。比如:WVGA屏480*800,按3.8寸屏算,ppi就是 √ (480^2 + 800^2) / 3.8 = 245,约等于240,px/dp=1.5。

回答第三个问题:先看图3,retina屏(视网膜屏)的设备像素比是2,相对于非视网膜屏用1个像素显示,就需要4个像素来显示。假设我们的图片是50×50px大小的,在非视网膜屏上,图片上的一个1×1的像素对应于显示屏的1×1像素,正常显示;在视网膜屏上,图片上的一个1×1的像素就对应于显示屏的2×2像素,相当于把1个点放大在了4个点的宽高上,所以图像就虚了。

高清屏上像素的显示

图3 高清屏上像素的显示

所以,常常在设计的时候,会按照200%的比例来设计,一个宽度设置为50px的图片,实际上的大小是100px,这样可以兼容支持像配备retina屏的iPhone那样的像素密度高达320 dpi及以上的设备。不过这也同时会造成图片大小变大,页面速度、流量等问题的出现。

在实践中有以下几个思路:

  • 最简单的方法高清大图仍然采用2倍的jpg图片;优化一些的方法,利用媒体查询-webkit-device-pixel-ratio来设置不同设备分辨率的图片 @media screen and (-webkit-device-pixel-ratio:2){ /设备分辨率2时候的样式/ }
  • 小icon图,能用css伪类css伪类写的各种图标写的就不用图片了;webfonts在酷派手机上会有问题,保守还是直接用图吧
  • 延迟加载、按需加载减少一次请求所有图片的手段也是需要同步加上的

顺带提一下设置全屏的背景图片时,宽大于高的图,如下代码段1会全屏撑开;高大于宽的图片,把background-size改成auto 100%。

1
2
3
4
代码段1:
background-image:url(..);
background-size:100% auto;
background-repeat: no-repeat;
文字

文字的大小也是我们在做网页的时候必须要设置的,关于px,em,rem单位度量的差别也是有各种文章详细的介绍。其中em和rem的区别在于,rem是相对于根元素的大小,em是相对于父元素的大小。移动端不考虑ie的情况,使用rem和em还是很推荐的。鉴于em的基准是相对于父元素,很容易计算出错,利用rem就比较简单。

如下例子所示,定义基准的根元素的大小,然后利用rem定义子元素的相对大小,同时利用media query来控制不同可视区域时根元素的字体大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
html{
    font-size : 16px;
}
@media (max-width:900px){
    html{font-size:14px}
}
h1{
    font-size: 3rem
}
h2{
    font-size: 2.5rem
}
...

对于是否是“最佳实践”的问题,有一篇将响应式布局的文章给出了这样的设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@media (min-width: 858px) {
    html {
        font-size: 12px;
    }

}

@media (min-width: 780px) {
    html {
        font-size: 11px;
    }

}

@media (min-width: 702px) {
    html {
        font-size: 10px;
    }

}

@media (min-width: 724px) {
    html {
        font-size: 9px;
    }

}

@media (max-width: 623px) {
    html {
        font-size: 8px;
    }

}

最后给出一些建议:

  • 使用16px的基准字体大小
  • 利用media query来调整基准的字体大小
  • 使用相对于基准字体大小的字体来排版

关于动画

css3在移动端的支持程度,使得我们在移动端的很多动画效果都可以直接采用css3来实现。当然关于css动画和javascript动画的性能问题也是讨论的轰轰烈烈。一句话概括就是各有利弊,各有千秋。

浏览器在处理css动画的时候是与“主线程”分开的,所谓的“主线程”就是javascript执行比如“布局”、“绘制”、“排版”这样的事情。所以这就意味着,当浏览器的主线程在工作的时候,css动画也能照常工作。transform和opacity这两个属性的改变,很多情况下是可以同时在css动画的线程中执行的,这两部分的改变也可以坚持使用css动画。

一些简单的,改变自己状态的UI元素的动画建议使用css动画;比如toggle这样的动画(出现、消失的来回切换)。

需要控制动画状态的,比如结束、暂停等动画,使用javascript。基于javascript的动画框架很多时候都能比基于css的动画框架提供更灵活、更复杂的动画效果。当然这些动画框架是纯为动画而生,并不是像jQuery这类核心在处理dom的框架,比如velocity.js、GSAP等。

不管是css动画还是javascipt动画只要触发了reflow和repaint,“主线程”都会被迫去工作。简单的移动翻转等动画能够使得页面看起来更加的酷炫,可是过多地使用动画也必然会照成页面出现卡顿等的现象。下面则会提到一些常用的优化手段。

(1)能用transition实现的动画,就不用animation(@keyframes)比如:

用transition实现旋转一圈360度:

1
2
3
4
5
6
7
<div class="anim"></div>
.anim{
         -webkit-transition: all 1.5s linear;
}
.anim:hover{
         -webkit-transform: rotate(360deg);
}

用animation实现:

1
2
3
4
5
6
7
8
9
10
11
<div class="anim"></div>
.anim:hover{
     -webkit-animation-name:loading;
     -webkit-animation-duration: 1.5s;
     -webkit-animation-iteration-count:once;
     -webkit-animation-timing-function: linear;
}
@-webkit-keyframes loading{
     from {-webkit-transform:rotate(0deg)}
     to {-webkit-transform:rotate(360deg)}
}

transition和animation有它们各自的适用场景,transition主要处理的是两个状态的变化,它的发生需要一些触发动作,比如:hover,media query的控制,class的改变等;animation可以用来处理更复杂一些的动画,不只是两个状态的变化,并且它的触发可以不通过用户的交互产生。

(2)使用translate3d(0,0,0),强制浏览器进行硬件加速;不要使用过多,以免崩溃。

1
2
3
4
css代码段:
-webkit-transform: translateZ(0);
-webkit-backface-visibility:hidden;
-webkit-perspective:1000;

硬件加速其实就是强制创建一个GPU层,把动画放到GPU上去完成,这样就不占用CPU的资源。一旦GPU层创建出来,GPU来处理像素的移动和复合是一件微不足道的事情。使用过多会造成性能下降的原因。

浏览器渲染的过程

图4 浏览器渲染过程

过多的使用硬件加速浏览器的渲染情况

图5 过多的使用硬件加速浏览器的渲染情况

图4是浏览器渲染的一个过程,我们就看最后一步composite layers合并图层。从图4中也可以看到transform和opacity两个属性只会影响到最后一步,不会产生严重的reflow和repaint,也印证了上面说的,这两个属性的改变大胆使用css动画。更重要的是,从图5(来自苹果官网分析的文章,见脚注5)我们看到,过多的GPU加速,虽然减少了浏览器前面步骤的开销,但是会大大加重最后一步合并图层的操作,甚至到了秒级。这也会严重影响页面的性能。如何优化这部分,可以在脚注5的文章中查到,利用chrome的开发者工具提供的功能,可以直接观察到页面上有哪些composite层,减少没有transform和opacity属性改变层的硬件加速能很好的解决滥用硬件加速的问题。

(3)javascript做动画的时候,使用requestAnimationFrame,避免使用setTimeout和setInterval。

在日常的页面性能中,动画达到60帧(frame per second,60fps),就被视为一个理想的状态。60帧意味着16.7毫秒绘制一帧,如果使用setTimeout或者setInterval显然是不能达到要求的。这又牵扯到浏览器内置时钟的更新频率问题。总之,由于精确度的原因,使用setTimeout和setInterval是无法达到60帧这样的动画流程要求。

关于事件

也许这部分是改变最多的,实际运用中却可能也是改变最少的。说改变最多是因为我们已经进入了全新的touch时代,说改变最少是因为在实际操作中,用click等点击事件,也是可以的。移动和PC在事件上的一个很大区别就是,移动端新增了很多强大的手势操作,如图6所示,所有的手势都可以通过移动端内置的touchstart、touchmove、touchend、touchcancel等事件进行封装实现。

移动端的各种手势

图6 移动端的各种手势

关于事件这部分有几个注意点:

(1)click事件在移动端同样可以使用,只是会有300ms的延迟;具体原因简单说就是浏览器分不清是click还是double tap造成。解决延迟的方案,使用fastclick.js(标准方案之一),代码段:

1
2
3
4
5
6
/**
 *  原理是:在touchEnd事件之后,立马实现一个合成的click事件并且阻止掉300ms以后那个真正的click事件
 */

window.addEventListener( "load", function() {
     FastClick.attach( document.body );
}, false ); //页面的click事件再也没有300ms延迟的烦恼

或者使用touchstart等内置touch事件代替click,或者使用第三方封装的touch模块提供的tap等方法来代替click事件。

(2)移动端的手势模块,强大的比如hammer.js,实现了绝大多数的手势事件;zepto的touch模块算是一个简单的手势库,鉴于很多网页的手势也不会很复杂,基本就是tap(轻触)、swipe(滑动)等,zepto的touch模块已经够用,不过会遭遇穿透的问题。所谓穿透问题,就是两个层,上面的层绑定的tap事件轻触以后消失,会触发下层的click的事件或者是a 元素的等的原生事件。

说到穿透问题就引出了移动端的一个情况。在很多情况下,触摸事件和鼠标事件会同时被触发(目的是让没有对触摸设备优化的代码仍然可以在触摸设备上正常工作)。如果你使用了触摸事件,可以调用event.preventDefault()来阻止鼠标事件被触发,这里不得不提到event.preventDefault()只有在touchstart的时候才能比较有效的阻止click事件的发生,但同时又会引起不能触发scrolling滚动的问题,如下图。

click事件被触发的各种情况

图7 click事件被触发的各种情况

解决方案可以延迟dom层的消失,还有其他的一些方法,比如重写zepto的touch模块等,见聊聊touch事件 聊聊touch模块

(3)用事件代理的方式绑定事件的时候,有些手机默认给出的触摸范围是在父元素的范围,无法定位到确定的元素上。可以利用CSS去掉浏览器原生的样式,再自定义样式。

1
2
3
4
5
6
.logo{
     -webkit-tap-highlight-color: rgba(0,0,0,0);
     -webkit-tap-highlight-color: transparent; /* For some Androids */ }
.logo:active{ -webkit-transform: scale3d(0.9, 0.9, 1);//需要自定义一个active的动作 }

<a class="logo" href="javascript:void(0);" ontouchstart="return true;"></a>

提醒一句,有时候带有浏览器原生的样式,能带给用户更好的交互感。

关于单页应用

除了上面提到的布局、图片、文字、动画、事件,PC与移动的差异外。移动端出现了大量的单页应用。一方面因为PC上浏览器有很强的tab标签概念,在移动端,很多时候我们的网页是内嵌在webview中,用户更喜欢后退,而不是去选择tab标签;用单页构建应用会更接近原生的体验,从用户角度来说体验更佳,有更快的导航。另一方面单页应用还减少了带宽,降低了流量。需要的javascript文件只用在第一次的时候加载,服务端只用返回json数据,所有的页面html拼装工作都放到客户端来进行。

单页应用对于前端工程师来说,其实是加大了开发难度的。因为前端同学需要从整体的架构上来把握,比如考虑单页应用框架、前端模板、UI库、模块化开发、测试等等问题。当然这些问题在非单页应用中也是会涉及的。移动端单页应用的一个特点是,需要考虑转场动画,转场就是从第一个页面转到第二个页面,相信转场动画的出现也是来自于移动端的交互方式。所以说不同的交互方式会导致我们开发方式的变化。不去讨论单页应用这个巨大的话题,就单单讲一讲转场这方面。核心思路:多个页面层叠加,利用css3的transition属性进行层之间的转化。如何来做呢?

提供一种方法:每个单页层都利用position:absolute来固定,用hash标明不同的页面;单页之间切换的时候,利用transition来做切换动画。

1
2
3
4
5
6
7
/*单页层代码片段:*/
position:absolute;left:0;top:0;right:0;bottom:0;
width:100%;height:100%

/*切换动画(往左滑动100%,即该层整体往左滑动):*/
-webkit-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0);

当然在做页面层之间切换的时候,也有很多种的动画样式,比如是“盖上去”,还是“推出去”等。不同的动画对应不同的css手段。这方面也有专门的文章来介绍,下面就用一个简单的多页面切换的例子来结束这部分吧。如下图所示是两个页面层切换的场景。切换的时候第二层往左滑动盖在了第一层的上面。滑动的触发可以通过手指的滑动动作或者点击某个按钮、区域等。

页面层切换的一种场景

图8 页面层切换的一种场景

下面提供了一个比较简单的切换页面层的例子,包括了html、css和JavaScript代码,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*JavaScript代码段:*/
function PageSwitcher(){
var currentPage,
stateHistory = [];
this.switchPage = function(){
var l = stateHistory.length,
state = window.location.hash;

if(state == stateHistory[l-2]){
page = currentPage.prev('.page');
if(page && page.length > 0){
currentPage.attr('class','page transition right100');
currentPage = page;
stateHistory.pop();
}
}else{
page = currentPage.next('.page');

if(page && page.length > 0){
stateHistory.push(state);
page.attr('class','page transition center');
currentPage = page;
}
}
},
this.init = function(obj){
currentPage = obj.curPage;
stateHistory.push(obj.curHash);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*css代码段:*/
#container {
position: absolute;left: 0;top:0;right: 0;bottom: 0;
width: 100%;
height: 100%;
overflow: hidden;
}


.page {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}

.page.center {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}

.page.transition {
-webkit-transition-duration: .25s;
transition-duration: .25s;
}


.page.right{
-webkit-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0);
}

.page.left{
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--html代码段:-->

<div id="container">
<div class="current page center">
<a href="#page2" class="btn btn-next">page2</a>
</div>
<div class="page2 page right" style="background:lightgreen">
<a href="#page1">back</a>
</div>
</div>
<script>
var pageSlider = new PageSwitcher();

$(window).on('hashchange', function(){
pageSlider.switchPage();
});

pageSlider.init({curPage : $('.current'),curHash:'#page1'});
</script>

总结

本文从布局、图片、文字、动画、事件5个方面分别介绍了移动端与PC端的一些不同,并给出了一些在实际工作中常用方法的建议。最后从应用的层面上再一次地强调了移动端在用户交互上的改变,从而导致我们日常开发工作的改变。本文的内容可以算是移动端的基础入门,对其中某一方面感兴趣的都可以去做深入的研究,因为每一个分支在移动上都是一个全新的领域,需要我们去挖掘。前端开发作为一个端的开发分支,同样面临着跟其他客户端一样的问题,当我们的外部环境变化的时候,我们的技术储备,技术更新就需要更新换代。客户端开发遭遇了从PC客户端到Android等智能手机客户端的变迁,同样我们也是从PC时代步入移动时代。当我们在面临大的环境变革的时候,希望大家都能及时抓稳方向盘,积极地从内在的技术知识上进行变革,跟上这股浪潮而不是被它拍死在沙滩上。