GreenSocks Animation Platform详细工作机制以及TweenMax用法

2020-04-17 10:20:47

参考地址 GreenSocks Animation Platform详细工作机制

GSAP(GreenSocks Animation Platform)是一个性能较好的前端动画库。最近在写一个前端SVG动画编辑器时选择了它作为底层的动画库。为了减少踩坑,我大致浏览了它的源代码,这篇文章主要是对我的理解进行记录。

我会先简单介绍一下这个动画库的API,再介绍它的插件机制,最后会从一个用例出发跟踪其运行机制。

1. API

GSAP一共有四个用来创建动画片段的api。TweenLite, TweenMax, TimelineLite和TimelineMax。TweenLite和TweenMax用于创建单个动画片段。TimelineLite和TimelineMax用于创建多个动画片段的组合。我会忽略Timeline API,只介绍Tween API.

TweenMax和TweenLite的区别主要有两点。一是TweenMax引入了许多实用插件比如CSSPlugin,BezierPlugin等,二是TweenMax继承了TweenLite并提供了一些额外的API比如repeat(), yoyo()等。以下是少部分TweenMax和TweenLite共有的API。

  1. TweenMax.to

TweenMax.to(target:Object, duration:Number, vars:Object ) : TweenMax

TweenMax.to是一个用来创建TweenMax的静态方法。当它被创建出来时,会在指定时间范围内将目标对象的给定属性从当前值变化到指定的值。动画是直接播放的。

例子:

var tween = TweenMax.to(obj, 1, {x:100});

上面这个例子中,obj的x属性会从当前值过渡到100,整个动画持续1秒,TweenMax实例会存放在变量tween之中。

2. TweenMax.fromTo

TweenMax.fromTo( target:Object, duration:Number, fromVars:Object, toVars:Object ) : TweenMax

该方法和TweenMax.to类似,区别在于动画属性的起始值为给定的fromVars

3. play

.play( from:*, suppressEvents:Boolean ) : *

TweenMax实例的继续播放动画方法,from为可选参数,表示播放的起始时间。 suppressEvent为false时将不会触发每帧更新动画时的回调函数,默认值为true

4. pause

.pause( atTime:*, suppressEvents:Boolean ) : *

TweenMax实例的暂停动画方法,atTime为可选参数,表示暂停的时间点。

5. seek

.seek( time:*, suppressEvents:Boolean ) : *

将动画跳跃至给定的时间点, 无论当前动画是暂停还是播放。

以上是常用API,其余API不作介绍。

2.GSAP插件

GSAP使用不同的插件来处理不同类型的属性的动画,比如AttrPlugin会负责DOM节点属性以及SVG节点属性的更新,而CSS属性则会被CSSPlugin处理。

在介绍插件的加载和使用机制之前,我们先来看一下GSAP的模块机制(源代码中是称为class,但是我觉得还是称其为模块比较好)。GSAP有一套自己的模块加载机制,这个模块机制和Angularjs的模块机制类似,每个模块有自己的名字和依赖的模块的名字。在初始化一个模块之前,GSAP会去查找当前模块依赖的模块是否初始化,如果所有的被依赖模块都已经初始化,GSAP会初始化当前模块。如果有任意一个被依赖模块还没有加载,GSAP会创建一个空模块作为它的实例,并且将当前当前的模块放在被依赖模块的子模块集合中,当这个被依赖模块被加载后它会通知所有依赖它的子模块。

下面的代码是CSSPlugin的模块定义部分:

