touch事件详解及应用
此篇文章主要介绍touch类的事件, 也会顺带介绍下mouse类的, 因为比较类似
分类
touch类和mouse类的时间主要有
touch 事件
touch事件初体验: http://demo.404mzk.com/event/base/window_all_type.html
- touchstart: 手指触摸屏幕时触发
- touchmove: 手指滑动屏幕时触发
- touchend: 手指离开屏幕时触发
- touchcancel: 手指触摸屏幕期间内, 由于手机电话、按HOME健离开浏览器时触发
touch事件对象包含属性
上图为ios safari下的console
- touches: 当前屏幕上的手指对象数组
- targetTouches: 当前绑定元素上的手指对象数组
- changeTouches: 状态发生改变的手指对象数组
这几个数组里的对象包含比较重要的属性有
- clientX: 触摸目标在视口中的x坐标
- clientY: 触摸目标在视口中的y坐标
- pageX: 触摸目标在页面中的x坐标
- pageY: 触摸目标在页面中的y坐标
- screenX: 触摸目标在屏幕中的x坐标
- screenY: 触摸目标在屏幕中的Y坐标
应用
判断各种手势
手势判断初体验: http://demo.404mzk.com/event/base/alloy_finger.html
这里按照讲解AlloyFinger源码的思路来解析各种手势
AlloyFinger在构造函数中监听元素移动
var AlloyFinger = function (el, option) {
...
this.element.addEventListener("touchstart", this.start, false);
this.element.addEventListener("touchmove", this.move, false);
this.element.addEventListener("touchend", this.end, false);
this.element.addEventListener("touchcancel", this.cancel, false);
...
}
比如在轮播图中, 要判断用户向左滑还是向右滑
tap
在移动端里, 如果监听click时间, 会有300ms的延迟时间(因为浏览器需要判断该次点击是否为其他操作)
移动端click有300毫秒延时,tap的本质其实就是touchend。但是要判断touchstart的手的坐标和touchend时候手的坐标x、y方向偏移要小于30。小于30才会去触发tap
AlloyFinger.prototype = {
start: function(evt){
this.x1 = evt.touches[0].pageX;
this.y1 = evt.touches[0].pageY;
//防止是长按 或滑动等其他操作
this._preventTap = false;
this.longTapTimeout = setTimeout(function () {
this.longTap.dispatch(evt, this.element);
this._preventTap = true;
}.bind(this), 750);
},
move: function(evt){
var currentX = evt.touches[0].pageX,
currentY = evt.touches[0].pageY;
this.x2 = currentX;
this.y2 = currentY;
},
end: function(evt){
var self = this;
if ((this.x2 && Math.abs(this.x1 - this.x2) > 30) ||
(this.y2 && Math.abs(this.y1 - this.y2) > 30)) {
...
} else {
if(!self._preventTap){
//触发tap
self.tap.dispatch(evt, self.element);
}
}
}
}
longtips
touchstart开启一个750毫秒的settimeout,如果750ms内有touchmove或者touchend都会清除掉该定时器。超过750ms没有touchmove或者touchend就会触发longTap
AlloyFinger.prototype = {
start: function(evt){
this.longTapTimeout = setTimeout(function () {
//触发长按tips
this.longTap.dispatch(evt, this.element);
}.bind(this), 750);
},
move: function(evt){
clearTimeout(this.longTapTimeout);
},
end: function(evt){
clearTimeout(this.longTapTimeout);
}
}
swipe滑动
触发此事件的因素有
- touchstart的手的坐标和touchend时候手的坐标x、y方向偏移要大于30
- 判断x坐标的变化大还是y坐标的变化大, 假如x坐标变化大则为向左或右, 假如y坐标变化大则向上或向下
- 通过判断touchstart的pageX和最后的touchmove的pageX 对比大小, 判断向左还是向右,向上向下同理
AlloyFinger.prototype = {
start: function(evt){
this.longTapTimeout = setTimeout(function () {
//触发长按tips
this.longTap.dispatch(evt, this.element);
}.bind(this), 750);
},
move: function(evt){
this.x2 = evt.touches[0].pageX;
this.y2 = evt.touches[0].pageY;
},
end: function(evt){
if ((this.x2 && Math.abs(this.x1 - this.x2) > 30) ||
(this.y2 && Math.abs(this.y1 - this.y2) > 30)) {
evt.direction = this._swipeDirection(this.x1, this.x2, this.y1, this.y2);
this.swipeTimeout = setTimeout(function () {
self.swipe.dispatch(evt, self.element);
}, 0)
}
},
_swipeDirection: function (x1, x2, y1, y2) {
return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
}
}
拖动div
拖动初体验: http://demo.404mzk.com/event/base/move_div.html
- 阻止preventDefault事件主要防止拖动时 浏览器跟着滑动和微信页面向下滑动
- 将pageX和pageY减去div的一半是为了让物体置于手指中间
- 判断pageX和pageY最后的最大和最小值, 为了防止拖动出屏幕
var div = document.querySelector('.j_demo_div'),
half_width = div.offsetWidth / 2,
half_height = div.offsetHeight / 2,
min_x = 0,
min_y = 0,
max_x = window.innerWidth - div.offsetWidth,
max_y = window.innerHeight - div.offsetHeight
div.addEventListener('touchmove', function(event){
event.preventDefault()
var x = event.touches[0].pageX - half_width,
y = event.touches[0].pageY - half_height
if ( x < min_x ) {
x = min_x
}else if ( x > max_x){
x = max_x
}
if ( y < min_y ) {
y = min_y
}else if ( y > max_y){
y = max_y
}
div.style.left = x + 'px'
div.style.top = y + 'px'
})
下拉加载更多
下拉加载更多 需要实现的点有
- 下拉的过程中列表和刷新提示向下移动
- 滑到一定距离后 出现 松开可刷新 并且箭头翻转
- 手指离开屏幕 判断距离是否大于指定距离 大于则进行刷新
var scroller = document.querySelector("#scroller"),
arrow = document.querySelector("#arrow"),
pull_refresh = document.querySelector("#pull_refresh"),
list = document.querySelector("#list"),
index = 0;
Transform(pull_refresh, true);
Transform(scroller, true);
new AlloyTouch({
touch: "#wrapper",//反馈触摸的dom
vertical: true,//不必需,默认是true代表监听竖直方向touch
target: scroller, //运动的对象
property: "translateY", //被滚动的属性
initialValue: 0,
min: window.innerHeight - 45 - 48 - 2000, //不必需,滚动属性的最小值
max: 0, //不必需,滚动属性的最大值
change: function (value) {
pull_refresh.translateY = value; //控制顶部刷新提示向下移动
},
touchMove: function (evt, value) {
if (value > 70) { //切换顶部提示的文字和箭头方向
//http://caniuse.com/#search=classList
arrow.classList.add("arrow_up");
} else {
arrow.classList.remove("arrow_up");
}
},
touchEnd: function (evt, value) {
if (value > 70) { //判断松手时 大于70px 滑动则刷新
this.to(60);
mockRequest(this);
return false;
}
}
})
function mockRequest(at) {
pull_refresh.classList.add("refreshing");
setTimeout(function () {
var i = 0,
len = 3;
for (; i < len; i++) {
var li = document.createElement("li");
li.innerHTML = "new row " + index++;
list.insertBefore(li, list.firstChild);
}
arrow.classList.remove("arrow_up");
pull_refresh.classList.remove("refreshing");
pull_refresh.translateY = 0;
at.to(at.initialValue);
at.min -= 40 * 3;
}, 500);
}
DEMO示例: http://alloyteam.github.io/AlloyTouch/refresh/pull_refresh/
注意事项
阻止缩放
在双指操控屏幕、连续双击屏幕等操作下, 很多浏览器会有默认的缩放或方法页面的效果
所以有冲突的情况下 可以阻止网页缩放
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
具体说明
width - viewport的宽度
height - viewport的高度
initial-scale - 初始的缩放比例
minimum-scale - 允许用户缩放到的最小比例
maximum-scale - 允许用户缩放到的最大比例
user-scalable - 用户是否可以手动缩放
android4.x 浏览器touchend的bug
android4.x 默认touchstart后只触发一次touchmove 并且不触发touchend
只能在touchstart或touchmove 里event.preventDefault() 才能继续触发多次touchmove和touchend
但是要注意的是: 不建议在documet.body上绑定touchmove然后event.preventDefault()
, 因为会阻止浏览器滚动
一般针对需要处理的div , 然后在其下监听touchmove 去event.preventDefault()
var is_android_4 = navigator.userAgent.indexOf('Android 4') !== -1
document.querySelector('.dialog').addEventListener('touchmove',function(e){
if(is_android_4)
e.preventDefault();
})
ios 11 addEventListener的新限制
如果你出现了 evnet.preventDefault() 无法阻止 浏览器原生事件, 可能掉进这个新坑里了
http://blog.404mzk.com/yi-dong-duan-de-keng-keng-wa-wa.html#ios
参考链接
- aloyfiger原理详解: https://github.com/AlloyTeam/AlloyCrop/blob/master/asset/alloyfinger.md
- android4.x bug: http://www.cnblogs.com/shuiyi/p/5138006.html