移动端Tap与滑屏实战技巧总结以及Vue混合开发自定义指令
最近在忙混合开发,因交互相对复杂,所以也踩了很多坑。在此做一下总结。
1.tap事件的实际应用
在使用tap事件时,老生常谈的肯定是点透问题,大多情况下,在有滑屏交互的页面时,我们会在根节点阻止默认行为以解决事件点透的bug。
阻止默认行为有优点,但也会相对带来一些问题。
优点:
(1)解决事件点透
(2)解决IOS10+ safari 以及部分安卓浏览器 不在支持 viewport的最大缩放值和禁止缩放的问题
(3)解决IOS10+ safari下给body加overflow:hidden无效的问题
给元素加了 一个绝对定位,但是元素本身没有定位父级,元素如果超出了body的宽度,body 上的overflow对这个元素,不起效果
解决办法:增加一个div作为根节点,并且添加相对定位和overflow:hidden
缺点:
(1)禁止mouse事件执行(也可以说成是优点,具体情况具体分析)
(2)阻止浏览器的自带效果(左右滑动切换页面,滚动回弹等)
(3)阻止触发浏览器的滚动条
(4)阻止触发浏览器系统菜单
(5)阻止图片文字被选中
(6)阻止input的输入(在新开页面进行输入 eg.淘宝)
2.touchEvent相关变量
(1)touches 当前屏幕上的手指列表
(2)targetTouches 当前元素上的手指列表
(3)changedTouches 触发当前事件的手指列表
(4)clientX 和 clientY 手指相对于可视区的坐标
(5)pageX 和 pageY 手指相对于页面的坐标
3.tap事件封装要点
(1)touchend时确定最大距离,大于此距离无效
(2)确定从touchstart到touchend的时间间隔,大于此事件间隔无效
(3)严格意义上讲,touchmove时也应该有最大距离限制(手指在某元素上点击后绕了一大圈再回到当前元素不应触发tap事件)
(4)需要考虑tap事件对click的影响
(5)如有需要的话,还要限制为只有单指操作才能触发
4. 遵循上述要求的完整的tap封装如下:
当然你可以扩展为面向对象,或者试用与Vue的各种形式,但原理都是如此。最后会附上Vue版的常用指令
function tap(el, fn) {var start = {};
var moveOverLimit;
el.addEventListener(\'touchstart\', function(e) {
start = {
x: e.changedTouches[0].pageX,
y: e.changedTouches[0].pageY,
startTime: new Date().getTime()
}
moveOverLimit = false;
});
el.addEventListener(\'touchmove\', function (e) {
if (Math.abs(e.changedTouches[0].pageX - start.x) > 5 || Math.abs(e.changedTouches[0].pageY - start.y) > 5) {
moveOverLimit = true
}
})
el.addEventListener(\'touchend\', function(e) {
var end = {
x: e.changedTouches[0].pageX,
y: e.changedTouches[0].pageY,
endTime: new Date().getTime()
}
// 此处的限制如第三点说明的一样
if (Math.abs(end.x - start.x) < 5 && Math.abs(end.y - start.y) < 5 && end.endTime - start.startTime < 300 && !moveOverLimit) {
fn && fn.call(el, e);
}
});
}
5. Vue版的常用指令如下:
import Vue from \'vue\';// 限制为单手操作,可根据需要自行修改
function commonSingleSlideCheck (ev) {
if (ev.touches.length > 1) {
return false;
}
return true;
}
class VueSlide {
constructor (el, binding, vnode) {
this.el = el;
this.binding = binding;
this.vnode = vnode;
this.evType = \'\';
this.startPos = {x: 0, y: 0};
this.startTime = null;
this.movePos = {};
this.canPullToLeft = true;
this.canPullToRight = true;
this.canPullToDown = true;
this.canPullToUp = true;
this.typeCheck = {
\'toLeft\': commonSingleSlideCheck,
\'toRight\': commonSingleSlideCheck,
\'toUp\': commonSingleSlideCheck,
\'toDown\': commonSingleSlideCheck,
\'drag\': commonSingleSlideCheck,
};
}
start(ev, el, binding, vnode) {
this.startPos.x = ev.changedTouches[0].clientX;
this.startPos.y = ev.changedTouches[0].clientY;
switch (this.evType) {
default:
if (ev.targetTouches.length > 1) {
return false;
}
break;
}
if (this.evType === \'tap\') {
this.startTime = new Date().getTime();
if (binding.value.stop === true) {
ev.stopPropagation();
}
if (binding.value.prevent === true) {
ev.preventDefault();
}
}
}
move(ev, el, binding, vnode) {
this.movePos.disX = Math.abs(ev.changedTouches[0].clientX - this.startPos.x);
this.movePos.disY = Math.abs(ev.changedTouches[0].clientY - this.startPos.y);
this.movePos.changeX = ev.changedTouches[0].clientX - this.startPos.x;
this.movePos.changeY = ev.changedTouches[0].clientY - this.startPos.y;
this.movePos.dir = \'\';
if (!this.typeCheck[this.evType](ev)) {
return false;
}
if ((Math.atan(this.movePos.disX / this.movePos.disY) > Math.PI / 3)) {
ev.preventDefault();
if (this.movePos.changeX > 0 && this.movePos.disX > 30) {
this.movePos.dir = \'right\';
}
if (this.movePos.changeX < 0) {
this.movePos.dir = \'left\';
}
}
if ((Math.atan(this.movePos.disX / this.movePos.disY) < Math.PI / 6)) {
if (this.movePos.changeY > 0 && this.movePos.disY > 30) {
this.movePos.dir = \'down\';
}
if (this.movePos.changeY < 0) {
this.movePos.dir = \'up\';
}
}
if (this.evType === \'drag\') {
binding.value({
ev: ev,
startX: this.startPos.x,
startY: this.startPos.y,
changeX: this.movePos.changeX,
changeY: this.movePos.changeY,
dir: this.movePos.dir,
});
}
}
end(ev, el, binding, vnode) {
let disX = Math.abs(ev.changedTouches[0].clientX - this.startPos.x);
let disY = Math.abs(ev.changedTouches[0].clientY - this.startPos.y);
let changeX = ev.changedTouches[0].clientX - this.startPos.x;
let changeY = ev.changedTouches[0].clientY - this.startPos.y;
if (this.evType !== \'drag\' && this.evType !== \'tap\') {
if ((Math.atan(disX / disY) > Math.PI / 3) && disX > 30) {
if (changeX > 0 && this.evType === \'toRight\' && this.canPullToRight) {
// console.log(\'向右滑动\');
this.canPullToRight = false;
binding.value(el);
}
if (changeX < 0 && this.evType === \'toLeft\' && this.canPullToLeft) {
// console.log(\'向左滑动\');
this.canPullToLeft = false;
binding.value(el);
}
}
if ((Math.atan(disX / disY) < Math.PI / 6) && disY > 30) {
if (changeY > 0 && this.evType === \'toDown\' && this.canPullToDown) {
// console.log(\'向下滑动\');
this.canPullToDown = false;
binding.value(el);
}
if (changeY < 0 && this.evType === \'toUp\' && this.canPullToUp) {
// console.log(\'向上滑动\');
this.canPullToUp = false;
binding.value(el);
}
}
}
if (this.evType === \'tap\') {
let endTime = new Date().getTime();
if (Math.abs(changeX) < 5 && endTime - this.startTime < 300) {
binding.value.handler(binding.value.param);
} else {
return;
}
}
this.canPullToLeft = true;
this.canPullToRight = true;
this.canPullToUp = true;
this.canPullToDown = true;
this.el.addEventListener(\'touchstart\', null);
this.el.addEventListener(\'touchmove\', null);
this.el.addEventListener(\'touchend\', null);
}
init(type) {
let _this = this;
this.evType = type;
this.el.addEventListener(\'touchstart\', function(ev) {
_this.start(ev, _this.el, _this.binding, _this.vnode);
});
this.el.addEventListener(\'touchmove\', function(ev) {
_this.move(ev, _this.el, _this.binding, _this.vnode);
});
this.el.addEventListener(\'touchend\', function(ev) {
_this.end(ev, _this.el, _this.binding, _this.vnode);
});
}
}
/*
v-tap: vue移动端tap时间
eg: <div v-tap="{handler: fn, param: testParam}"></div>
参数: handler: 监听函数(function)
param: 监听函数的参数 (object)
stop: 是否阻止冒泡 (boolean)
prevent: 是否阻止默认行为 (booleam)
*/
Vue.directive(\'tap\', {
bind: function(el, binding, vnode) {
new VueSlide(el, binding, vnode).init(\'tap\');
},
});
Vue.directive(\'to-left\', {
bind: function(el, binding, vnode) {
new VueSlide(el, binding, vnode).init(\'toLeft\');
},
});
Vue.directive(\'to-right\', {
bind: function(el, binding, vnode) {
new VueSlide(el, binding, vnode).init(\'toRight\');
},
});
Vue.directive(\'to-up\', {
bind: function(el, binding, vnode) {
new VueSlide(el, binding, vnode).init(\'toUp\');
},
});
Vue.directive(\'to-down\', {
bind: function(el, binding, vnode) {
new VueSlide(el, binding, vnode).init(\'toDown\');
},
});
// 拖拽指令:执行函数中注入了三个参数
// changeX(Number) : 横轴的变化量
// changeY(Number) : 纵轴的变化量
// dir(String): 用户想要滑动的方向,只有在横纵轴的左右偏移方向在30deg内才会存在方向,其他角度范围内为空字符。
Vue.directive(\'drag\', {
bind: function(el, binding, vnode) {
new VueSlide(el, binding, vnode).init(\'drag\');
},
});
以上是 移动端Tap与滑屏实战技巧总结以及Vue混合开发自定义指令 的全部内容, 来源链接: utcz.com/z/374626.html