alloyfinger是一款非常轻量的开源手势库,由于其轻量、基于原生js等特性被广泛使用。关于其原理,它的官方团队解析的非常详细——传送门。相信学过高数的人看起来应该不难,这里不深入解析了。
其核心代码只有300多行,完成了14个手势,其手势并不是浏览器原生的事件,而是通过监听touchstart、touchmove、touchend、touchcancel四个原生浏览器事件hack出来的手势,故其用法与原生可能有些不同。比如阻止默认事件、阻止冒泡,不能像原生事件那样用。
官方代码除了alloyfinger的核心库外还有react、vue的实现。在这里只对核心库即vue版本的解析。
核心库:
/* AlloyFinger v0.1.7
* By dntzhang
* Github: https://github.com/AlloyTeam/AlloyFinger
* Note By keenjaan
* Github: https://github.com/keenjaan
*/
; (function () {
// 计算距离和角度等的数学公式
// 根据两边的长度求直角三角形斜边长度(主要用于求两点距离)
function getLen(v) {
return Math.sqrt(v.x * v.x + v.y * v.y);
}
// 主要用于计算两次手势状态间的夹角的辅助函数
function dot(v1, v2) {
return v1.x * v2.x + v1.y * v2.y;
}
// 计算两次手势状态间的夹角
function getAngle(v1, v2) {
var mr = getLen(v1) * getLen(v2);
if (mr === 0) return 0;
var r = dot(v1, v2) / mr;
if (r > 1) r = 1;
return Math.acos(r);
}
// 计算夹角的旋转方向,(逆时针大于0,顺时针小于0)
function cross(v1, v2) {
return v1.x * v2.y - v2.x * v1.y;
}
// 将角度转换为弧度,并且绝对值
function getRotateAngle(v1, v2) {
var angle = getAngle(v1, v2);
if (cross(v1, v2) > 0) {
angle *= -1;
}
return angle * 180 / Math.PI;
}
// 用于处理手势监听函数的构造函数
var HandlerAdmin = function(el) {
this.handlers = []; // 监听函数列表
this.el = el; // 监听元素
};
// 构造函数的添加监听函数的方法
HandlerAdmin.prototype.add = function(handler) {
this.handlers.push(handler);
}
// 构造函数的删除监听函数的方法
HandlerAdmin.prototype.del = function(handler) {
if(!handler) this.handlers = []; // handler为假值时,代表清空监听函数列表
for(var i=this.handlers.length; i>=0; i--) {
if(this.handlers[i] === handler) {
this.handlers.splice(i, 1);
}
}
}
// 触发用户事件监听回调函数
HandlerAdmin.prototype.dispatch = function() {
for(var i=0,len=this.handlers.length; i<len; i++) {
var handler = this.handlers[i];
if(typeof handler === 'function') handler.apply(this.el, arguments);
}
}
// 实例化处理监听函数的对象
function wrapFunc(el, handler) {
var handlerAdmin = new HandlerAdmin(el);
handlerAdmin.add(handler); // 添加监听函数
return handlerAdmin; // 返回实例
}
// 手势的构造函数
var AlloyFinger = function (el, option) {
this.element = typeof el == 'string' ? document.querySelector(el) : el; // 绑定事件的元素
// 绑定原型上start, move, end, cancel函数的this对象为 AlloyFinger实例
this.start = this.start.bind(this);
this.move = this.move.bind(this);
this.end = this.end.bind(this);
this.cancel = this.cancel.bind(this);
// 绑定原生的 touchstart, touchmove, touchend, touchcancel事件。
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);
// 保存当有两个手指以上时,两个手指间横纵坐标的差值,用于计算两点距离
this.preV = { x: null, y: null };
this.pinchStartLen = null; // 两个手指间的距离
this.zoom = 1; // 初始缩放比例
this.isDoubleTap = false; // 是否双击
var noop = function () { }; // 空函数,没有绑定事件时,传入的函数
// 对14种手势,分别实例化监听函数对象,根据option的值添加相关监听函数,没有就添加空函数。
this.rotate = wrapFunc(this.element, option.rotate || noop);
this.touchStart = wrapFunc(this.element, option.touchStart || noop);
this.multipointStart = wrapFunc(this.element, option.multipointStart || noop);
this.multipointEnd = wrapFunc(this.element, option.multipointEnd || noop);
this.pinch = wrapFunc(