移动端Web开发小结

##视口

###设备像素和CSS像素

  • 设备像素是设备屏幕的物理像素,任何设备的物理像素的数量都是固定不变的;
  • CSS像素是为Web开发者创造的,在CSS中使用的一个抽象的像素层。

e.g.

当给一个元素设置width: 200px;时,该元素可能会覆盖200个设备像素,也可能不会,比如在高分辨率屏幕上,一个CSS像素等于多个设备像素。当将该元素放大时,它覆盖的设备像素越多,而将其缩小时,它覆盖的设备像素就会减少。

为什么会这么设计呢?因为当设备屏幕分辨率高时,如果一个设备像素 = 一个CSS像素,则会使元素的尺寸缩小,例如字体缩小。

###视口

三个视口:布局视口视觉视口理想视口

####布局视口 在传统浏览器中,默认情况下布局视口等于浏览器窗口大小,CSS布局根据这个尺寸计算布局,可以通过为初始包含块设置超过浏览器窗口尺寸的大小使布局视口扩大,但是在移动设备中,布局视口与设备屏幕宽度完全独立,CSS布局将被布局视口约束。

####视觉视口

视觉视口与设备屏幕一样宽,它的CSS像素可随用户的缩放而改变。

#####理想视口

默认的布局视口并不适合移动设备,因此需要一个理想的布局视口,当一个网站愿意支持移动设备并为它做出优化时,可以手动设置布局视口等于理想视口,否则布局视口任维持默认宽度。

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">

meta视口标签应该放在<head>标签中,每一个名值队都是指令,它们被逗号隔开,共有5个。

  1. width: 设置布局视口的宽度为特定的值;
  2. init-scale: 设置页面的初始缩放程度及布局视口的宽度;
  3. minimum-scale: 设置最小缩放程度(用户可缩小的范围);
  4. maxmum-scale: 设置最大缩放程度(用户可放大的程度);
  5. user-scalable: 是否阻止用户进行缩放。

width 该值将布局视口的宽度设置为设备宽度(理想视口宽度)。理想视口宽度应随设备的横竖屏旋转而改变,但是在iOS的Safari中则不会。

当然,也能将布局视口的宽度设置为一个具体的值,但是在安卓WebKit中,不允许任何小于布局视口的宽度,否则它将自动转换为默认布局视口(通常是980px),而在IE10中,不允许任何超出480px的值,否则会自动转换为默认宽度(1024px)。

initial-scale 该指令指定了页面的初始缩放程度。缩放程度是与视觉视口成逆相关的。,越高的缩放程度意味着视觉视口越小。

initial-scale有一个副作用:它同时将布局视口的尺寸设置为缩放后的尺寸。因此,initial-scale=1width=device-width具有相同的效果。

当设置initial-scale=1时,Safari的理想视口尺寸将会随着屏幕的旋转而改变。在竖屏模式下,布局视口的宽度是320px,在横屏模式下是480px或568px。

然而在IE10上则相反,initial-scale为1时它在横屏模式下也保持320px的宽度,但是在设置width=device-width时它会响应横屏。因此在设置视口meta标签时应该同时设置width=device-widthinitial-scale=1

####分辨率

#####物理分辨率

屏幕实际物理像素数量。用像素数量除以屏幕以英寸为单位的宽度可以得到设备每英寸的点数(DPI)。无法通过js获取到这个物理尺寸值。

#####设备像素比

设备像素比(DPR: Device Pixel Ratio):设备像素个数与理想视口的比值。

e.g.

早期iPhone设备物理像素宽度是320px,理想视口像素宽度也是320px,DPR为1;而在iPhone4及以上中,设备物理像素宽度是640px,而理想视口宽度还是320px,因此DPR为2。

JS可使用window.devicePixelRatio获取设备DPR,CSS中也有device-pixel-ratio和分辨率的媒体查询。

//js
if (window.devicePixelRatio > 1) {
	//DPR大于1时执行
}

//css
@media all and (-webkit-min-device-pixel-ratio: 2) {
	//DPR大于等于2时执行
}

在IE11及以下版本中不支持DPR,必须使用dpi来进行代替。 一英寸对应CSS中96个像素,因此,1DPR等于96dpi

@media all and ((-webkit-min-device-pixel-ratio: 2),
	 (min-resolution: 192dpi)) {
	/*DPR大于等于2时执行*/
}

###媒体查询

媒体查询有三种类型:

  1. 媒介类型查询:这是什么类型的设备;
  2. 视口相关媒体查询;
  3. 特性相关媒体查询:浏览器是否支持某个特性。

####媒体类型

媒体类型中唯一有用的是print类型,其他类型从未被正确的实现。

####视口媒体查询

@media all and (max-width: 400) {
	/*css代码*/
}

