Skip to content

Instantly share code, notes, and snippets.

@yibuyisheng
Created October 31, 2017 08:09
Show Gist options
  • Save yibuyisheng/b07fced50502295d813b7239c4f9d962 to your computer and use it in GitHub Desktop.
Save yibuyisheng/b07fced50502295d813b7239c4f9d962 to your computer and use it in GitHub Desktop.
Animate
/**
* @file 逐帧动效类
* @author yibuyisheng
*/
/**
* 逐帧动效类
*
* @class
*/
export default class {
/**
* 动效依附的DOM元素,使用background实现动效
*
* @type {HTMLElement}
* @private
*/
element = null;
/**
* 每张小图的宽度(每张小图的宽度必须是一样的)
*
* @type {number}
* @private
*/
width = 0;
/**
* 每张小图的高度(每张小图的高度必须是一样的)
*
* @type {number}
* @private
*/
height = 0;
/**
* 整个大图每行有多少个小图
*
* @type {number}
* @private
*/
countPerLine = 0;
/**
* 其它一些参数,此处支持入场(show)、鼠标移入(hover)、鼠标移出(out)三种动画效果,这三种效果可以通过options配置,
* 每种动效的start代表起始图片是第几张,end代表结束图片是第几张。
*
* @type Object
* @private
*/
options = {
ranges: {
show: {
start: 0,
end: 0
},
hover: {
start: 0,
end: 0
},
out: {
start: 0,
end: 0
}
}
};
/**
* 动效任务列表,主要是为了防止后续的动效覆盖掉前面的动效,出现“突变”效果。
* 每一个task函数都有一个动画执行完成的回调函数。
*
* 每个元素的结构如下:
* {
* fn: function (callback) { ... },
* name: 'show' // 入场动画,目前支持的取值为:show、hover、out
* }
*
* @type {Array.<Object>}
* @private
*/
animationTasks = [];
/**
* 当前动效到哪张小图了
*
* @type {number}
* @private
*/
current = 0;
/**
* constructor
*
* @public
* @param {Element} element 动画元素
* @param {number} width 每张小图的宽度
* @param {number} height 每张小图的高度
* @param {number} countPerLine 每一行小图的数量
* @param {Object} options 额外参数
*/
constructor(element, width, height, countPerLine, options) {
this.element = element;
this.width = width;
this.height = height;
this.countPerLine = countPerLine || 17;
this.options = options || this.options;
}
/**
* 执行动画的基本方法
*
* @private
* @param {number} start 起始图片(在大图中第几张)
* @param {number} end 结束图片(在大图中第几张)
* @param {number} speed 动画执行速度
* @param {Function} completeCallback 完成动画的回调函数
*/
animate(start, end, speed = 1, completeCallback) {
this.isPaused = false;
let me = this;
let speedCounter = 0;
this.current = start;
run(start);
function run() {
requestAnimationFrame(function () {
if (speedCounter++ < speed) {
return run(me.current);
}
speedCounter = 0;
let x = -((me.current - 1) % me.countPerLine) * me.width;
let y = -Math.floor((me.current - 1) / me.countPerLine) * me.height;
me.element.style.backgroundPosition = x + 'px ' + y + 'px';
if (!me.isPaused && me.current < end) {
++me.current;
run();
}
else {
completeCallback && completeCallback();
}
});
}
}
/**
* 暂停
*
* @public
*/
pause() {
this.isPaused = true;
}
/**
* 添加动效任务。如果队列当中已经存在相应name的动画,就不再添加了。
*
* @private
* @param {Function} taskFn 动效函数,带有一个动效完成回调函数参数
* @param {string} name 动画名
*/
addTask(taskFn, name) {
if (!this.animationTasks.some(task => task.name === name)) {
this.animationTasks.push({
fn: taskFn,
name: name
});
}
}
/**
* 开始循环检测动效任务列表,只要发现有动效任务,就执行。
* 一个实例只能调用一次。
*
* @public
*/
startExecute() {
let tasks = this.animationTasks;
run();
function run() {
requestAnimationFrame(function () {
if (tasks.length) {
execute(run);
}
else {
run();
}
});
}
function execute(completeCallback) {
let task = tasks[0];
if (task) {
task.fn(() => {
// 动画执行完成,清理一下队列,同时开始下一个动画的执行
tasks.shift();
execute(completeCallback);
});
}
else {
completeCallback && completeCallback();
}
}
}
/**
* 入场动效。
*
* @public
*/
show() {
let rangesShow = this.options.ranges.show;
this.addTask(
completeCallback => this.animate(rangesShow.start, rangesShow.end, 3, completeCallback),
'show'
);
}
/**
* 鼠标移入动效
*
* @public
*/
hover() {
let rangesHover = this.options.ranges.hover;
this.addTask(
completeCallback => this.animate(rangesHover.start, rangesHover.end, 2, completeCallback),
'hover'
);
}
/**
* 鼠标移出动效
*
* @public
*/
out() {
let me = this;
let rangesOut = me.options.ranges.out;
this.addTask(
completeCallback => this.animate(rangesOut.start, rangesOut.end, 1, completeCallback),
'out'
);
}
/**
* 绑定鼠标事件
*
* @public
*/
bindMouseEvents() {
this.element.addEventListener('mouseenter', mouseenter);
this.element.addEventListener('mouseout', mouseout);
let me = this;
function mouseenter() {
me.hover();
}
function mouseout() {
me.out();
}
}
/**
* 绑定“入场”事件,在入场的时候执行相应动画
*
* @public
*/
bindShow() {
let me = this;
document.addEventListener('scroll', scroll);
// 初始化检查一下
scroll();
function scroll() {
let rect = me.element.getBoundingClientRect();
let viewHeight = window.innerHeight;
let buffer = 50;
if (rect.top > viewHeight - buffer || rect.bottom < buffer) {
return;
}
me.show();
document.removeEventListener('scroll', scroll);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment