canvas 动画库 CreateJs 之 EaselJS(上篇)

本文来自网易云社区

作者:田亚楠


须知

本文主要是根据 createjs 中的 EaselJS 在 github 上的 tutorials 目录下的文章整理而来 (原文链接),同时也包含了很多本人的理解,如过有叙述不当的地方,请联系我 :-D 

本文对原文中的一些知识点的解释进行了删减,对另外一些进行了扩展,同时对文中的 demo 进行了改写,如果感到阅读困难的话请参考原文(本文中有对应的原文链接)

如果文中提供的 demo 无法打开,可以参考原文中的 demo。


从一个简单例子开始

对应原文:Getting Started

EaselJS 类的结构:

201809280956116a5e26b7-3edd-4a50-9d5e-c989f5aacdb8.png


我们把上图所有的类都称作「元件」

上图列出了 createjs 的主要类结构,如图可以得到如下信息:

  1. 所有元件的基类都是 DisplayObject,所有元件都是一个构造函数(类)

  2. Container 可以包含其他(任何)元件。由于它本身也是一个元件,所以不同的 Container 之间可以相互包含

  3. 舞台 Stage 是一个特殊的 Container ,也是一个 DisplayObject,它内部封装了 canvas 对象

  4. Sprite 元件用来表现帧动画(类似 gif)

  5. Bitmap 元件用来表现纯静态的图片

  6. Shape 元件用来表现矢量图形,它的实例包含一个 Graphics 元件,用来描述图形

  7. Filter 和 Shadow 则是滤镜分支,可以针对任意元件实现颜色变换、模糊、阴影等效果。 使用滤镜的方式跟 Flash 一致,需要新建 Filter 实例,添加到目标元件的 FilterList 中,Createjs 框架在下一帧就会把该元件加上滤镜效果

  8. 另外图中没有画的元件还有:Text 元件用来表现文本、DOMElement 元件用来控制 HTML DOM 元素


在一个应用中,各个元件类的实例之间的关系类似下面这张图:

20180928095645b09123c5-96c9-4315-baaf-df03e9899af8.png



每个类都有各自的静态方法,如:createjs.Ticker.setFPS(20) ,同时也可以用来初始化一个对象,如:new createjs.Stage('myCanvas')


参考:

  1. 博客园 kenkofox 的文章

  2. API doc


Container 容器

如果不太好理解,可以先大概看一看,对阅读后面的部分没有影响

首先所有可以绘制到画布上的对象(图形、图片等)都称作「显示对象」,而「容器」可以容纳一个显示对象列表。

例如你可以将头、四肢、躯干 3 个显示对象容纳到一个 person 容器中,通过:

//var head = .., body = .., arm = ..//实例化一个容器对象var person = new createjs.Container();//将显示对象放入容器中person.addChild(head, body, arm);

这样你可以将 person 对象作为一个整体进行图形变换,也可以单独的将每一部分进行图形变换。即人可以整体向前移动,而他的头部也可以同时左右摆动。


Stage 舞台

舞台(Stage)对象指向我们的 canvas 元素 ,它是一个特殊的 Container 对象,它包含了所有我们希望绘制到 canvas 上的图形/图片,即 任何一个希望展示在画布上的图形/图片都必须包含在 Stage 对象中。

例如一个图形 s1 希望绘制到 canvas 上,则必须先 stage.addChild(s1

Stage 对象的实例化过程入下:

<canvas id=canvasId width=400 height=400></canvas>//... balabala ...//参数为 canvas id 或者 canvas DOMElementvar stage = new createjs.Stage('canvasId');


Shape

shape 对象帮助我们在 canvas 上绘制矢量图形(vector graphics)

注:canvas 绘制的图形都是位图,createjs 应该是通过计算绘制出类似矢量图的效果

shape 对象有一个 graphics 属性,它指向一个 Graphics 实例,它拥有所有有关定义矢量图形的方法。

所有「显示对象」(可以在画布上看到的对象,类似 shape 矢量图形),都拥有统一的 位置 和 图形变换 属性,如 x y(位置属性)、rotation(旋转属性)、scaleX scaleY(缩放属性)等。

绘制一个红色的圆形,如下:

var circle = new createjs.Shape();//红色的半径为50的圆形,它位于(0, 0)点坐标// - 注意:(0, 0)点是相对于 circle 对象来说的,是相对坐标circle.graphics.beginFill("red").drawCircle(0, 0, 50);//所有「显示对象」通用的「位置」属性circle.x = 100;
circle.y = 100;//只有添加到 stage 舞台之后,才可以显示stage.addChild(circle);//返回值为 circle 本身

以上代码也可以简写如下:

stage.addChild(new createjs.Shape()).set({x: 100, y: 100}).graphics.f('red').dc(0, 0, 50);


update

虽然我们已经将 circle 矢量图添加到 stage 的显示列表中了,但只有我们在 update 之后才会绘制到 canvas 上。

stage.update();

update 函数会先将画布清空(通过 clearRect ),然后执行 stage 中所有 children 的绘制方法,最终将他们绘制在画布上。

最终的 DEMO


动画 & Ticker

对应原文:Animation and Ticker


动画基础

canvas 2d 的原生基本动画实现请参考另一篇文章的第一部分:canvas 学习总结

除了直接使用 setTimeout、requestAnimationFrame 等方法,以某个固定间隔循环以实现动画效果之外,createjs 还提供了 Ticker 类用来更方便实现动画效果。

使用 Ticker ,你可以暂停,或修改循环频率。还可以有选择的使用 setTimeout 或 requestAnimationFrame。

Ticker 是 createjs 上的静态接口,不需要 new instance。

默认帧率为 20FPS。有 3 种修改方法:

//方法一:createjs.Ticker.setFPS(40);//方法二:createjs.Ticker.framerate = 40;//方法三:单位 ms,(修改的是时间间隔 regular interval)// - 25ms 与前两个方法等价,因为:1000ms / 25ms = 40fpscreatejs.Ticker.interval = 25;

基本使用:

// on 也可以改成 addEventListener。用来监听 tick 事件(最终绑定在 window 对象上)createjs.Ticker.on('tick', function(event){circle.x += 5;stage.update(event);
});

参考:DEMO;


基于时间的动画(而非刷新频率)

问题

请耐心看完 :z

 

使用 requestAnimationFrame 的动画都存在一个问题,当运行在性能较差的设备上的时候,浏览器的刷新频率会降低。

如果动画的位移是根据频率计算的。也就是说:我们是根据频率(假设为 60fps)计算的每一次循环的时间间隔,假设为 1000 / 60 = 16.667 毫秒,即一次循环时间间隔为 16.667 毫秒, 然后可以通过这个时间得出对应的位移量:

假设我们希望小球 1秒 位移 500px,那么 16.667ms 需要位移 500/60 = 8.333px,由此计算出位移量为 8.333px。

那么当浏览器的刷新频率降低,我们预期的 60fps 没有达到,可能变为 40fps ,那么每次循环时间间隔变为 1000 / 40 = 25ms, 因为我们设置的每次循环的位移量还是 8.333px,那么 1s 的位移量就变为了 333.333px,其结果就是在看起来小球的移动速度降低了。

另外,在代码中动态修改设置的 FPS 值的时候,所有的位移值都需要重新计算,也会遇到上述问题。


解决

Ticker 可以将动画与帧率解耦,通过保证动画的执行是基于时间(而非频率), 来实现动态的改变FPS(无论是因为设备性能差,还是代码中进行了修改)时不会影响动画的速率。

也就是说,「位移量」取决于时间,而不是变来变去的帧率。当帧率下降的时候,每次循环中的位移量会相应变大。

它的实现方式很简单:
在 tick 事件的回调函数的中增加参数 delta。 delta 的值是上一次触发 tick 事件到现在(两次 tick 事件之间)的时间间隔。如果代码运行在一个性能很差的设备上时,delta 的值将明显比预期的 1000/FPS 大。 我们可以根据 delta 来计算当前 tick 的位移。

var fps = 60;
createjs.Ticker.setFPS(fps);createjs.Ticker.on('tick', function (evt) {    // 为方便计算,循环体中的所有位移,均认为是 1s 中的位移,只要乘以 delta 参数,即可得到真正的位移//  - 「真实时间」相对于 1000ms 「缩放」的倍数evt.delta = evt.delta / 1000;    // 500 表示位移速度为:500px/s,所有的位移均乘以 delta 缩放倍数,换算成「真实时间」的位移circle.x += 500 * evt.delta;    //...其他代码stage.update(evt);
});

参考:DEMO,在例子中,切换帧率不影响小球的运动速率

