# 帧动画函数库

#
timeline

// timeline.js
'use strict';

var DEFAULT_INTERVAL = 1000 / 60;

//初始化状态
var STATE_INITIAL = 0;
//开始状态
var STATE_START = 1;
//停止状态
var STATE_STOP = 2;

/**
 * Timline时间轴类
 * @constructor
 */
function Timeline() {
	this.animationHandler = 0;
	this.state = STATE_INITIAL;
}

/**
 * 时间轴上每一次回调执行的函数
 * @param time 从动画开始到当前执行的时间
 */
Timeline.prototype.onenterframe = 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.restart = function () {
	if (this.state === STATE_START)
		return;
	if (!this.dur || !this.interval)
		return;

	this.state = STATE_START;

	//无缝连接停止动画的状态
	startTimeline(this, +new Date() - this.dur);
};

/**
 * 动画停止
 */
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);
};

/**
 * 时间轴动画启动函数
 * @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) {
			timeline.onenterframe(now - startTime);
			lastTick = now;
		}
	}
}

/**
 * raf
 */
var requestAnimationFrame = (function () {
	return window.requestAnimationFrame ||
		window.webkitRequestAnimationFrame ||
		window.mozRequestAnimationFrame ||
		window.oRequestAnimationFrame ||
			//所有都不支持,用setTimeout兼容
		function (callback) {
			return window.setTimeout(callback, (callback.interval || DEFAULT_INTERVAL)); // make interval as precise as possible.
		};
})();

/**
 * cancel raf
 */
var cancelAnimationFrame = (function () {
	return window.cancelAnimationFrame ||
		window.webkitCancelAnimationFrame ||
		window.mozCancelAnimationFrame ||
		window.oCancelAnimationFrame ||
		function (id) {
			window.clearTimeout(id);
		};
})();

module.exports = Timeline;

#
animation

'use strict';

var Timeline = require('./timeline');
var loadImage = require('./imageloader');

//初始化状态
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 Animation() {
	this.taskQueue = [];
	this.timeline = new Timeline();
	this.state = STATE_INITIAL;
	this.index = 0;
}

/**
 * 添加一个同步任务,预加载图片
 * @param imglist 图片数组
 */
Animation.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 图片地址
 */
