发布:2016/2/3 9:00:30作者:管理员 来源:本站 浏览次数:1762
参照jquery animate,我简单地实现了animate动画效果,下面为大家介绍其中基本原理。
单步动画是相对多步动画而言的,单步动画是指物体的多个动画同步执行,而多步动画是指物体的多个动画是按照步骤执行的。由于多步动画是基于单步动画实现的,所以本文主要介绍单步动画。
我们都知道,动画是有一帧一帧画面构成的,每一帧的画面都记录着物体在动画中某时刻下的状态。为了绘制出每一帧画面,我们需要用到缓动函数。
缓动函数是决定物体状态变化方式的一种函数,只要我们给出四个数值:已过时间、初始状态、改变量、持续时间,缓动函数就能帮助我们计算出这一帧画面下物体的状态。比如easeInQuad
缓动函数:
// t: 已过时间,已过时间 = 当前时间 - 动画开始时间,范围是[0-d] // b: 初始状态 // c: 改变量,改变量 = 最终状态 - 初始状态 // d: 动画持续时间 var easeInQuad = function(t, b, c, d) { return c*(t/=d)*t + b; };
现在假设页面中有个矩形,它的动画是:在1000毫秒内从left:100px
变为left:300px
,我们可以计算出500毫秒时矩形的left
属性值:
/* 已过时间 t = 500 初始状态 b = 100 改变量 c = 300 - 100 = 200 持续时间 d = 1000 */ easeInQuad(500, 100, 200, 1000); // <-- 150
该缓动函数返回150,由此我们知道:在动画开始500毫秒后,矩形的left
属性值应该为150px。
缓动函数有很多种,相同动画下不同缓动函数绘制出来的每一帧画面都各不相同。就上述矩形而言,在不同缓动函数下,矩形的移动可能是匀速的,可能是先慢后快,也可能是其他效果。所幸的是,这些缓动函数都已经写好了,我们可以参考:jQuery.easing。
由于下面会用到,所以我们先将这些缓动函数稍微改改,并添加到代码中:
var easing = { def: 'easeOutQuad', swing: function (t, b, c, d) { return easing[easing.def](t, b, c, d); }, // ... };
对于网页来说,要绘制动画,就要获取和改变元素的样式,所以我们可以实现一个类似于jQuery.fn.css
的函数:
var css = function(elem, obj) { if (arguments.length === 3) { elem.style[arguments[1]] = arguments[2]; } else { return getComputedStyle(elem, null)[obj]; } }; // 获取样式 css(elem, 'left'); // 改变样式 css(elem, 'left', 100);
我们都知道,动画的流畅程度与每秒的帧数有关,那么,对于动画来说,每秒多少帧才能达到画面流畅?
其实玩过游戏的同学都知道,如果游戏运行能达到60帧,则游戏画面是流畅的,而低于60帧的画面会让我们就觉得卡顿,所以同样,我们绘制动画只要达到每秒60帧就足够了。
结合缓动函数,我们用setInterval
以每秒60帧来绘制动画,我们先写出代码形式:
// 每秒60帧意味着每16.6666毫秒就要绘制一帧动画 setInterval(function() { var val = easeInQuad(t, b, c, d); css(elem, 'left', val) }, 16);
刚刚开始学js的时候,我们也许都试过为每一个执行动画的元素新建一个setInterval
。但是,想想看,如果有100个元素要执行动画,就有100个setInterval
在运行,这是非常影响性能的。
实际上,setInterval
的作用不过是绘制每一帧的动画,所以我们可以将所有动画集中起来,统一用一个setInterval
来处理。
所以,我们设想是这样的:创建一个动画池,将待执行动画都扔到这个池子中,然后用一个setInterval
统一绘制。
我们先创建一个动画池:
var pool = [];
接下来,我们准备一个函数animation
,负责获取绘制动画所需要的所有信息,并将这些信息打包成一个对象扔到动画池中,并开启动画绘制函数。(注意,为了计算已过时间,我们还需要记录下动画开始时间):
/* elem: 元素 attr: 要改变的样式属性,形式如:{'left': 100, top: 300} duration: 动画持续时间,默认400 type: 缓动函数类型,默认为swing */ var animation = function(elem, attr, duration, type) { var beginVal, prop; // 记录所需信息 for (prop in attr) { beginVal = parseInt(css(elem, prop)); pool.push({ elem: elem, propName: prop, // 样式属性名 beginVal: beginVal, // 初始状态 changeVal: attr[prop] - beginVal, // 改变量 bTime: new Date().getTime(), // 动画开始时间 duration: duration || 400, // 动画持续时间 type: type || 'swing' // 缓动函数类型 }); } // 开启动画绘制 run(pool, easing); };
当我们将所有信息都打包扔到动画池后,我们开启动画绘制,下面介绍如何扫描动画池并绘制动画
一旦动画池中装载了待绘制的动画,我们需要用一个函数不断地扫描动画池中的各个动画,并统一绘制这些动画。我们准备一个run
函数来负责这部分工作:
var run = function(pool, easing) { var timeId = setInterval(function() { // ... }, 16); };
为了避免run
函数创建多个setInterval
,我们要采取一定的措施:如果有setInterval
正在处理动画池,则此时动画池的状态为running
,run
函数不执行:
var run = function(pool, easing) { // 如果动画池正在被处理 或 为空,则不执行 if (pool[0] === 'running' || !pool.length) return; // 在处理动画池之前,先为动画池加上状态标识 pool.unshift('running'); var timeId = setInterval(function() { // ... }, 16); };
有了以上保证,下面我们可以安心处理动画池内的动画了:
var run = function(pool, easing) { // ... var timeId = setInterval(function() { var obj, val, i, t, b, c, d; // 扫描动画池 并 绘制动画 for(i = pool.length - 1; i > 0; i--) { obj = pool[i]; t = new Date().getTime() - obj['bTime']; b = obj['beginVal']; c = obj['changeVal']; d = obj['duration']; type = obj['type']; // 计算状态 // 超过时间,则表示动画完成,从动画池中剔除该动画 if (t >= d) { val = b + c; pool.splice(i, 1); } else { val = easing[type](t, b, c, d); } // 改变状态 css(obj['elem'], obj['propName'], val); } // 如果动画池只剩下‘running’标识,即动画池为空 // 则删除Interval和状态标志 if (pool.length === 1) { clearInterval(timeId); pool.pop(); } }, 16); };
假设我们有如下代码:
animation(block, { 'left': '200px' }, 800, 'easeOutBounce'); animation(block, { 'top': '100px' }, 800, 'easeOutBounce');
效果为:
以上矩形有两个动画,一个是向右移动,一个是向下移动,这两个动画是同时执行的,这是因为animation函数负责单步动画,无法让动画按步骤执行的。
多步动画的实现需要结合上述的单步动画和队列。其原理在于:多步动画有多个步骤,每个步骤就是一个单步动画,分别将这些单步动画包装成独立的函数并压入队列中,在绘制多步动画的过程中,每次从队列中拿出一个单步动画来执行,只有当前一个单步动画执行完,才去处理队列中的下一个动画,这样每个单步动画之间就有了先后的关系。代码形式如下:
var animate = function(elem, attr, duration, type) { // 将单步动画包装成一个独立的函数 var fnc = animation.bind(window, elem, attr, duration, type); // 压入队列 queue(elem, fnc); };
(由于多步动画涉及数据存储以及queue队列的实现,介绍起来会比较繁琐,所以点到为止,想了解的同学可以看看下面的详细代码)
结合队列的操作,动画终于能够按步骤执行了:
// 同一个animate下的动画同步执行,不同animate下的动画按先后顺序执行 animate(block, { 'left': '200px', 'opacity': 0.5 }, 800, 'easeOutBounce'); animate(block, { 'top': '100px', 'opacity': 1 }, 800, 'easeOutBounce');
效果如下:
© Copyright 2014 - 2025 柏港建站平台 ejk5.com. 渝ICP备16000791号-4