注意:必须在 update 的时候将 evt 作为参数传入 stage.update(evt);,才能正确的获取 evt.delta 值

另外 Ticker 还会暴露一个 getTime 方法,可以获取到从 Ticker 初始化(监听 tick 事件开始)到现在的总时间。

createjs.Ticker.getTime();

同时,使用精灵图实现 gif 图片效果的时候,也可以使用基于时间的动画。涉及API:Sprite、SpriteSheet等。


TIMING MODE

较新的浏览器都开始支持 requestAnimationFrame, 它能带来很多好处,包括动画的平滑运行、减少 CPU 和电量的消耗等。 然而 createjs 默认不启用它,而是使用 setTimeout。

我们可以通过 Ticker.timingMode 来启用 requestAnimationFrame。

它的默认值为 Ticker.TIMEOUT 从而使用的 setTimeout ,它兼容所有浏览器,并提供了可预期的、灵活的帧率(见上一小节), 然而抛弃了 requestAnimationFrame 的各种好处。不过你可以通过减少帧率来降低 CPU/GPU 的消耗。

有两种模式可以启用 requestAnimationFrame,它们都会在浏览器不支持 requestAnimationFrame 的时候,自动回退到 setTimeout:

  1. Ticker.RAF 模式会单纯的使用 RAF ,忽略掉设置的帧速率等值(3种设置帧率的方式全部失效)。
    因为 RAF 的频率是不确定的(由浏览器和当前运行环境决定)。如果使用了这种模式,建议一定要使用前面提到的「基于时间的动画」来确保动画速率的一致。


Ticker.RAF_SYNCHED 模式试图协调 RAF 和你设置的帧率,这种方式结合了 setTimeout 和 RAF 的优点, 但会在帧周期中造成极大的差异(无法理解原文的含义),因此这种模式在帧率为 60 的因子时运行的最好,如:10、12、15、30、60。


createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED;
createjs.Ticker.framerate = 30;


暂停

所有的 Ticker 都是可以暂停的,如果设置

Ticker.paused = true;//或者createjs.Ticker.setPaused(true);

那么 createjs.Ticker.getPaused() 的返回值为 true,在循环 tick 中,我们可以通过这个返回值判断需要跳过执行的代码:

function tick (evt){    if(!createjs.Ticker.getPaused()){        //...这段代码当 paused 的时候不会执行}stage.update(evt);
}

另外,createjs.Ticker.getTime(true) 返回除去暂停的时间的真正运行的总时长。

参考:DEMO


Tick

当调用 stage.update(event) 方法的时候,每一个 stage 的 children 都会触发它本身暴露的 tick 事件。

通过这些 children 的 tick 事件,我们可以灵活的处理每一个显示对象,且上下文为它本身:

//原来的写法createjs.Ticker.on('tick', function (evt) {circle.x += 5;stage.update(evt);
});//现在可以写成createjs.Ticker.on('tick', function (evt) {stage.update(evt); //会触发 circle 的 'tick' 事件});
circle.on('tick', function(){ this.x += 5 }); //上下文为 circle


性能

注意高的帧率不一定意味着更好的流畅度,较低的帧率会减少 CPU/GPU 的使用,并提供始终如一的体验。 请为你的项目选择一个适合的帧率。

可以通过 createjs.Ticker.getMeasuredFPS() 获取到过去一秒钟的平均帧率。
或者通过 createjs.Ticker.getMeasuredTickTime() 获取到过去一秒钟,每一次 tick 循环的平均时间

如果 getMeasuredFPS 接近设定的 FPS 值,说明性能良好。

getMeasuredTickTime 的值表示一次循环中,执行动画函数消耗的时间, 比 1000 / fps 越小,说明在一次循环中有更多的时间冗余,剩余更充足的时间提供给浏览器进行渲染等其他工作。

参考:DEMO


TWEENJS

通过缓动函数创建动画或动画序列。

createjs.Tween.get(txt).to({x:300}, 1000).to({x:0}, 0).call(onAnimationCompleteFn);


文本

有关字体的一点知识

如果浏览器无法再本机中找到 css 要求的字体,那么会自动匹配类似的字体(主要是英文),如下:

serif,衬线体,在字的主要线条后面会有一个小尾巴,常见:Georgia、Times、Times New Roman

sans-serif,无衬线体,没有小尾巴更清爽,常见:Arial、Verdana、Helvetica

monospace,等宽字体,顾名思义(反义:non-monospace),常见:Courier、Courier New

cursive,手写体,例如:Comic Sans、Monotype Corsiva

fantasy,装饰用的字体,常用来表示标题等少量文本,不适宜文章主体使用,例如:Impact、Haettenschweiler


展示文本

var text = new createjs.Text("Hello World", "bold 86px Arial", "#ff7700");

或者:

var text = new createjs.Text();
text.text = "Hello World!";
text.font = "bold 96px Dorsa";
text.color = "#000000";

其他的参数还有:(与 canvas 2d 中 context 关于文本的属性含义相同)

text.lineHeight = 15;
text.textAlign = 'center';
text.textBaseline = 'top';

初始化的第二个参数,跟 css 中 font 的格式完全一致。

另外如果要显示在画布上,不要忘记写:

stage.addChild(text);
stage.update();

还是上一个DEMO


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

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


   


相关文章:
【推荐】 责任链模式的使用-NettyChannelPipeline和MinaIoFilterChain分析
【推荐】 防不胜防这些游戏被外挂活生生地毁了
【推荐】 Kylin性能调优记——业务技术两手抓

转载于:https://www.cnblogs.com/163yun/p/9717314.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/426724.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

细说fgetc

fgetc int fgetc(FILE *stream) 注意到参数类型FILE *&#xff0c;因为这个函数是我们在对文件进行读写操作时常用到的&#xff0c;文件流&#xff08;即我们所定义的指向文件的指针&#xff09;。同时还要注意到函数的返回类型int,参考了其他博主一些文章后总结出来&#xf…

实例65:python

#题目&#xff1a;有n个人围成一圈&#xff0c;顺序排号。从第一个人开始报数&#xff08;从1到3报数&#xff09;&#xff0c; #凡报到3的人退出圈子&#xff0c;问最后留下的是原来第几号的那位。 coding:utf-8 nint(input(“输入人数:”)) List[] for i in range(1,n1): L…

算术类型转换、整型提升

分享一个很有意思的小tip 有人在编写代码时运行出了一个让人摸不着头脑的结果: -20>0U 怎么会是真值呢&#xff1f;&#xff1f; 这位朋友还特意检验了一下0U的值&#xff0c;当然是0没错。可是出现这样的结果到底是为什么呢&#xff1f; 这就涉及到c语言中的算术类型转换…

Mysql解压版配置

mysql安装包可到官网下载&#xff0c;地址&#xff1a;https://dev.mysql.com/downloads/mysql 1、首先解压文件包&#xff0c;我这解压到E:\install_work\mysql目录下&#xff1a; 2、发现mysql根目录下没有data目录和my.ini文件&#xff0c;不要紧&#xff0c;初始化mysql的时…

第二次作业重交

一、项目简介 1、Gitee项目地址&#xff1a;https://gitee.com/xnsy/WC 2、开发语言&#xff1a;C#语言 3、解题思路 刚看完作业要求后&#xff0c;只知道这个程序要完成对文件的统计工作&#xff0c;但是对于程序设计仍然是一头雾水&#xff0c;而后百度了怎么编写wordcount程…

java学习(4):第一个java程序

1第一个java文件 编写一个.java后缀的文件 public class helloworld{ public static void main(String[] args){ System.out.println(“helloworld”); } } 2cmd 编译java javac helloworld 生成class文件使用 Java helloworld 输出helloworld结束 个人练习 public class test…

java学习(5):全局变量和局部变量

public class qulitity{ static int num125; public static void main(String[] args){ System.out.println(“全局变量的值为”num1); int num212; System.out.println(num2); Test(); } public static void Test(){ int num21000; System.out.println(num2); } }

C语言知识点笔记完全整理

这个大长篇相当于是自己对于c语言学习的一个总结&#xff0c;会持续更新完善。 后续会在寒假整理一些经典的例题附带题解&#xff0c;当然希望我学到的东西、总结的经验&#xff0c;能够给后来者提供一个更好的学习途径&#xff0c;从入门到精通而不再是放弃。 也欢迎读者提出…

[HAOI2016]食物链

题目描述 如图所示为某生态系统的食物网示意图&#xff0c;据图回答第1小题现在给你n个物种和m条能量流动关系&#xff0c;求其中的食物链条数。物种的名称为从1到n编号M条能量流动关系形如a1 b1a2 b2a3 b3......am-1 bm-1am bm其中ai bi表示能量从物种ai流向物种bi,注意单独的…