首先,所有媒体查询都需要定义媒体类型,一般设置为all或screen;

第二点,在判断条件中总是使用min-或max-前缀,通常情况下你不应该只关心一个值而应该关心一个范围;

最后,虽然没有定义正式的单位,但媒体查询的默认单位是像素,也可以显式的使用em等其他css单位(注意,百分比不是特别有用)。

在媒体查询中使用height并不是那么容易,因为他会计算上浏览器的工具栏,而这个工具栏在用户滚动时隐藏。

####在媒体查询中使用em单位

在CSS中,1em等于一个字体的大小,在媒体查询中,em对应的是文档根元素的字体大小,而不是一个特定的元素。

在移动端,根元素的字体大小在缩放的时候是不会发生变化的,缩放是放大CSS像素的过程,这个过程与字体大小没有任何关系,所以不管用户缩放了多少,1em的宽度还是同样多的CSS像素。

####device-width和device-height

应该壁面使用device-width和device-height,因为它在所有浏览器上总是使用screen.width和screen.height,这个值在不同的浏览器中有不同的实现,有可能给出理想视口的尺寸,也有可能给出的是设备像素的数量。

####device-pixel-ratio和分辨率

现阶段基于WebKit的浏览器需要使用-webkit-device-pixel-ratio,而其他浏览器使用resolution(分辨率)。resolution可接受dpi和dpr两种单位值(但需要显式的设置单位),然而所有浏览器都支持dpi但是都不支持dpr的单位dppx,因此,最好使用dpi。将两种查询组合成跨浏览器的解决方案:

@media all and ((-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi)) {
	/*定义设备像素比大于等于1.5时的样式*/
}

####orientation

该属性能探测当前设备的转向。值为landscape(横屏) 或 portrait(竖屏)

####aspect-ratio和device-aspect-ratio

这两个值分别提供了布局视口宽高比和screen.width/screen.height的值。这两个值都使用一个分数表示。但要注意现在或未来浏览器工具栏可能会改变布局视口的比例。

###JS读取视口属性

docuent.documentElement.clientWidth获取浏览器尺寸,不包含滚动条,即布局视口尺寸;

window.innerWidth获取包含滚动条宽度的浏览器尺寸,即视觉视口尺寸;

screen.width返回的尺寸在不同浏览器中有不同的实现,可能为理想视口尺寸,也可能为屏幕物理像素尺寸。

一般我们使用docuent.documentElement.clientWidth进行js的媒体查询。

js也提供了window.devicePixelRatio属性读取DPR值。

####改变meta视口标签

无法通过移除meta视口标签来改变布局视口为默认值,但是可以给meta标签的width属性设置固定值改变布局视口大小。

var meta = document.getElementsByTagName('meta')[0];
meta.setAttribute('content', 'width=400');

**注意:**IE、firefox和部分浏览器不支持这个操作。

####orientationchange事件

改变设备方向时,将触发orientationchange事件,IE11和Firefox31暂时不支持该事件。

####resize事件

该事件在视口尺寸改变时触发,但目前为止,大部分浏览器的实现方案都不尽相同,因此暂时不可使用。

##CSS

移动端和桌面端对CSS支持不尽相同,开发方式也有所不同,原因在于:

  1. 桌面端的用例子触摸屏上不存在,或者说无法很好的适应触摸屏。例如,hover。
  2. 移动端有多重视口需要考虑,以及对应的单位,例如vw和vh。
  3. 对独立滚动层的需求,由于移动端资源受限,在移动端更难实现,例如,background-attachment ( 用于设置背景图像是否随元素滚动 )
  4. 由于硬件和性能受限,动画及过渡在移动设备上可能无法使用。

###position: fixed

position: fixed应该相对于布局视口而非视觉视口定位,但在一些老的浏览器中实现为根据视觉视口定位。原因在于用户滑动时固定层不动而其他部分是需要滚动的,这就需要浏览器需要支持每个层的独立滚动,然而这在浏览器上很难实现。

IE11支持’position: device-fixed’参数。

###overflow: auto

如之前所述,移动端浏览器很难实现独立的滚动层,因此,对于overflow: auto这样会触发独立滚动层的属性应该慎用。

###overflow-scrolling

Safari和一些浏览器默认禁用了overflow-scrolling,因为这能降低能耗,但是Safari也提供了-webkit-overflow-scrolling: auto来提供更好的滚动效果。

###background-attachment

该属性可设置独立滚动的背景图,有三个可选值:

  • scroll: 这是默认值,背景图随页面一起滚动;
  • fixed: 背景图相对于视口不动,这样元素就好像一个窗口,滚动就像移动窗口,可以看到图片的不同部分;
  • local: 背景图随元素一起滚动。

显然,随元素而非页面一起滚动的local的背景图是一个独立的滚动层。

