手机端Web开发中遇到的那些问题

摘要

这个feature 应该不仅仅只关心手机端, 而且还包括PC, 因为苹果不仅仅在手机端 (从 iphone4 开始), 而且在, MAC 上也使用了 retina. 首先, retina 的意思就是, dpr(device-pixel-ratio)是普通屏幕的两倍. 对比于 CSS 来说就是, 原来在 css 中设置的是 1px , 在实际的屏幕显示时, 也是 1px. 但是, 在 retina 下, css 的 1px 就是实际屏幕的 2px.

retina 图片兼容

这个 feature 应该不仅仅只关心 手机端, 而且还包括PC, 因为苹果不仅仅在手机端 (从 iphone4 开始), 而且在, MAC 上也使用了 retina. 首先, retina 的意思就是, dpr(device-pixel-ratio)是普通屏幕的两倍. 对比于 CSS 来说就是, 原来在 css 中设置的是 1px , 在实际的屏幕显示时, 也是 1px. 但是, 在 retina 下, css 的 1px 就是实际屏幕的 2px.

1474709878-4846-css-device-pixels

这样的结果就是:

1474709892-6167-5b1b24e9458db8f80499d0684765

有同学会想, 会啥会这样呢? 命名看起来大小一样的, 为什么图片变模糊了? 这实际上是位图的特性. 比如一个 jpg 文件, 大小是 200×300. 那么他的实际屏幕像素就是 200px × 300px. 放在 dpr=1 的屏幕中, 大小设置为:

width: 200px;
height:300px;

这样, 当然可以正常显示. 但在 retina 中, 每个 css px 等于 2 倍的 px. 这样, 原来正常显示的图片就被放大了一倍. 原来图片上每个点,可以正好的放在 dpr=1 的屏幕中, 而在 dpr=2 时, 每个点都被拆分了, 所以就有锯齿造成模糊的现象.

而解决办法也有很多.

使用 media query

这种方法适用于背景图的添加. 使用到的是 css3 提供的 device-pixel-ratio query 来进行执行. 简单的demo就是:

#myimage {
	width: 400px;
	height: 300px;
	background: url(lo-res.jpg) 0 0 no-repeat;
}
// 这里主要针对的是 Android 的一些设备
@media
screen and (-webkit-min-device-pixel-ratio: 1.5),
screen and (min--moz-device-pixel-ratio: 1.5),
screen and (-o-min-device-pixel-ratio: 3/2),
screen and (min-device-pixel-ratio: 1.5) {
	#myimage {
		background-image: url(hi-res.jpg);
	}
}
// 如果你想针对苹果的, 则可以直接使用:
@media only screen and (-webkit-device-pixel-ratio: 2),
       only screen and (-moz-device-pixel-ratio: 2),
       only screen and (-o-device-pixel-ratio: 2/1),
       only screen and (device-pixel-ratio: 2) {
        #myimage {
		background-image: url(hi-res.jpg);
	}
}

但这样成本很大, 而且每次都需要准备两份, 做一些价值特别低的工作.

使用 js 判断

除了上面写冗余的 css 代码外, 还可以使用 js 进行判断. 然后,替换 data-src 里面的内容进行懒加载.

    if (window.devicePixelRatio > 1) {
        var images = $("img");
        images.each(function(i) {
            var x1 = $(this).attr('data-src');
            $(this).attr('src',x1);
        });
    }

而且,该属性的支持度挺高的, 基本上所有的手机端和PC 都支持, 除了 IE8.

使用矢量图

在 web 中使用矢量图的方式有很多. 比如, SVG, Fonts. 这两个, 应该是最适合的, 不过, 在画图时, 大多都是位图的形式, 所以, 需要转换为 SVG 和 fonts 来说, 难度有点大. 针对一些小的 logo 和 icon 来说, 还是没太大的问题的. 并且, 上述的两种方式所占用的空间大小也是很小的.

手机基本情况

现在手机的问题不在 js 脚本, 而在页面渲染. 因为, 手机的屏幕显示全是通过 CPU 进行处理. 而没有像 PC 端一样有独立的显卡专门来对图像进行绘制.

手机上的键盘

一般遇到需要输入的元素标签, 比如, input 当获取焦点时, 都会触发键盘的弹出. 但, 对于 ios 和安卓, 这两者的键盘弹出的处理方式不一样.

ios 的键盘

键盘的渲染有两种方式:

  • 如果 input 已经在键盘的上方, 则只是会将控件向上推一点, 推导键盘的上方.
  • 如果 input 在键盘的下方,键盘会覆盖该控件, 并将整个页面向上推, 直到控件推到键盘上方为止.

另外, 当在 ios7 一下时, 如果有元素是 fixed 属性. 那么,此时打开 键盘时, fixed 有可能会当做 absolute 进行渲染. 所以, 这真是个问题.

android 的键盘

同样,也有两种情况.

  • 第一种没问题
  • 第二种, 当 input 在键盘的下方, 会将整个 document 的高度增加, 直到控件高度超过键盘高度为止.

对于 android 将整个 document 向上推的情况, 对于绝对定位和 fixed 属性定位来说. 会存在一定的问题. 增加 document 并未增高 viewport 的位置, 所以, 如果使用 fixed 可能会出现, 元素跑到键盘下面. 但, 由于键盘是在整个浏览器上方的, 所以, 你也不可能覆盖掉键盘. 一般的解决办法就是, 监听输入的 focus 事件, 来动态 设置fixed 的位置.(不过好复杂).

软盘类型

针对于不同的输入,键盘上显示的类型实际上是不一样的, 一般兼容性比较好的是:数字/手机号. 可以设置为:

input[type=tel]
input[type=number]

软盘人工弹起

当用户没有触发 input 的 focus 事件. 而是开发者人工触发的, 这里就有两种不同的情况.

IOS

ios6 以前, 当控件触发了 focus 事件, 但, focus 不是用户触发的, 那么键盘是不会弹起的. 在 ios6 以后, 可以手动添加一个 autofocus 属性即可.

Android

只要不是用户触发的,都不能弹起.

键盘的收起 键盘的收起直接触发 js 的 blur 事件即可.

页面滚动

设计到页面滚动有两个事件, 一个是 scroll, 一个是 touchmove. 手机端为了解决性能问题, 当页面进行滚动时, js 进行的动态渲染是无效的, 即, 使用 js 改变页面上元素的位置,是无效的. 知道页面滚动结束才行. 这种效果主要体现在 scroll 事件触发的机制上. 在 ios8 以下, 当页面滚动时, js 的渲染被暂停了. 而,对于 Andriod 4.0 以上来说, scroll 触发都是连续滚动的. 如果你想设置局部滚动, 可以添加 -webkit-overflow-scrolling: touch css 属性.

flex 问题

由于历史原因, 想在 web 上实现 flex 的效果. 则需要注意他的兼容性, 因为 flex 有三个版本, 而且三个版本的支持性都不一样. 分别是:

  • display: box
  • display: flexbox
  • display: flex

由于 Android 使用的是 Webkit 开源内核, 我们需要给 flex 加上 webkit 前缀, 来兼容 低版本 Android.

  display: -webkit-box;
  display: -moz-box;
  display: -ms-flexbox;
  display: -webkit-flex;
  display: flex;

目前的兼容性是:

1474709873-2452-E785A7202016-09-212013.26.34

  • old 使用的是 display: box;
  • tweener 使用的是 display: flexbox;
  • new 使用的是 display: flex;

fixed 问题

在 mobile 里面使用 fixed 是一个比较复杂的问题. 因为,在输入时, 往往还会设计到键盘的弹出, 鉴于 ios 和 android 的不同效果. fixed 有可能在键盘弹出时, 出现错位的现象. 例如:

1474709873-3012-111030pvtt0t0nfen6tef4

具体参考: fixed. 在 ios5 以前是不支持 fixed, android 在 4.x 之后, fixed 基本上才可用.

touch 事件解析

300ms click 时延

click 时延算是, web 手机开发的一个坑吧. 因为厂商在设计手机端时, 主要, 考虑到缩放的事. 即: 当第一次点击时, 并不会触发 click 时间, 浏览器会等待 300ms 时延, 并判断你在 300ms 内,有没有再次点击, 如果有, 则会触发 zoom in 的效果。 后来, 300ms 时延被取消了, 同时, 长按选中文本也取消了. 后来觉得不行, 又加上了. 在 Chrome 32+, IE/FF 都是 Ok的. 最近在 IOS 9.3 中也得到了修复. 不过前期是, 你需要在 head 标签里面加上.

// 告诉浏览器取消双击放大的效果
<meta name="viewport" content="width=device-width">

或者给特定的 tag 加上特定的 css 属性

html {
  touch-action: manipulation;
}

不过 FF 不支持.

click 点透

这个 feature 应该算是比较经典了. 当一个罩层覆盖在一个 a 标签上。

<div id="model"></div>
<a href="www.villainhr.com">www.villainhr.com</a>

此时, model 是覆盖在 a 标签上的. 当点击罩层, 罩层消失. (罩层绑定的是 tap 事件) 由于, click 事件有 300ms 时延, 所以, 此时 a 标签的跳转效果会触发. 解决办法是:

  • 设置罩层延时消失效果. 延时时间设置为 300ms+ 即可.
  • 在 touchend 里面执行 preventDefault()
  • 使用插件禁止掉 click 事件, 转而使用模拟的.

