最近在忙混合开发,因交互相对复杂,所以也踩了很多坑。在此做一下总结。

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混合开发自定义指令的更多相关文章

  1. (17/24) webpack实战技巧:生产环境和开发环境并行设置,实现来回切换

    1. 概述 生产环境和开发环境所需依赖是不同: --开发依赖:就是开发中用到而发布时用不到的.在package.json里面对应的就是devDependencies下面相关配置. --生产依赖: 就是 ...

  2. 极客时间-vue开发实战学习(ant-design vue作者)

    vue基础 属性 事件 插槽 指令(Directives) 生命周期 底层原理 vue生态 路由管理器vue Router 状态管理器vuex 同构Nuxt vue实战 实战项目-ant-desing ...

  3. H5案例分享:移动端touch事件判断滑屏手势的方向

    移动端touch事件判断滑屏手势的方向 方法一 当开始一个touchstart事件的时候,获取此刻手指的横坐标startX和纵坐标startY: 当触发touchmove事件时,在获取此时手指的横坐标 ...

  4. H5案例分享:移动端滑屏 touch事件

    移动端滑屏 touch事件 移动端触屏滑动的效果的效果在电子设备上已经被应用的越来越广泛,类似于PC端的图片轮播,但是在移动设备上,要实现这种轮播的效果,就需要用到核心的touch事件.处理touch ...

  5. H5-移动端实现滑屏翻页-原生js/jquery

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. 移动端H5混合开发,Touch触控,拖拽,长按, 滑屏 实现方案

    概述 近期由于产品快速原型开发的需要,不想用原声的方式开发App两端一起搞时间来不及,目前产品处于大量上feature的阶段,采用混合开发是最合适的选择,所以花了3天的时间研究怎么去实现移动端,拖拽, ...

  7. 利用轮播原理结合hammer.js实现简洁的滑屏功能

    最近有个任务,做一个非常小的h5的应用,只有2屏,需要做横向的全屏滑动切换和一些简单的动画效果,之前做这种东西用的是fullpage.js和jquery,性能不是很好,于是就想自己动手弄一个简单的东西 ...

  8. 滑屏 H5 开发实践九问

    滑屏的交互形式自从在 H5 中流行起来,便广泛应用在产品宣传.广告.招聘和活动运营等场景中,作为微信朋友圈广告惯用的形式,其影响力更是得到了强化与放大.如今滑屏H5可谓玲琅满目,数不尽数. 作为一个 ...

  9. mobile_竖向滑屏

    竖向滑屏 元素最终事件 = 元素初始位置 + 手指滑动距离 移动端,"手指按下","手指移动" 两个事件即可(且不需要嵌套),有需要时才使用 "手指离 ...

随机推荐

  1. swift计算 switch case

    var year = var month = var day = ; let daysOfFeb = year % == && year% != || year % == ?: var ...

  2. (原)tensorflow中使用指定的GPU及GPU显存

    转载请注明出处: http://www.cnblogs.com/darkknightzh/p/6591923.html 参考网址: http://stackoverflow.com/questions ...

  3. .NET中制做对象的副本(三)通过序列化和反序列化为复杂对象制作副本

    1.类的定义 /// <summary> /// 学生信息 /// </summary> [Serializable] public class Stu { /// <s ...

  4. Linux内存管理2---段机制

    1.前言 本文所述关于内存管理的系列文章主要是对陈莉君老师所讲述的内存管理知识讲座的整理. 本讲座主要分三个主题展开对内存管理进行讲解:内存管理的硬件基础.虚拟地址空间的管理.物理地址空间的管理. 本 ...

  5. mysql系列九、mysql语句执行过程及运行原理(分组查询和关联查询原理)

    一.背景介绍 了解一个sql语句的执行过程,了解一部分都做了什么,更有利于对sql进行优化,因为你知道它的每一个连接.where.分组.子查询是怎么运行的,都干了什么,才会知道怎么写是不合理的. 大致 ...

  6. windows命令行中英文切换

    Windows下cmd命令提示符窗口的语言设置(中英) 打开cmd命令提示窗口 输入 chcp 936 使用ping 命令 显示中文 2 同样 输入chcp 437 3 使用ping 命令

  7. 深入解析内存原理:RAM的基本原理

    1. 寻址原理概述RAM 主要的作用就是存储代码和数据供CPU 在需要的时候调用.但是这些数据并不是像用袋子盛米那么简单,更像是图书馆中用有格子的书架存放书籍一样,不但要放进去还要能够在需要的时候准确 ...

  8. 基于 OpenSSL 的 CA 建立及证书签发

    http://rhythm-zju.blog.163.com/blog/static/310042008015115718637/ 建立 CA 建立 CA 目录结构 按照 OpenSSL 的默认配置建 ...

  9. Zabbix Agent active批量调整客户端为主动模式监控

    Zabbix Agent active批量调整客户端为主动模式监控 zabbix_server端当主机数量过多的时候,由Server端去收集数据,Zabbix会出现严重的性能问题,主要表现如下: 1. ...

  10. centos6.5生产环境编译安装nginx-1.11.3并增加第三方模块ngx_cache_purge、nginx_upstream_check、ngx_devel_kit、lua-nginx

    1.安装依赖包 yum install -y gcc gcc-c++ pcre-devel openssl-devel geoip-devel 2.下载需要的安装包 LuaJIT-2.0.4.zip ...