而fixed是相对于视觉视口,所以也会创建独立的滚动层。

大多数浏览器只支持fixed和local中的一个,因此在移动端该属性并不可靠。

###尺寸单位vw和vh

vw和vh是相对与布局视口的百分比值。

Android 4.4以下的Android Browser不支持该属性,另外在iOS 7 Safari中,有时会根据html元素高度进行计算,因此导致当添加新元素后vh高度变化。

##JS事件

随着触屏手机的兴起,触摸、鼠标、和键盘三种交互方式并存,因此必须确保网站对三种交互的支持。

###触摸事件

一共有四种触摸事件:

  • touchstart: 在手指触摸屏幕的瞬间触发;
  • touchmove: 在移动手指的过程中连续触发;
  • touchend: 在手指离开屏幕的瞬间触发;
  • touchcancel: 在触摸序列被取消时触发,该事件大多数浏览器中并没有实现,从代码的安全起见,可将其等价与touchend事件。

思考交互模式和JavaScript事件的三个问题:

  1. 是不是每个交互模式都需要自己的事件?
  2. 已有的交互模式的事件在新设备上不再有意义时,是否继续支持?
  3. 如何判断设备支持哪种交互模式或用户正在使用哪种模式?

####等价事件

有些事件是等价的:

鼠标触摸键盘
mousedowntouchstartkeydown
mousemovetouchmovekeydown/keypress
mouseuptouchendkeyup
mouseover-focus
mouseout-blur

可以发现,触摸序列touchstart-touchmove-touchend和鼠标序列mousedown-mousemove-mouseup及键盘序列keydown-keypress-keyup很相似。然而它们之间仍有不同之处:

当鼠标指针移入元素或按下按键时,系统可以马上判断出应该触发哪个事件,而在触摸事件则不然,在触摸瞬间,系统无法判断你的意图,是tap某个元素,还是滚动滚动层,还是缩放等等。因此浏览器必须等一段事件后才能进行判断和响应事件,这就造成了一个可感知到的时间间隔。

e.g.

在移动端浏览器中,click事件有300毫秒的延迟,因此在需要使用click事件时,我们通常阻止click事件默认动作,而将执行函数绑定在tap事件中(Zepto提供的事件)。

element.on("click", function(evt){
	evt.preventDefault();
	evt.stopPropagation();
	return false;
})

element.on("tap", function(evt){
	//执行函数
})

其次,鼠标指针事件总是指向某个像素,而手指触摸会覆盖很多像素点,系统会从这些像素点中计算一个中心值作为触摸事件的坐标,因此,触摸事件的坐标值并不精准。

触摸事件是不连续的,而鼠标事件是连续的。

另外,触摸有多点触摸而鼠标则无法模拟。

####指针事件

微软认为没必要分出触摸和鼠标两种事件,因此它创造了指针事件。

鼠标 触摸键盘
pointerdownkeydown
pointermovekeydown/keypress
pointerupkeyup
pointeroverfocus
pointeroutblur

####检测浏览器是否支持某种事件

检测浏览器是否支持某种事件最好的方法是依次检测各种交互模式:

var interactionMode;
document.onpointerdown = function (e) {
	interactionMode = e.pointerType;
}

document.ontouchstart = function () {
	if (!interactionMode) {
		interactionMode = "touch";
	}
}

document.onmousedown = function () {
	if (!interactionMode) {
		interactionMode = "mouse";
	}
}

document.onkeydown = function () {
	if (!interactionMode) {
		interactionMode = "keyboard";
	}
}

###触摸事件的级联

当用户触摸屏幕时会触发一系列的触摸事件及鼠标事件,这便是触摸事件的级联。

####轻触操作

当用户轻触某个元素时将触发下列事件:

  1. touchstart/pointerdown
  2. touchend/pointerup
  3. mouseover
  4. mousemove
  5. mousedown
  6. mouseup
  7. click
  8. :hover样式应用于元素

具体的事件顺序因浏览器而异。这些顺序的不同并不会有什么影响。由于一次将触发多个事件,因此,尽可能的最多只处理一个鼠标事件。

当用户点击其他元素时,mouseout事件在前一个元素触发,:hover样式被移除。

如果用户再次轻触同一个元素,整个事件级联再次触发,除了mouseover。

####其他事件

  1. 滑动动作 (Swipe):touchstart、touchmove、touchend、scroll
  2. 缩放动作(Pinch):touchstart、touchmove、touchend、scrill,可能还有resize
  3. 双触(Double-tap):touchstart、两次touchend、scroll,可能还有resize
  4. 按住(Touchhold):touchstart、touchend,有些浏览器还会触发contentmenu事件

###Safari

Safari有两个不同的地方,一个是如果mouseover或mousemove事件引起了内容的改变,那么Safari将会取消后续的级联事件,并且不会触发mousedown、mouseup、click事件。