触摸事件详解

在手机上是没有关于鼠标的相关操作的, 比如 hover, mouseover, mousenter等等. 只有, 相关的 touch 事件.

  • touchstart 当第一根手指触摸到屏幕时, 触发.
  • touchmove 当某一个手指在屏幕上移动时, 触发.
  • touchend 当手指从屏幕上移开时, 触发.
  • touchcancel 当手指触控被打断时触发. 具体有以下下几种.
    • 其他事件的发生打断了 touch 事件. 比如, js 操作强制跳转
    • 当浏览器的 UI 覆盖到当前的 web 上
    • 触摸的手指数超过了浏览器的支持数量. 如果发生, 那么第一根触摸的手指会被取消

// 暂不支持下列两个事件

  • touchenter 当手指进入指定元素
  • touchleave 当手指移出指定元素

因为在触发 touch 事件时, 不仅仅只是相关的 touch 事件会触发, 还会触发相关的 mouse 和 click 事件. 所以, 如果 mouse 和 click 事件有影响时, 则需要显示的使用 event.preventDefault()取消掉后面的触发机制.

在每个 touch 事件里面, 还会返回挂载到 event 上的属性, 常用的有:

  • touches: 当前屏幕上的属性
  • targetTouches: 在指定 DOM 元素上的属性
  • changedTouches: 返回触发时间的相关手指数. 比如, touchmove 事件中, 返回正在移动的手指. 在 touchend 事件中, 返回移除的手指.

并且每一个 touch 上面都会附带相关的属性.

  • identifier: 每一个手指独一无二的 ID
  • target: 返回一开始触发 touch 事件的 DOM 元素
  • screenX/Y: 返回相对于整个手机屏幕而言的位置.
  • pageX/Y: 返回相对于整个页面的位置, 包括 scroll 的距离.
  • clientX/Y: 返回相对于浏览器 viewport 的位置. 不包含 scroll 的距离.
  • radiusX/Y: 返回手势椭圆的长轴和短轴的大小. 目前来说, 还不支持. 和 screenX/Y 有点类似.

touchmove 的坑

移动而不滚动 当触发 touchmove 时. 如果,不加限制的话, 往往会触发 scroll 的效果. 为了消除这样的问题, 只需要将默认行为禁掉就 ok.

document.body.addEventListener('touchmove', function(event) {
  event.preventDefault();
}, false); 

不要将 touchmove 来用作渲染触发 因为, touchmove 的机制是浏览器自身来决定的. 他触发的次数是很有限的. 所以, 我们一般只是利用 touchmove 来获得数据, 而渲染则需要使用 requestAnimationFrame .

var touches = []
canvas.addEventListener('touchmove', function(event) {
  touches = event.touches;
}, false);
// Setup a 60fps timer
timer = setInterval(function() {
  renderTouches(touches);
}, 15);

媒体查询

媒体查询机制在 css2 里面就已经提出来. 比如, 针对打印机的:

<link rel="stylesheet" media="print" href="printer.css">

不过, 在 css3 中, 媒体查询的机制得到了补充. 并且除了 IE8 以外, 其他浏览器或者说, 手机端都是支持的. 他主要的用途还是区分 手机端,PC端, 屏幕的大小等.

屏幕大小

先看个 demo:

<link rel="stylesheet"
  media="only screen and (min-width: 641px) and (max-width: 800px)" href="ipad.css">

设置当屏幕的大小为 [641,800]之间时, 才加载 ipad.css. 如果, 不符合, 浏览器会默认忽略这个 tag. 其中 only 这个 flag 是非常重要的. 他主要作用就是告诉浏览器不符合则忽略的规则. 通过, 屏幕的大小来判断 mobile , pad , PC 是比较有用的.

meta 标签

meta 主要用来设置网页的源信息, 比如, 缩放, 宽度等等. 最常用的就是手机端上的

<meta content="
width=device-width;  // 网页初始宽度和设备宽度一致
initial-scale=1.0;  // 初始放大效果
maximum-scale=1.0;  // 最大放大效果
user-scalable=no"   // 防止用户缩放
name="viewport" /> 
<meta content="yes" name="apple-mobile-web-app-capable" />  //允许全屏模式浏览
<meta content="telephone=no" name="format-detection" /> //忽略将屏幕中的数字识别为电话号码;

这里只是整理了一些皮毛, 有兴趣的同学,可以参考另外两篇文章:

移动web问题小结 无线Web开发经验谈

后面如果遇到些比较坑的问题, 会及时更新的。

  • 版权声明: 本文源自 VillianHR, 于2个月前,由整理发表,共 5754字。
  • 原文链接:点此查看原文

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: