canvas 动画库 CreateJs 之 EaselJS(下篇)

本文来自网易云社区

作者:田亚楠


继承

对应原文:Inheritance

我们可以继承已有的「显示对象」,创建新的自定义类。实现方法有很多种,下面介绍其中之一。

举例:实现一个继承于 Container 类的自定义类 Button:

共分 4 步:

  1. 自定义构造器

  2. 继承父类,获得父类的功能

  3. 重写已有方法,扩展自身方法

  4. promote 继承来的方法,返回自定义类 

(function () {    // 自定义构造器
    //  - 调用继承的父类构造器()
    //  - label 为自定义入参
    var Button = function (label) {      this.Container_constructor();      this.label = label;
    };    // 继承 Container 类
    //  - 将 Container 添加到 Button 的调用链上,赋予 Button 所有 Container 的方法
    //  - 返回值为 Button.prototype
    var p = createjs.extend(Button, createjs.Container);    // 重写 draw 方法
    //  - 首先调用父类的 draw 方法
    p.draw = function () {      this.Container_draw();      // 添加自定义逻辑
    };    // 将父类 Container 的所有方法(包括 constructor)都重写添加到 Button
    //  - 例如上文用到的:Container_constructor、Container_draw
    //  - 新方法命名规则:prefix_methodName,prefix 为参数 "Container"
    window.Button = createjs.promote(Button, "Container");
  })();

createjs.promote 跟 createjs.extend 是不同的。上面代码的逻辑是:

  1. 首先通过 extend,赋予 Button 所有 Container 的方法(只是放在调用链 Button.prototype 上);

  2. 再通过 promote,将调用链上的方法 提升(promote) 到 Button 对象上,并修改了方法名,防止命名冲突,便于记忆。

原文中的 DEMO源码


命中测试

对应原文:HITTEST


hitTest & globalToLocal

显示对象拥有函数 hitTest ,用来检测一个点是否在该显示对象中间。我们可以通过它来检测指针(鼠标/手指点按)是否在一个图形中。

circle.hitTest(stage.mouseX, stage.mouseY);

上面的写法检测指针当前的位置(stage.mouseX, stage.mouseY)是否在 circle 圆形中,但存在一个问题。

无论 createjs.Stage 类,还是 createjs.Shape 类,都继承于 createjs.Container 类。 他们都是一个容器,都拥有 addChild 方法,因此可以互相嵌套。并且他们都位于「各自的坐标系」中。 其中 stage 的坐标系我们可以称为 global(世界坐标系),而其他的容器对象如 cirlce 的坐标系我们称为 local。

circle.hitTest 的参数当然需要基于 circle 的坐标系,而 stage.mouseX 获得的点的坐标系是基于 global 的。

因此我们需要通过 circle.globalToLocal(stage.mouseX, stage.mouseY)(返回值为:{x, y} 对象), 来将 global 坐标系的坐标转换为 circle 坐标系的坐标。

参考:DEMODEMO2

从 DEMO2 可以看出 globalToLocal 方法会将目标对象的父元素的「图形变换」的因素都考虑在内。 如果有多级元素嵌套,我们仍可以使用该方法将 global 坐标转换为内层子元素的 local 坐标。


localToLocal

除了检测指针相对于图形的位置,我们还可以检测一个显示对象相对于另一个显示对象的位置。

objA.localToLocal(posX, posY, objB) 方法,可以将 objA 对象的 local 坐标系中的点(posX, posY), 映射到 objB 对象的 local 坐标系中。返回值仍然为 {x:, y:}。

注意:(posX, posY)坐标是从 objA 映射到 objB,source.localToLocal(x, y, target)    

参考:DEMO


鼠标交互

对应原文:Mouse Interaction

基础

鼠标交互,就是监听鼠标/手指等的交互事件。「显示对象」通过使用 addEventListener 即可监听事件。比如 click 事件:

circle.addEventListener('click', function () { alert('circle clicked') });

可以监听的事件有:
click, dblclick,
mousedown, pressmove, pressup,
mouseover / mouseout, and rollover / rollout.

其中最后 4 个(mouseover / mouseout, rollover / rollout)是有一定关联的,它们默认不启用,使用的时候需要:

stage.enableMouseOver(frequency);

其中 frequency 是指在一秒内检查(计算)多少次事件是否触发。默认值为 20 次/秒。 设置的值越高,相应速度越快,但相应的需要更多的计算量。

这样做的好处是使 检查的频率 与 设置的帧率 解耦。

有几点需要注意:

  1. 没有 mouseup 和 pressdown 事件。可以把 mousedown、pressmove、pressup 分成一组。

  2. pressup 与 click 事件的区别是,click 事件在同一点按下与抬起时触发,而 pressup 会在任意一处拿起时都会触发。


  3. on 方法可以用来替代 addEventListener,并且 on 方法还额外提供了一些参数:


circle.on(type, listener, scope, once, data, useCapture);

监听事件的回调函数 listener 的参数是一个 EaselJS 定义的 MouseEvent 对象,它包含一些有用的属性:

  1. type:事件类型('mousedown'、'pressmove'、'pressup' 等)

  2. target:触发事件的显示对象

  3. nativeEvent:基于的原生事件对象

  4. stageX、stageY:触发事件的点在 global 坐标系的坐标

  5. 还有一些不常用的属性,可参考 API

参考:DEMO

通过上面的 DEMO,当多个事件同时触发时(更换绑定顺序结果不变):

  1. 先触发 click 再触发 pressup

  2. 先 rollover 再 mouseover

  3. 先 mouseout 再 rollout


对性能的好处

事件 mouseover / mouseout, rollover / rollout 也可以通过对检查频率的设置,来优化性能。 要知道小于 100ms 的响应时间用户是几乎不会感知到的,而它只需要 10fps,相对于 60fps 的动画来说性能提升了 6 倍。

而其他的事件 click, dblclick, mousedown, pressmove, pressup, 我们可以通过监听原生事件,在「对应的原生事件」触发的时候才调用回调,而不是放到 tick 循环中,因此可以提升性能。


事件冒泡

对于 DOM 节点来说,当一个事件被触发之后,会经过 3 个阶段:捕获阶段、目标阶段、冒泡阶段

参考文章:事件阶段MDN 文章中有一个很直观的demo

补充:当事件进行到目标阶段时,目标阶段上注册的捕获事件和冒泡事件的触发顺序是由注册顺序决定的(addEventListener 代码的执行顺序)

注册捕获事件需要使用 addEventListener(type, listener, useCapture) 的第三个参数 useCapture 设置为 true,

跟在 DOM 上绑定事件一样,createjs 也对事件的触发有着相似的处理方法。

由于是对虚拟的 js 对象(而非 DOM 结构)进行事件的绑定,因此它内部的处理方式是 createjs 仿照 DOM 的机制实现的一套逻辑。 跟 DOM 事件没有必然联系。其本质上都是 canvas 元素触发了事件之后,再由 createjs 进行处理。

createjs 中的 on 方法也有 useCapture 参数用来注册捕获事件:circle.on(type, listener, scope, once, data, useCapture)

我们先明确一下名词的定义(以 click 事件为例):

  1. target:触发事件的节点中最内层的节点。比如点击有多个节点重合(父节点子节点都有),那么最内层的子节点就是 target。

  2. currentTarget:事件流转到的当前节点。

createjs 中的对象/容器处理事件也经过 3 个同样的阶段:

  1. 捕获阶段:
    首先触发 stage 的捕获事件(stage 上绑定的 useCapture == true 的事件),然后依次触发 target 的最外层祖先容器到最内层父容器的捕获事件

  2. 目标阶段:
    target 对象触发自身的事件(包括所有捕获事件和冒泡事件)

  3. 冒泡阶段:
    与捕获阶段相反,依次触发 target 的最内层父容器到最外层祖先容器,直到 stage 对象的冒泡事件(useCapture == false 默认值)

这个 DEMO 中所有容器与显示对象都注册了 click 事件(包括 useCapture 值为 true 和 false 两种), 其中 button 对象是一个 Container 容器对象,它包含两个显示对象:background、label。你可以通过点击「红色背景」和「白色文字」来分别查看对应「事件阶段」结果。