Animation.prototype.changePosition = function (ele, positions, imageUrl) {
	var len = positions.length;
	var taskFn;
	var type;
	if (len) {
		var me = this;
		taskFn = function (next, time) {
			//如果指定图片,则设置dom对象的背景图片地址
			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);
};

/**
 * 添加一个异步定时任务,通过定时改变背景图片地址,实现帧动画
 * @param ele dom(Image对象)
 * @param imglist 图片地址数组
 */
Animation.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 taskFn 每帧执行的任务函数
 */
Animation.prototype.enterFrame = function (taskFn) {
	return this._add(taskFn, TASK_ASYNC);
};

/**
 * 添加一个同步任务,可在上一个任务完成执行回调函数
 * @param callback 回调函数
 */
Animation.prototype.then = function (callback) {
	var taskFn = function (next) {
		callback(this);
		next();
	};
	var type = TASK_SYNC;

	return this._add(taskFn, type);
};

/**
 * 开始执行任务
 * @param interval 异步定时任务执行的间隔
 */
Animation.prototype.start = function (interval) {
	//如果任务已经开始,则返回
	if (this.state === STATE_START)
		return this;
	//如果任务链中没有任务,则返回
	if (!this.taskQueue.length)
		return this;
	this.state = STATE_START;

	this.interval = interval;
	this._runTask();
	return this;
};

/**
 * 添加一个同步任务,该任务就是回退到上一个任务中,
 * 实现重复上一个任务的效果,可定义重复的次数。
 * @param times 重复次数
 */
Animation.prototype.repeat = function (times) {
	var me = this;
	var taskFn = function () {
		if (typeof times === 'undefined') {
			//无限回退到上一个任务
			me.index--;
			me._runTask();
			return;
		}
		if (times) {
			times--;
			//回退到上一个任务
			me.index--;
			me._runTask();
		} else {
			//达到重复执行次数,则跳转到下一个任务
			var task = me.taskQueue[me.index];
			me._next(task);
		}
	};
	var type = TASK_SYNC;

	return this._add(taskFn, type);
};

/**
 * 添加一个同步任务,该任务就是无线循环上一次任务
 */
Animation.prototype.repeatForever = function () {
	return this.repeat();
};

/**
 * 设置当前任务结束后下一个任务开始前的等待时间
 * @param time 等待的时长
 */
Animation.prototype.wait = function (time) {
	if (this.taskQueue && this.taskQueue.length > 0) {
		this.taskQueue[this.taskQueue.length - 1].wait = time;
	}
	return this;
};

/**
 * 暂停当前执行的异步定时任务
 */
Animation.prototype.pause = function () {
	if (this.state === STATE_START) {
		this.state = STATE_STOP;
		this.timeline.stop();
		return this;
	}
	return this;
};

/**
 * 重新开始执行当前异步定时任务
 */
Animation.prototype.restart = function () {
	if (this.state === STATE_STOP) {
		this.state = STATE_START;
		this.timeline.restart();
		return this;
	}
	return this;
};

/**
 * 释放资源
 */
Animation.prototype.dispose = function () {
	if (this.state !== STATE_INITIAL) {
		this.state = STATE_INITIAL;
		this.taskQueue = null;
		this.timeline.stop();
		this.timeline = null;
		return this;
	}
	return this;
};

/**
 * 添加一个任务到任务队列中
 * @param taskFn 任务方法
 * @param type 任务类型
 * @returns {Animation}
 * @private
 */
Animation.prototype._add = function (taskFn, type) {
	this.taskQueue.push({
		taskFn: taskFn,
		type: type
	});
	return this;
};

/**
 * 执行任务
 * @private
 */
Animation.prototype._runTask = function () {
	if (!this.taskQueue || this.state !== STATE_START)
		return;
	//如果任务链任务执行完则释放资源
	if (this.index === this.taskQueue.length) {
		this.dispose();
		return;
	}
	//获得任务链上的一个任务
	var task = this.taskQueue[this.index];
	if (task.type === TASK_SYNC) {
		this._syncTask(task);
	} else {
		this._asyncTask(task);
	}
};

/**
 * 同步任务
 * @param task 执行任务的函数
 * @private
 */
Animation.prototype._syncTask = function (task) {
	var me = this;
	var next = function () {
		//切换到下一个任务
		me._next(task);
	};
	var taskFn = task.taskFn;
	taskFn(next);
};

/**
 * 异步任务
 * @param task 执行异步的函数
 * @private
 */
Animation.prototype._asyncTask = function (task) {
	var me = this;
	//定义每一帧执行的回调函数
	var enterframe = function (time) {
		var taskFn = task.taskFn;
		var next = function () {
			//停止执行当前任务
			me.timeline.stop();
			//执行下一个任务
			me._next(task);
		};
		taskFn(next, time);
	};

	this.timeline.onenterframe = enterframe;
	this.timeline.start(this.interval);
};

/**
 * 切换到下一个任务,如果当前任务需要等待,则延时执行
 * @param task 下一个任务
 * @private
 */
Animation.prototype._next = function (task) {
	var me = this;
	this.index++;
	task.wait ? setTimeout(function () {
		me._runTask();
	}, task.wait) : this._runTask();
};


function createAnimation() {
	return new Animation();
}

createAnimation.version = __VERSION__;

module.exports = createAnimation;

#
imageloader

'use strict';

var __id = 0;

/**
 * 动态创建id
 * @returns {number}
 */
function getId() {
	return ++__id;
}

/**
 * 预加载图片函数
 * @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];

		// 如果item是个字符串,则构造object
		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,则直接调用
	if (!count) {
		callback(success);
	}
	//如果设置了加载时长,则设置超时函数计时器
	else if (timeout) {
		timeoutId = setTimeout(onTimeout, timeout);
	}

	/**
	 * 真正进行图片加载的函数
	 * @param item 图片元素对象
	 */
	function doLoad(item) {
		item.status = "loading";

		var img = item.img;
		//定义图片加载成功的回调函数
		img.onload = function () {
			//如果每张图片都成功才算成功
			success = success && true;
			item.status = "loaded";
			done();
		};
		img.onerror = function () {
			//若有一张图片加载失败,则为失败
			success = false;
			item.status = "error";
			done();
		};
		//发起一个http(s)请求加载图片
		img.src = item.src;

		/**
		 * 每张图片加载完成的回调函数
		 */
		function done() {
			//事件清理
			img.onload = img.onerror = null;

			try {
				//删除window上注册的属性
				delete window[item.id];
			}
			catch (e) {

			}
			//每张图片加载完成,计数器减一,当所有图片加载完毕且没有超时的情况下,
			//清除超时计时器,且执行回调函数
			if (!--count && !isTimeout) {
				clearTimeout(timeoutId);
				callback(success);
			}
		}
	}

	/**
	 * 超时函数
	 */
	function onTimeout() {
		isTimeout = true;
		callback(false);
	}
}

module.exports = loadImage;