移动端Tap与滑屏实战技巧总结以及Vue混合开发自定义指令

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

回到顶部