javascript帧动画(实例讲解)
前⾯的话
帧动画就是在“连续的关键帧”中分解动画动作,也就是在时间轴的每帧上逐帧绘制不同的内容,使其连续播放⽽成的动画。由于是⼀帧⼀帧的画,所以帧动画具有⾮常⼤的灵活性,⼏乎可以表现任何想表现的内容。本⽂将详细介绍javascript帧动画
概述
【分类】
常见的帧动画的⽅式有三种,包括gif、CSS3 animation和javascript
git和CSS3 animation不能灵活地控制动画的暂停和播放、不能对帧动画做更加灵活地扩展。另外,gif图不能捕捉动画完成的事件。所以,⼀般地,使⽤javascript来实现帧动画
【原理】
js实现帧动画有两种实现⽅式
1、如果有多张帧动画图⽚,可以⽤⼀个image标签去承载图⽚,定时改变image的src属性(不推荐)
2、把所有的动画关键帧都绘制在⼀张图⽚⾥,把图⽚作为元素的background-image,定时改变元素的background-position属性(推荐)
因为第⼀种⽅式需要使⽤多个HTTP请求,所以⼀般地推荐使⽤第⼆种⽅式
【实例】
下⾯是使⽤帧动画制作的⼀个实例
<div id="rabbit" ></div>
<button id="btn">暂停运动</button>
<script>
var url = 'rabbit-big.png';
var positions = ['0,-854','-174 -852','-349 -852','-524 -852','-698 -852','-873 -848'];
var ele = ElementById('rabbit');
var oTimer = null;
if(btn.innerHTML == '开始运动'){
frameAnimation(ele,positions,url);
btn.innerHTML = '暂停运动';
}else{
clearTimeout(oTimer);
btn.innerHTML = '开始运动';
}
}
frameAnimation(ele,positions,url);
function frameAnimation(ele,positions,url){
ele.style.backgroundImage = 'url(' + url + ')';
ele.style.backgroundRepeat = 'no-repeat';
var index = 0;
function run(){
var pos = positions[index].split(' ');
ele.style.backgroundPosition = pos[0] + 'px ' + pos[1] + 'px';
index++;
if(index >= positions.length){
index = 0;
}
oTimer = setTimeout(run,80);
}
run();
}
</script>
通⽤帧动画
下⾯来设计⼀个通⽤的帧动画库
【需求分析】
1、⽀持图⽚预加载
2、⽀持两种动画播放⽅式,及⾃定义每帧动画
3、⽀持单组动画控制循环次数(可⽀持⽆限次)
4、⽀持⼀组动画完成,进⾏下⼀组动画
5、⽀持每个动画完成后有等待时间
6、⽀持动画暂停和继续播放
7、⽀持动画完成后执⾏回调函数
【编程接⼝】
1、loadImage(imglist)//预加载图⽚
2、changePosition(ele,positions,imageUrl)//通过改变元素的background-position实现动画
3、changeSrc(ele,imglist)//通过改变image元素的src
4、enterFrame(callback)//每⼀帧动画执⾏的函数,相当于⽤户可以⾃定义每⼀帧动画的callback
5、repeat(times)//动画重复执⾏的次数,times为空时表⽰⽆限次
6、repeatForever()//⽆限重复上⼀次动画,相当于repeat()
7、wait(time)//每个动画执⾏完成后等待的时间
8、then(callback)//动画执⾏完成后的回调函数
9、start(interval)//动画开始执⾏,interval表⽰动画执⾏的间隔
10、pause()//动画暂停
11、restart()//动画从上⼀交暂停处重新执⾏
12、dispose()//释放资源
【调⽤⽅式】
⽀持链式调⽤,⽤动词的⽅式描述接⼝
【代码设计】
1、把图⽚预加载 -> 动画执⾏ -> 动画结束等⼀系列操作看成⼀条任务链。任务链包括同步执⾏和异步定时执⾏两种任务
2、记录当前任务链的索引
3、每个任务执⾏完毕后,通过调⽤next⽅法,执⾏下⼀个任务,同时更新任务链索引值
【接⼝定义】
'use strict';
/* 帧动画库类
* @constructor
*/
function FrameAnimation(){}
/* 添加⼀个同步任务,去预加载图⽚
* @param imglist 图⽚数组
*/
FrameAnimation.prototype.loadImage = function(imglist){}
/* 添加⼀个异步定时任务,通过定时改变图⽚背景位置,实现帧动画
* @param ele dom对象
* @param positions 背景位置数组
* @param imageUrl 图⽚URL地址
*/
FrameAnimation.prototype.changePosition = function(ele,positions,imageUrl){}
/* 添加⼀个异步定时任务,通过定时改变image标签的src属性,实现帧动画
* @param ele dom对象
* @param imglist 图⽚数组
*/
FrameAnimation.prototype.changeSrc = function(ele,imglist){}
/* 添加⼀个异步定时任务,⾃定义动画每帧执⾏的任务函数
* @param tastFn ⾃定义每帧执⾏的任务函数
*/
Frame = function(taskFn){}
/* 添加⼀个同步任务,在上⼀个任务完成后执⾏回调函数
* @param callback 回调函数
*/
FrameAnimation.prototype.then = function(callback){}
/* 开始执⾏任务,异步定时任务执⾏的间隔
* @param interval
*/
FrameAnimation.prototype.start = function(interval){}
/* 添加⼀个同步任务,回退到上⼀个任务,实现重复上⼀个任务的效果,可以定义重复的次数
* @param times 重复次数
*/
peat = function(times){}
/
* 添加⼀个同步任务,相当于repeat(),⽆限循环上⼀次任务
*
*/
peatForever = function(){}
/* 设置当前任务执⾏结束后到下⼀个任务开始前的等待时间
* @param time 等待时长
*/
FrameAnimation.prototype.wait = function(time){}
/* 暂停当前异步定时任务
*
*/
FrameAnimation.prototype.pause = function(){}
*/
start = function(){}
/* 释放资源
*
*/
FrameAnimation.prototype.dispose = function(){}
图⽚预加载
图⽚预加载是⼀个相对独⽴的功能,可以将其封装为⼀个模块imageloader.js
'use strict';
/**
* 预加载图⽚函数
* @param images 加载图⽚的数组或者对象
* @param callback 全部图⽚加载完毕后调⽤的回调函数
* @param timeout 加载超时的时长
*/
function loadImage(images,callback,timeout){
//加载完成图⽚的计数器
var count = 0;
//全部图⽚加载成功的标志位
var success = true;
//超时timer的id
var timeoutId = 0;
//是否加载超时的标志位
var isTimeout = false;
//对图⽚数组(或对象)进⾏遍历
for(var key in images){
//过滤prototype上的属性
if(!images.hasOwnProperty(key)){
continue;
}
//获得每个图⽚元素
//期望格式是object:{src:xxx}
var item = images[key];
if(typeof item === 'string'){
item = images[key] = {
src:item
};
}
//如果格式不满⾜期望,则丢弃此条数据,进⾏下⼀次遍历
if(!item || !item.src){
continue;
}
//计数+1
count++;
//设置图⽚元素的id
item.id = '__img__' + key + getId();
//设置图⽚元素的img,它是⼀个Image对象
item.img = window[item.id] = new Image();
doLoad(item);
}
//遍历完成如果计数为0,则直接调⽤callback
if(!count){
callback(success);
}else if(timeout){
timeoutId = setTimeout(onTimeout,timeout);
}
/**
* 真正进⾏图⽚加载的函数
* @param item 图⽚元素对象
*/
function doLoad(item){
item.status = 'loading';
var img = item.img;
//定义图⽚加载成功的回调函数
success = success && true;
item.status = 'loaded';
done();
}
//定义图⽚加载失败的回调函数
success = false;
item.status = 'error';
done();
}
//发起⼀个http(s)请求
img.src = item.src;
/**
* 每张图⽚加载完成的回调函数
*/
function done(){
try{
delete window[item.id];
}catch(e){
}
//每张图⽚加载完成,计数器减1,当所有图⽚加载完成,且没有超时的情况,清除超时计时器,且执⾏回调函数 if(!--count && !isTimeout){
clearTimeout(timeoutId);
callback(success);
}
}
}
/**
* 超时函数
*/
function onTimeout(){
isTimeout = true;
callback(false);
}
}
var __id = 0;
function getId(){
时间轴
在动画处理中,是通过迭代使⽤setTimeout()实现的,但是这个间隔时间并不准确。下⾯,来实现⼀个时间轴类timeline.js
'use strict';
var DEFAULT_INTERVAL = 1000/60;
//初始化状态
var STATE_INITIAL = 0;
//开始状态
var STATE_START = 1;
//停⽌状态
var STATE_STOP = 2;
var requestAnimationFrame = (function(){
questAnimationFrame || window.webkitRequestAnimationFrame|| RequestAnimationFrame || window.oRequestAnimationFrame || function(callback){ return window.setTimeout(callback,(callback.interval || DEFAULT_INTERVAL));
}
})();
var cancelAnimationFrame = (function(){
return window.cancelAnimationFrame || window.webkitCancelAnimationFrame || Cancel
AnimationFrame || window.oCancelAnimationFrame || function(id){
return window.clearTimeout(id);
}
})();
/**
* 时间轴类
* @constructor
*/
function Timeline(){
this.animationHandler = 0;
this.state = STATE_INITIAL;
}
/**
* 时间轴上每⼀次回调执⾏的函数
* @param time 从动画开始到当前执⾏的时间
*/
rframe = function(time){
}
/**
* 动画开始
* @param interval 每⼀次回调的间隔时间
*/
Timeline.prototype.start = function(interval){
if(this.state === STATE_START){
return;
}
this.state = STATE_START;
this.interval = interval || DEFAULT_INTERVAL;
startTimeline(this,+new Date());
}
/**
* 动画停⽌
*/
Timeline.prototype.stop = function(){
if(this.state !== STATE_START){
return;
}
this.state = STATE_STOP;
//如果动画开始过,则记录动画从开始到现在所经历的时间
if(this.startTime){
this.dur = +new Date() - this.startTime;
}
cancelAnimationFrame(this.animationHandler);
}
/
**
* 重新开始动画
*/
start = function(){
if(this.state === STATE_START){
return;
}
if(!this.dur || !this.interval){
return;
}
this.state = STATE_START;
/
/⽆缝连接动画
startTimeline(this,+new Date()-this.dur);
}
/**
* 时间轴动画启动函数
* @param timeline 时间轴的实例
* @param startTime 动画开始时间戳
*/
function startTimeline(timeline,startTime){
//记录上⼀次回调的时间戳
var lastTick = +new Date();
timeline.startTime = startTime;
nextTick.interval = timeline.interval;
nextTick();
/**
* 每⼀帧执⾏的函数
*/
function nextTick(){
var now = +new Date();
timeline.animationHandler = requestAnimationFrame(nextTick);
//如果当前时间与上⼀次回调的时间戳⼤于设置的时间间隔,表⽰这⼀次可以执⾏回调函数
if(now - lastTick >= timeline.interval){
lastTick = now;
}
}
动画类实现
下⾯是动画类animation.js实现的完整代码
'use strict';
var loadImage = require('./imageloader');
var Timeline = require('./timeline');
//初始化状态
var STATE_INITIAL = 0;
//开始状态
var STATE_START = 1;
//停⽌状态
var STATE_STOP = 2;
//同步任务
var TASK_SYNC = 0;
//异步任务
var TASK_ASYNC = 1;
/**
* 简单的函数封装,执⾏callback
* @param callback 执⾏函数
*/
function next(callback){
callback && callback();
}
/* 帧动画库类
* @constructor
*/
function FrameAnimation(){
this.taskQueue = [];
this.index = 0;
this.timeline = new Timeline();
this.state = STATE_INITIAL;
}
/* 添加⼀个同步任务,去预加载图⽚
* @param imglist 图⽚数组
*/
FrameAnimation.prototype.loadImage = function(imglist){
var taskFn = function(next){
loadImage(imglist.slice(),next);
};
var type = TASK_SYNC;
return this._add(taskFn,type);
}
/* 添加⼀个异步定时任务,通过定时改变图⽚背景位置,实现帧动画
* @param ele dom对象
* @param positions 背景位置数组
* @param imageUrl 图⽚URL地址
*/
FrameAnimation.prototype.changePosition = function(ele,positions,imageUrl){ var len = positions.length;
var taskFn;
var type;
if(len){
timeout on t2 timervar me = this;
taskFn = function(next,time){
if(imageUrl){
ele.style.backgroundImage = 'url(' + imageUrl + ')';
}
//获得当前背景图⽚位置索引
var index = Math.min(time/me.interval|0,len);
var position = positions[index-1].split(' ');
//改变dom对象的背景图⽚位置
ele.style.backgroundPosition = position[0] + 'px ' + position[1] + 'px';
if(index === len){
next();
}
}
type = TASK_ASYNC;
}else{
taskFn = next;
type = TASK_SYNC;
}
return this._add(taskFn,type);
}
/* 添加⼀个异步定时任务,通过定时改变image标签的src属性,实现帧动画
* @param ele dom对象
* @param imglist 图⽚数组
*/
FrameAnimation.prototype.changeSrc = function(ele,imglist){
var len = imglist.length;
var taskFn;
var type;
if(len){
var me = this;
taskFn = function(next,time){
//获得当前背景图⽚位置索引
var index = Math.min(time/me.interval|0,len);
//改变image对象的背景图⽚位置
ele.src = imglist[index-1];
if(index === len){
next();
}
}
type = TASK_ASYNC;
}else{
taskFn = next;
type = TASK_SYNC;
}
return this._add(taskFn,type);
}
/* 添加⼀个异步定时任务,⾃定义动画每帧执⾏的任务函数
* @param tastFn ⾃定义每帧执⾏的任务函数
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论