另一个与众不同的地方是鼠标和click事件只在特定的情况下回冒泡到document:

  1. 目标元素是一个链接或者表单域;
  2. 目标元素或其祖先元素(上溯至<body>但不包括)显式包含任意鼠标事件的处理函数。
  3. 目标元素或其祖先元素(上溯至并包括document)包含cursor: pointer CSS声明。

因此,如果需要在Safari中将鼠标或click事件冒泡到document或body,但没有生效,只需给目标元素设置一个空的click事件处理函数即可: targetElement.onclick = function(){};

###Click事件

当前所有浏览器都支持click事件,绝大部分浏览器都依靠click事件。

####300毫秒延时

在触摸屏幕到执行click事件函数间,将产生300毫秒的间隔,这是因为触摸屏幕的行为被重载(overload)了。在手指触摸屏幕的瞬间,浏览器无法预知用户是在轻触(tap)、双触(double-tap)、滑动(swipe)、或是其他操作,因此浏览器只能等待一段事件来确定当前是什么行为,尤其是在双触的时候,即使手指离开的屏幕,浏览器依然不知道手指是否还会再次回来,为此,浏览器不得不进行300毫秒的间隔进行判断。

目前在chrome浏览器中,当页面包含 <meta name="viewport", content="width=device-width"> 时,Chrome会假设不会出现双触操作,因为页面不需要进行缩放,此时会移除300毫秒的时间间隔。

而在IE中,可以通过在CSS中设置touch-action: manipulation 移除延时。

####同一个像素 click事件只有当mousedown和mouseup指向同一个像素时才会触发,在桌面浏览器中,鼠标指针总是指向一个像素,而在触摸设备中,由于手指不可能精准到某一个像素点,因此移动浏览器会给用户预留一些余地,而不是touchstart和touchend必须在同一个像素上。因此,如果在测试版的浏览器上发现无法触发click事件,很可能是浏览器的问题而不是你的。

###触摸事件

和其他事件一样,触摸事件对象有事件类型、事件目标对象、可以阻止默认行为。

####touchList

触摸事件有一个指针事件没有的特性:touchList数组属性,其中包括了每个触摸点的信息。如果用户使用四个手指触摸屏幕,那这个数组就会有4个元素。一共有三个这样的数组:

  1. touches:当前触摸屏幕的触摸点数组;
  2. changedTouches:导致触摸事件被触发的触摸点数组;
  3. targetTouches:事件目标元素上的触摸点数组。

e.g.

用户将四个手指放在屏幕上,其中两个手指放在同一个div中,当一个手指移动时,触发touchmove事件,changedTouches数组将包含引起touchmove事件的那个触摸点的信息;而targetTouches数组中包含那两个放在div元素上的触摸点的信息。

使用changedTouches时有一点注意:如果用户最后一个手指离开屏幕触发touchend事件,这最后一个触摸点信息不回出现在touches或targetTouches数组中,但会出现在changedTouches数组中,因为是它的离开触发了touchend事件,所以changedTouches数组仍然包含它。

####获取事件坐标

IE不支持touchList,但是可以通过全局事件对象给出触摸坐标信息,因此我们可以实现一个兼容所有浏览器的获取事件坐标函数如下:

function findCoordinates(e) {
	// 如果需要,可以使用pageX/Y代替clientX/Y
	var x, y;
	if (e.changedTouches) {
		x = e.changedTouches[0].clientX;
		y = e.changedTouches[0].clienty;
	} else {
		x = e.clientX;
		y = e.clientY;
	}
	return [x,y];
}

changedTouches数组中的第一个元素就是导致事件触发的那个触摸点对象,这个触摸点对象有clientX/Y (相对于视觉视口的左上角)和pageX/Y(相对于布局视口的左上角)坐标信息.

###指针事件

指针事件目前仅在IE中实现,有时候需要加上MS前缀,IE10需要将事件名按驼峰法书写。指针事件无法在JS中阻止默认行为。

el.onmspointermove = doSomething;
el.onpointermove = doSomething;
el.addEventListener("MSPointerMove",doSomething, false);
el.addEventListener("pointermove", doSomething, false);

####touch-action

touch-action CSS声明告诉浏览器哪种触摸操作应该被操作系统处理,目前该声明得到了IE和Chrome的支持

触摸操作含义
none操作系统不处理任何触摸操作
auto操作系统处理所有操作
pan-x操作系统处理水平平移
pan-y操作系统处理垂直平移
pinch-zoom操作系统处理多指缩放操作
double-tap-zoom操作系统处理双触缩放操作
manipulation操作系统处理除双触操作以外的其他触摸操作

最好不要使用none,因为它禁止了操作系统的所有触摸操作,你得自己处理滚动和缩放等。