_gsScope._gsDefine("plugins.CSSPlugin", ["plugins.TweenPlugin","TweenLite"], function(TweenPlugin, TweenLite) {
   // CSSPlugin 模块代码})

它声明CSSPlugin模块依赖TweenPlugin和TweenLite。

TweenPlugin是所有插件的基类。TweenPlugin和TweenLite是紧密结合在一起的,它会和TweenLite共享一些私有方法,其中最重要的是_addPropTween方法和setRatio方法。_addPropTween方法会在TweenLite创建的时候被调用,这个方法负责创建处理动画的最小单位pt(我也不知道这是什么英文单词的缩写)。pt对象的属性如下:

{
  t, //动画的目标对象,有可能是原始目标也有可能是插件的实例  p, //动画作用的属性名或者方法名  s, //起始值  c, //change  f. //表示p是不是函数  _next, //下一个pt  _prev, //上一个pt  pg, //是否是plugin  n, // name  pr, // 优先级  m // mod (function|0)}

TweenLite中会包含一个pt的链表,在每帧动画发生的时候会被依次调用。

setRatio负责在每帧动画发生时更新目标对象属性,它的参数是一个0-1范围内的浮点数,0表示动画的起始状态,1表示结束状态。setRatio方法会从第一个pt开始遍历并调用pt上的方法或者设置属性的值。

此外TweenPlugin还有一个静态方法activate(plugins),该方法负责把插件的构造函数存入插件列表中。

接下来我们就从一个例子中来看一下GSAP是如何工作的。

3. 使用GSAP创建颜色渐变动画

为了简单起见,下面这个例子是将一个svg circle元素的填充颜色从红色渐变成绿色。

<!DOCTYPE html><html><head>
    <title>颜色渐变</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script></head><body>
    <svg width="100" height="100">
        <circle id="test" fill="red" stroke="black" stroke-width="2" cx="50" cy="50" r="48"></circle>
    </svg>
    <script>
        var tween = TweenMax.fromTo(document.getElementById("test"), 5, {attr: {fill: "red"}}, {attr: {fill: "green"}})
        tween.seek(1)
    </script></body></html>

实际效果如下:

接下来我会从上面这个例子出发来看一下GSAP是如何在每一帧动画发生的时候去设置circle的颜色。动画本身用的是浏览器的requireAnimationFrame接口,这个不作介绍。

首先是加载TweenMax。

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script>

在上文中提到TweenMax包含TweenLite,AttrPlugin和CSSPlugin。AttrPlugin负责调用对象的setAttribute方法去设置颜色,CSSPlugin在这个例子中会负责解析颜色。整个代码执行过程分为以下三步:

(1)GSAP代码的加载和初始化。

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script>

在这一步中会注册AttrPlugin和CSSPlugin。

const AttrPlugin =_gsScope._gsDefine.plugin({
        propName: "attr",
	API: 2,
	version: "0.6.1",
        //called when the tween renders for the first time. This is where initial values should be recorded and any setup routines should run.	init: function(target, value, tween, index) {
		var p, end;
		if (typeof(target.setAttribute) !== "function") {
			return false;
		}
		for (p in value) {
			end = value[p];
			if (typeof(end) === "function") {
				end = end(index, target);
			}
			this._addTween(target, "setAttribute", target.getAttribute(p) + "", end + "", p, false, p);
			this._overwriteProps.push(p);
		}
		return true;
	}});

上面这段代码是注册AttrPlugin,其中 _gsScope._gsDefine.plugin是用以注册无模块依赖的Plugin的简便方法。

propName: "attr"

propName字段表示插件将会作用在动画起始值和结束值的"attr"属性上。

注意在API介绍中,用来创建动画片段的TweenMax.fromTo方法中接受的起始值和结束值都是JSON。

init字段存放创建插件实例时会被调用的初始化方法。

CSSPlugin代码量较多,只截取例子中会用到的部分。

_gsScope._gsDefine("plugins.CSSPlugin", ["plugins.TweenPlugin","TweenLite"], function(TweenPlugin, TweenLite) {
	var CSSPlugin;
	// CSSPlugin的定义部分
	CSSPlugin.colorStringFilter = function(a) {
		var combined = a[0] + " " + a[1],
		toHSL;
		if (_colorExp.test(combined)) {
			toHSL = (combined.indexOf("hsl(") !== -1 || combined.indexOf("hsla(") !== -1);
			a[0] = _formatColors(a[0], toHSL);
			a[1] = _formatColors(a[1], toHSL);
		}
		_colorExp.lastIndex = 0;
	};

	if (!TweenLite.defaultStringFilter) {
		TweenLite.defaultStringFilter = CSSPlugin.colorStringFilter;
	}
	// 其余定义部分
	TweenPlugin.activate([CSSPlugin]);
	return CSSPlugin;})

CSSPlugin模块使用的是_gsScope._gsDefine,因为它有两个依赖模块。

在初始化模块的时候,CSSPlugin定义了一个colorStringFilter方法,该方法用来解析DOM属性和CSS属性中的颜色字符串比如"red","rgb(0,0,0)"等。CSSPlugin将会把TweenLite的defaultStringFilter设置为该方法。

(2)TweenMax的创建

var tween = TweenMax.fromTo(document.getElementById("test"), 5, {attr: {fill: "red"}}, {attr: {fill: "green"}})

代码段中的这一句表示对id是"text"的元素创建一个TweenMax,动画持续时间为5秒,给定起始值fill为red结束值fill为green。

在TweenMax对象的创建阶段,它会根据传入参数的属性来决定使用哪个Plugin。在注册AttrPlugin时,参数中有一个属性叫

propName: "attr"

这个参数决定了TweemMax会将动画参数对象中的attr部分交给AttrPlugin处理。

AttrPlugin在解析属性的时候看到fill的值为"red"(字符串),将会调用defaultStringFilter来解析这个值。

经过一系列初始化操作之后,最终返回的TweenMax大致会包含如下图所示的内容。

其中AttrPlugin的_firstPT会包含三个pt分别用来插值计算R,G,B三个通道的值,这里为了简化图片没有将它们都列出来。

(3)播放动画

tween.seek(1)

表示将动画跳跃至1秒钟的位置。TweenMax将会从_firstPT遍历调用所有的pt。

在处理pt时,TweenMax会根据pt上的f字段的值来判断到底是要调用pt绑定的对象(pt.t)的方法还是设置对象的字段的值。

第一步先从TweenMax自身的_firstPT出发,根据判断调用了绑定的对象(AttrPlugin)实例上的setRatio方法(位于AttrPlugin的原型链上)。该方法和TweenMax用来遍历pt的方法是同一个函数。

第二步也是根据AttrPlugin的_firstPT的属性继续递归调用setRatio函数。

第三步AttrPlugin._firstPT.t中包含了四个pt,setRatio会先遍历前三个函数计算出R,G,B通道的值,最后再调用_applyPT对象上的setAttribute方法设置svg ellipse元素的fill属性。

总结

GreenSocks动画库在处理动画时比较巧妙地用pt作为插值计算的最小单位把复杂的属性值比如颜色或者三维空间坐标拆分成了一串pt来分别计算不同通道的值。这样对复杂属性的操作就简化成了如何将其拆分为一串pt,减少了处理各种复杂属性的代码量(给看源代码的人造成了不少麻烦,滑稽)。以上就是我在看了源代码之后的笔记,希望以后调试起来更加得心应手。


  • 2019-08-14 13:18:59

    Linux系统下CPU使用(load average)梳理

    在平时的运维工作中,当一台服务器的性能出现问题时,通常会去看当前的CPU使用情况,尤其是看下CPU的负载情况(load average)。对一般的系统来说,根据cpu数量去判断。比如有2颗cup的机器。如果平均负载始终在1.2以下,那么基本不会出现cpu不够用的情况。也就是Load平均要小于Cpu的数量。

  • 2019-08-14 14:27:35

    计算密集型和IO密集型

    在进行I/O操作的时候,是将任务交给DMA来处理,请求发出后CPU就不管了,在DMA处理完后通过中断通知CPU处理完成了。I/O操作消耗的cpu时间很少.

  • 2019-08-14 14:29:12

    浅谈nodejs和php

    现在,Web开发公司和开发人员可以选择多种技术栈来构建Web应用程序。早期网络发展,不同的技术被用于前端和后端开发。但是,随着Node.js的发布,布局发生了变化,因为它允许开发人员使用 JavaScript 编写后端代码。这最终催生了MEAN(MongoDB + Express +AngularJS + NodeJS )堆栈 web 开发框架,从前端到后端甚至是数据库(MongoDB -JSON)都使用 JavaScript。在 Node.js 之前,Web 开发通常是在 PHP 的帮助下完成的,因为它很容易与 HTML 集成,帮助开发人员立即构建动态网站。在这篇文章中,我们将比较 Node.js 和 PHP,看哪一个最适合当前的行业需求。

  • 2019-08-15 13:32:18

    Node.js是如何解决服务器高性能瓶颈问题的

    在Java、PHP或者.net等服务器端语言中,会为每一个客户端连接创建一个新的线程。而每个线程需要耗费大约2MB内存。也就是说,理论上,一个8GB内存的服务器可以同时连接的最大用户数为4000个左右。要让Web应用程序支持更多的用户,就需要增加服务器的数量,而Web应用程序的硬件成本当然就上升了。

  • 2019-08-15 13:33:53

    nodejs的10个性能优化技巧

    在我接触JavaScript(无论浏览器还是NodeJS)的时间里,总是遇到有朋友有多线程的需求。而在NodeJS方面,有朋友甚至直接说到,NodeJS是单线程的,无法很好的利用多核CPU。那么我们在使用过程中,就要非常注意性能优化了

  • 2019-08-16 13:18:48

    使用ffmpeg进行ts切片并AES-128加密

    由于解密的key文件都是公开的,所以并不能算上完全加密,用户只要把你的key+m3u8里的ts切片文件全部下载,用ffmpeg还是能解,这时就要考虑url的key防止用户直接下载和盗链。 ​

  • 2019-08-18 22:22:54

    Error:error: unable to remove file: Permission denied

    JNI里写的C++增加了函数或修改了,如果此时是Debug模式下,而且还没退出程序,就出现这个Permission denied的提示。解决也很简单:就是退出App即可。如果退出无响应,直接拔usb,重新插上也可以

  • 2019-08-19 10:24:29

    浅析Express中的路由与应用模式

    Express是一个基于Node.js的轻量级web开发框架,具有体积小,使用灵活等特点。查看Express的源码,如果不计供使用的中间件,主体框架只有一千余行代码,非常简练。