另外还有两个属性用来控制捕获&冒泡:

  1. mouseChildren 可用来将一个显示对象集合作为一个事件整体,如上面的 DEMO 中,设置 button.mouseChildren = false; 那么 button 这个容器 所包含的所有子显示对象的事件将不会触发,整个 button 集合将作为整体对事件进行相应。

  2. mouseEnabled 顾名思义,可以用来禁止一个对象的所有事件。需要注意的是,如果 button 这个集合设置了 button.mouseEnabled = false; 那么它的所有子显示对象的事件将都不会再被触发了。


HITAREA

前文介绍了鼠标交互的各种事件,但可以被触发事件的只有显示对象的「可见 且 不透明 」的像素点。 在上面事件阶段的 DEMO 中,可以发现 label (按钮的文字)上注册的事件想要被触发, 必须精确的点击到「文字的线条」上。

createjs 提供了 hitArea 。你可以设置另一个对象 objB 作为显示对象 objA 的 hitArea,当点击到 objB 时就相当于点击到了 objA。 这个 objB 不需要添加到显示对象列表,也不需要可见,但它会在交互事件的触发中替代 objA。

注意:hitTest 命中检测并不适用于 hitArea,命中检测还是针对显示对象的「可见且不透明」的像素点(不然命中检测的逻辑就显得混乱了)。 hitArea 只针对交互事件的触发。如果真的有这种需求,可以非常简单的自己实现。

参考:DEMO

上面的 demo 中,container 对象为所有蓝色的圆形的整体,它的 hitArea 是红色的圆形, 当指针 mouseover 红色的圆形时,container 的 mouseover 事件会被触发。


stage 对象的交互事件

在 EaselJS 0.5 版本之前,stage 对象是无法绑定交互事件的,后来有人提了 ISSUES,在之后的版本中解决掉了这个问题。

一般的显示对象监听事件触发的范围为「可见且不透明」的像素点,而 stage 对象显然不同。

stage 对象有它特殊的交互事件:stagemousedown, stagemouseup, stagemousemove,整个 canvas 都对它们生效。

注:stage 对象还可以监听 click、mouseleave、mouseenter,后两者可以监听指针进入/离开 canvas。

当指针离开 canvas 范围之后 stagemousemove 事件就不会触发了,如果希望在画布之外继续触发事件,需要设置: stage.mouseMoveOutside = true; 。 之后当离开画布范围后,stage 的 3 个特殊事件都会继续被监听。

evt.stageX, evt.stageY 不会超出画布的边界范围(大于 0 小于 width/height),如果希望获取到外界的坐标,可以使用 evt.rawX, evt.rawY。

//超出画布之后仍允许监听 stage 的各种事件stage.mouseMoveOutside = true;

stage.on("stagemousemove", function(evt) {    //永远在边界以内
    console.log("stageX/Y: "+evt.stageX+","+evt.stageY);    //可以超出边界,小于 0 或 大于 canvas 的宽高(CW/CH)
    console.log("rawX/Y: "+evt.rawX+","+evt.rawY);
});

我们可以通过 stage.mouseInBounds (参考之前 hitTest 的这个 DEMO 有用到)来判断指针是否离开 canvas 范围。 或者监听 stage 的事件:mouseleave, mouseenter 来判断。

参考:DEMO


拖拽

通过对前面介绍过的一些事件的监听,我们可以非常方便的实现拖拽效果。主要是使用 mousedown, pressmove, pressup 这一组事件。

直接看 DEMO


其它常用 API

  1. Container.getObjectUnderPoint(x, y, mode) ,参考 API doc

  2. Container.getObjectsUnderPoint(x, y[, mode = 0]) ,参考 API doc

  3. DisplayObject.hitTest(x, y),本文前面的「命中测试」部分介绍过

  4. ...请阅读 API doc


相关阅读: canvas 动画库 CreateJs 之 EaselJS(上篇)

网易云免费体验馆,0成本体验20+款云产品! 

更多网易研发、产品、运营经验分享请访问网易云社区



相关文章:
【推荐】 Kubernetes在网易云中的落地优化实践

posted @ 2018-09-28 10:43  网易数帆  阅读(1325)  评论(0编辑  收藏  举报