移动开发框架剖析

一直在写jQuery的源码教程,都没时间研究别的框架了。Hammer是我项目御用的一个手势库,早期1.x版本的swipe事件的响应不灵敏的问题而改过源码,2.x就已经更正过来,而且源码的结构也整个翻新了一遍,不管从逻辑还是组织结构上,我个人都觉得有必要深入,所以就当作一个系列教程一起学习吧。

本章主要讲解下使用,因为官方的API都是英文的,中文资料也相对较少,源码的分析后续再更新。

通过网方的教程还是有很多地方不是很明白的,可能需要后期看源码才能弄懂了。

Hammer.js是一个专门用于控制、定制手势的JavaScript库。它可以识别出常见的触摸、拖动、长按、缩放等等,对于希望在网页上对手势有所处理的朋友们,应该很有帮助。

官方也表明了2.0版本是完全重写,包括手势识别器、和改进支持最近的移动浏览器利用touch-action css属性。同时还支持多种设备,所以多用户成为可能。功能上更加强大了

使用上很简单,引入源码并且创建一个实例。

 


Hammer
var hammertime = new Hammer(myElement, myOptions);
hammertime.on('pan', function(ev) {
    console.log(ev);
});

默认设置下自动添加了,tap、doubletap、press,pan与swipe的横向滑动,多点触摸pinch与rotate手势。

pinch和rotate识别器在默认情况下都是禁用的,因为他们会有元素阻塞,但是我们可以手动开启:

hammertime.get('pinch').set({ enable: true });
hammertime.get('rotate').set({ enable: true });

当然,我们还可以为pan与swipe 开启纵向滑动

hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL });
hammertime.get('swipe').set({ direction: Hammer.DIRECTION_VERTICAL

我们能通过meta的标记,禁用doubletap /触控放大。但是新的浏览器支持touch-action属性所以可以不需要这个

<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">

 


1.基本的实现

绑定一个简单的swipe事件,通过回调得到事件的响应

var mc = new Hammer(el);

mc.on('swipe',function(evt){
    console.log(evt)
})

 

2.改变事件处理的方向

但是默认情况下Hammer是屏蔽了上下滑动的响应的,所以我们如果只指竖向响应,就需要再配置中设置

复制代码
mc.get('pan').set({
    direction: Hammer.DIRECTION_VERTICAL
});

mc.on('swipe',function(evt){
    console.log(evt)
})
复制代码

我们还可以同时响应横向与竖向,除此之外,还可以单独为某个指定的识别器配置

复制代码
var mc = new Hammer(el);

mc.get('swipe').set({
    direction: Hammer.DIRECTION_ALL
});

mc.on('swipe pan',function(evt){
    console.log(evt)
})
复制代码

通过get方法我们可以得到指定对应的识别器,我们这里只给swipe启动了上下左右滑动的响应,那么pan事件则没开启,这种指定特定事件的处理相当的灵活

当然以上都是比较简单常见的事件处理,如果在一个元素上绑定多个不同的事件处理,那可以引入Hammer.Manager控制了

 

3.Hammer.Manager

我们可以通过这个Manager设置自己的识别器的实例。可以设置更多的被识别的手势。

一个复杂的多事件处理的Example https://cdn.rawgit.com/hammerjs/hammer.js/master/tests/manual/visual.html

复制代码
var mc = new Hammer.Manager(myElement, myOptions);

mc.add( new Hammer.Pan({ direction: Hammer.DIRECTION_ALL, threshold: 0 }) );
mc.add( new Hammer.Tap({ event: 'quadrupletap', taps: 4 }) );

mc.on("pan", handlePan);
mc.on("quadrupletap", handleTaps);
复制代码

上面的示例创建一个实例包含一个pan和一个quadrupletap手势

当然如果我们同时给一个元素上 绑定多个事件用new Hammer(el);直接通过on方法也是可以实现的

但是实际上的测试效果,识别度与灵活度比Hammer.Manager低很多。

因为Manager控制里面,引入了recognizeWith与requireFailure用来关联2个相近的事件,从而提高可用性

var pinch = new Hammer.Pinch();
var rotate = new Hammer.Rotation();
pinch.recognizeWith(rotate);

当然具体内部如何实现,要等以后源码分解才知道了。官方给的说明确实太少了

最后官方还提到提供一个神秘hammer.input事件,在每一次有用户交互的时候都会被触发,可以得到非常有用的数据

hammertime.on("hammer.input", function(ev) {
   console.log(ev.pointers);
});

 

除此之外,还有很多参数的

比如事件对象,Directions方向,输入动作Input Events,识别器状态等等

 

提供的方法Utils

类似addEventListener的事件绑定与销毁

Hammer.on(window, "load resize scroll", function(ev) {
    console.log(ev.type);
});

遍历

Hammer.each([10,20,30,40], function(item, index, src) { });
合并
复制代码
var options = {
    b: false
};

var defaults = {
    a: true,
    b: true,
    c: [1,2,3]
};
Hammer.merge(options, defaults);

// options.a == true
// options.b == false
// options.c == [1,2,3]
复制代码
extend,inherit,bindFn等等

hammerjs确实很强大,做了市面上大多数的设备的适配,我可以看到源码中关于适配的代码就接近一半,整个源码的结构其实也是比较规整的

大多数人能用1,2步的处理就已经完全足够了,但是这都不是重点,重点是我们要的不是使用,而且能够“造轮子”弄懂原理!


浏览器底层并没有给元素提供类似,单击,双击,滑动,拖动这些直接可以用的控制接口,一切的手势动作都只能通过模拟出来。移动端浏览器唯一给我们提供的就只是mousedown -> mousemove -> mouseup三种最基本的事件接口。那么我们只能通过这些简单的接口模拟出复杂的手势出来。

常规的做法流程:

1.给元素上绑定三个事件,mousedown ,mousemove,mouseup

2.在交互的时候,用户只触发mousedown,mouseup没有触发mousemove,就可以单算是一次点击的动作, 这里可以是 单击,双击与长按,具体可以通过间隔的时间判断

3.如果mousemove触发了,自然就是滑动与拖动了

当然手势还要涉及到多点操作缩放与旋转的处理,就之后在讨论。

 


先抛开流程,我们要知道设计一个手势库需要考虑的问题,至少有2点:

1.运行的平台

2.用户的手势

 

那么我们可以总结市面上的终端设备有那么三种:

1 手机/pad移动端

2 pc类

3 还有种带触摸屏的电脑一类

 

用户的手势行为大体分:

单击tap , 双击doubletap,平移pan,滑动swipe,长按press,缩放pinch,旋转rotate

 


从设计的角度来讲,有着不同的兼容与选择问题,却又有着一些相同的共性与处理我们要如何去组织结构?

当然依旧是OOP设计了,抽出父类,实现继承,引入策略模式

我们看看Hammer在结构上是如何实现这类设计的

常规来说手势的处理,要分为初始化与执行期。初始化的时候构建所有相关的参与与方法

hammer源码里面分几大块:

1. Hammer类,一个简单的工厂方法,用来创建一个管理和初始化默认的识别器。Hammer.defaults配置一些基本的选项

包括针对每种识别器的配置与元素CSS属性的设置

2. Manager类,整个库的管理类。内部初始化了input输入对象,所有手势对象,元素css设置对象touchAction

3. InputHandler类,事件回调的具体加工类,用来生成包装后的事件对象与派发事件到每一个识别器

4. TouchAction类,设置元素的touchAction属性

5. Input类,事件处理类。用来处理绑定与销毁,事件句柄的回调。每一个输入类都需要继承

6. Recognizer类,所有识别器需要继承的基类

以上就是整个库的类块了,当然5与6都是属于基类继承的,在代码运行的时候就自动构建完毕了

 


关于继承inherit方法

hammer用的是传统的类似继承

复制代码
function inherit(child, base, properties) {
    var baseP = base.prototype,
        childP;

    childP = child.prototype = Object.create(baseP);
    childP.constructor = child;
    childP._super = baseP;

    if (properties) {
        extend(childP, properties);
    }
}
复制代码

只继承了原型的方法,因为原型都是共享的,如果放置属性可以被任何一个继承的子类所有修改,所以属性的继承需要用call方法

继承的子类有一个私有属性 _super指向父类,同时还能额外的扩展方法

inherit(MouseInput, Input, {
     handler: function MEhandler(ev){
    }
}

子类MouseInput继承父类Input类的所有原型方法,并扩展了handler方法

 


输入设备初始化继承:就是通过什么设备触发动作(PC,手机,ipad等等)

输入设备hammer分为

MouseInput,PointerEventInput,SingleTouchInput,TouchInput,TouchMouseInput

我们看看最简单的桌面PC的鼠标输入处理MouseInput,其余的结构基本类似。

复制代码
function MouseInput() {
    this.evEl = MOUSE_ELEMENT_EVENTS;
    this.evWin = MOUSE_WINDOW_EVENTS;
    //用来禁止TouchMouse事件
    this.allow = true; // used by Input.TouchMouse to disable mouse events
    //鼠标按下的状态
    this.pressed = false; // mousedown state
    Input.apply(this, arguments);
}

inherit(MouseInput, Input, {
     handler: function MEhandler(ev){
       ..//
    }
}
复制代码

MouseInput初始化了几个必要的判断属性,然后就只handler方法, 此外还集成了Input输入类

比如我们调用

new MouseInput(callback)的时候,通过Input.apply(this, arguments)去初始化了基类input类,然后基类内部的init绑定了事件,并且把事件的回调,

this.domHandler指向了外部的callback回调,其实也就是handler方法了

addEventListeners(this.element, this.evEl, this.domHandler);

 

另一个基类就是Recognizer

因为我们把用户的行为分为单击tap , 双击doubletap,平移pan,滑动swipe,长按press,缩放pinch,旋转rotate,那么类似相同点我们也必须抽象成一个基类

Recognizer比如复杂,留在执行期的时候讲解。

 


Hammer 使用:

var mc = new Hammer(el);

那么内部的构建

复制代码
function Hammer(element, options) {
    options = options || {};
    //配置手势识别器参数
    options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
    return new Manager(element, options);
}
复制代码

可见最终是Manager构建的对象实例了

Manager内部,通过createInputInstance创建一个输入环境的实例对象,创建一个输入环境的实例对象

this.input = createInputInstance(this);

createInputInstance的作用主要是用来选择当然的平台,不同的平台会调用不同的手势输入处理,这里就有策略选择了的处理了

复制代码
function createInputInstance(manager) {
    var Type;
    var inputClass = manager.options.inputClass;

    if (inputClass) {
        Type = inputClass;
    } else if (SUPPORT_POINTER_EVENTS) {
        Type = PointerEventInput;
    } else if (SUPPORT_ONLY_TOUCH) {
        Type = TouchInput; //移动手机端
    } else if (!SUPPORT_TOUCH) {
        Type = MouseInput; //桌面
    } else {
        Type = TouchMouseInput;
    }
}
复制代码

如果是桌面PC端,我们就会走MouseInput

return new MouseInput(manager, inputHandler);

这样把具体的通过Input类绑定的回调放到MouseInput的handler处理了,最终的回调会进入总处理inputHandler类

inputHandler类就会遍历所有的手势识别器把输入的input传入

manager.recognize(input);

每一个识别器各自处理其行为了,当然这里面倒是如何触发,手势识别器如何判断是那种手势,就放一章了。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值