java学习(6):数据类型

public class Shortdata{ public static void main(String[] args){ byte by 45; short sho 32767; System.out.println(“sho的值是”sho); //获取最大值 System.out.println(Byte.MAX_VALUE); System.out.println(Short.MAX_VALUE); //获取最小值System.out.println(Byte.M…

Xcode添加pch文件

1.打开Xcode工程. 在Supporting Files目录下,选择 File > New > File > iOS > Other > PCH File 然后点击下一步&#xff1b; 2.如果项目名称为Demo, PCH 文件的名字为Test.pch,然后创建&#xff1b;3.选择 PCH 文件创建Test.pch文件4.找到 Project > Build …

java学习(7):巩固练习

//任务1 //使用记事本或其他文本编辑器编写一个java控制台程序&#xff0c;定义一个包含main方法的java类&#xff0c;在main方法中使用合适的数据类型定义如下局部变量&#xff0c;标识符要严格遵守java规范。 //学生姓名&#xff1b;学生年龄&#xff1b;学生身高&#xff0c…

java学习(8):巩固练习

//任务2 编写控制台程序将以下给定的整数常量用合适的变量接收并将其10进制值与二进制表示形式分别输出打印在控制台界面 //55&#xff1b;666&#xff1b;1080&#xff1b;2500&#xff1b;78451&#xff1b; public class test02{ public static void main(String[] args){ /…

wordpress安装_WordPress第三课:使用SOFTACULOUS安装WORDPRESS

在精简的过程中&#xff0c;你会发现你更加明确想要什么&#xff01;目标变得明确&#xff0c;生活也将变得清晰。安装WordPress最简单的方法是使用自动安装程序&#xff0c;这是一个特殊的工具&#xff0c;可以在你的网站上安装程序。大多数虚拟主机都会提供一个自动安装程序作…

keepalive日志_12.日志收集项目-数据流图以及nginx安装

数据流图nginx安装中文文档http://tengine.taobao.org/nginx_docs/cn/docs/基础依赖与安装yum -y install gcc gcc-c autoconf pcre pcre-devel make automakeyum -y install wget vim httpd-toolsyum源在官网拷贝vi /etc/yum.repos.d/nginx.repo[nginx-stable]namenginx stabl…

【算法】禁忌搜索算法(Tabu Search,TS)超详细通俗解析附C++代码实例

01 什么是禁忌搜索算法&#xff1f; 1.1 先从爬山算法说起 爬山算法从当前的节点开始&#xff0c;和周围的邻居节点的值进行比较。 如果当前节点是最大的&#xff0c;那么返回当前节点&#xff0c;作为最大值 (既山峰最高点)&#xff1b;反之就用最高的邻居节点来&#xff0c;替…

14. Java基础之泛型

一. 泛型概念的提出&#xff08;为什么需要泛型&#xff09;&#xff1f; 首先&#xff0c;我们看下下面这段简短的代码: 1 public class GenericTest {2 3 public static void main(String[] args) {4 List list new ArrayList();5 list.add("qqyum…

java学习(15):巩固练习

//任务 1 //编写控制台java程序&#xff0c;使用Scanner 对象相关方法从 //控制台接收用户输入如下数据并使用相关的局部变量接收&#xff0c;在控制台打印输出。 //老师的姓名&#xff1b;老师的性别&#xff1b;老师的工资&#xff1b;老师的年龄&#xff1b;工作时长 import…

java学习(16):巩固练习

/任务 2 编写控制台java程序&#xff0c;将以下数据使用合理类型变量进行接收赋值 3.5&#xff1b;185.59&#xff1b;8500.50 要求在控制台打印这些数据并只显示整数部分。/ import java.util.Scanner; public class test02{ public static void main(String[] args){ Scanner…

mac电脑投屏到小米盒子_苹果手机搜不到小米盒子怎么办?

刚买的小米电视盒子迫不及待想投屏&#xff0c;但是手机是苹果系统&#xff0c;都是连得同一wifi&#xff0c;可是手机就是搜索不到小米家的客厅电视&#xff0c;这种情况该怎么办呢&#xff1f;以下小编给大家详细介绍了苹果手机搜不到小米盒子该怎么办。苹果设备中搜不到小米…