前言:
在上篇的基础上,接入doAnimation()
逻辑图:
实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jQuery 之 $().animate()的实现 </title>
</head>
<body>
<!--<script src="jQuery.js"></script>-->
<div id="A" style="width:100px;height:50px;background-color: deeppink"> 这是 A </div>
<script>
// (function(a){// console.log(a) //name
// })('name')
//
// (function (b) {// console.log(b) //function(){console.log('name')}
// })(function () {// console.log('name')
// })
// 匿名函数自调用,下面好长好长的 function 就是 $
// 也就是说 $ 是一个 function(){xxx}
(function($) {window.$ = $;})(
// 这里也是匿名函数自调用
// 本质就是经过一系列操作得到 chenQuery 并作为参数 $,赋值给 window.$
function() {
// 匹配 ID
let rquickExpr = /^(?:#([\w-]*))$/;
//jQuery 初始化
function chenQuery(selector) {return new chenQuery.fn.init(selector);
}
function getStyles(elem) {return elem.ownerDocument.defaultView.getComputedStyle(elem, null);
}
// 模仿 swing 动画效果
// 两头慢,中间快
function swing(p) {return 0.5 - Math.cos(p * Math.PI) / 2;
}
// 创建动画缓动对象 //
function Tween(value, prop, animation) {
this.elem=animation.elem;
this.prop=prop;
this.easing= "swing"; // 动画缓动算法
this.options=animation.options;
// 获取初始值
this.start=this.now = this.get();
// 动画最终值
this.end= value;
// 单位
this.unit="px"
}
Tween.prototype = {
// 获取元素的当前属性
get: function() {let computed = getStyles(this.elem);
let ret = computed.getPropertyValue(this.prop) || computed[this.prop];
return parseFloat(ret);
},
// 运行动画
run:function(percent){
let eased
// 根据缓动算法改变 percent
this.pos = eased = swing(percent);
// 获取具体的改变坐标值
this.now = (this.end - this.start) * eased + this.start;
// 最终改变坐标
this.elem.style[this.prop] = this.now + "px";
return this;
}
}
// 创建开始时间
function createFxNow() {setTimeout(function() {Animation.fxNow = undefined;});
return (Animation.fxNow = Date.now());
}
let inProgress
function schedule() {
//inProgress 是判断整个动画流程是否结束的标志
// 当 inProgress=null 时,整个动画结束
if (inProgress) {
// 走这边
// 使用 requestAnimationFrame 来完成动画
// 递归
window.requestAnimationFrame(schedule);
/* 执行动画帧 */
Animation.fx.tick();}
}
// 动画核心函数
function Animation(elem, options, optall,func,){
// 动画对象
let animation = {
elem:elem,
props:options,
originalOptions:optall,
options:optall,
// 动画开始时间
startTime:Animation.fxNow || createFxNow(),
// 存放每个属性的缓动对象,用于动画
tweens:[]}
// 生成属性对应的动画算法对象
for (let k in options) {
// tweens 保存每一个属性对应的缓动控制对象
animation.tweens.push(new Tween(options[k], k, animation) )
}
// 动画状态
let stopped;
// 动画的定时器调用包装器
// 单帧循环执行
let tick = function() {if (stopped) {return false;}
// 动画时间算法
let currentTime = Animation.fxNow || createFxNow,
// 动画运动时间递减
remaining = Math.max(0, animation.startTime + animation.options.duration - currentTime),
// 百分比
temp = remaining / animation.options.duration || 0,
percent = 1 - temp;
let index = 0,
length = animation.tweens.length;
// 执行动画改变
for (; index < length; index++) {
//percent 改变值
animation.tweens[index].run(percent);
}
// 当进度不到 100% 时,继续绘制动画帧
if (percent < 1 && length) {return remaining;}
// 当结束时通知单个动画结束
tick.complete()
return false
}
tick.elem = elem;
tick.anim = animation
// 这个是自定义的属性,也就是单个动画结束的标志
tick.complete = func
// 开始执行下个动画
Animation.fx.timer(tick)
}
// 用于 requestAnimationFrame 调用
Animation.timers =[]
Animation.fx = {
// 开始动画队列,不是帧队列
//Animation.tick()
timer: function(timer,) {Animation.timers.push(timer);
if (timer()) {
// 开始执行动画
Animation.fx.start();
// func()}
// else {// Animation.timers.pop();
// }
},
// 开始循环
start: function(func) {if ( inProgress) {return;}
// 动画开始即为运行中,加上锁
inProgress = true;
// 运行
schedule();
// func()},
// 停止循环
stop:function(){inProgress = null;},
// 动画帧循环的的检测
tick: function() {
var timer,
i = 0,
timers = Animation.timers;
Animation.fxNow = Date.now();
for (; i < timers.length; i++) {timer = timers[i];
if (!timer() && timers[i] === timer) {
// 如果完成了就删除这个动画
timers.splice(i--, 1);
}
}
if (!timers.length) {Animation.fx.stop();
}
Animation.fxNow = undefined;
}
}
// 假设是在数据缓存中存取队列
const Queue=[]
// 数据缓存
const dataPriv={get:function (type) {if(type==='queue') return Queue
},
}
const dequeue=function() {const Queue=dataPriv.get("queue")
let fn = Queue.shift()
// 当单个动画结束后,执行下个动画
const next = function() {dequeue();
}
if (fn === "inprogress") {fn = Queue.shift();
}
if (fn) {Queue.unshift( "inprogress");
/* 执行 doAnimation 方法,doAnimation(element, options,function() {firing = false;_fire();})*/
/*fn 的参数就是形参 func*/
/*func 方法是用来通知上个动画结束,下个动画运行的重要 function*/
//func 的作用是用来通知动画执行结束,并继续执行下一个动画
const func=function() {next();
}
fn(func);
}
}
// 省略 type
const queue=function(element, options, callback,) {
// 模仿从数据缓存中得到的队列, 直接写 Queue.push 也行
const Queue=dataPriv.get("queue")
// 向动画队列中添加 doAnimation 触发器
Queue.push(function(func) {
//doAnimation
callback(element, options, func);
});
// 如果没有动画在运行,运行动画
// 动画锁 inprogress
if(Queue[0]!=='inprogress'){dequeue()
}
}
/* 动画 */
const animation = function(element,options) {const doAnimation = function(element, options, func) {
// const width = options.width
/*=== 这里面定义了动画的算法,也就是 Animation 实现的地方 ===*/
// 默认动画时长 2s
// element.style.transitionDuration = '400ms';
// element.style.width = width + 'px';
/* 监听单个动画完结 */
//transitionend 事件在 CSS 完成过渡后触发
// element.addEventListener('transitionend', function() {// func()
// });
// 动画的默认属性
let optall={complete:function(){},
old:false,
duration: 400,
easing: undefined,
queue:"fx",
}
let anim=Animation(element, options, optall,func)
}
// 每调用一次 animation,就入一次队
return queue(element, options,doAnimation,);
}
// 为 chenQuery 的 fn 和 prototype 原型属性 赋 animate 属性
chenQuery.fn = chenQuery.prototype = {
// 也可以直接把 animation 里的代码放这里来,但这样就太长了,降低了可读性
animate: function(options) {animation(this.element, options);
// 注意返回的是 this, 也就是 $("#A"), 这样就能继续调用 animate 方法
// 也就是链式调用
return this;
}
}
// 为 chenQuery 的 fn 属性添加 init 方法
const init = chenQuery.fn.init = function(selector) {// ["#A", "A",groups: undefined,index: 0,input: "#A"]
const match = rquickExpr.exec(selector);
// 这边默认是只找 id 的元素
const element = document.getElementById(match[1])
//this 指 chenQuery.fn.init 方法
// 为该方法添加 element 属性
this.element = element;
// 返回 chenQuery.fn.init
return this;
}
// 挺绕的,再将 init 的原型等于 chenQuery.fn 方法
init.prototype = chenQuery.fn;
//chenQuery 本身是一个 function(){}
// chenQuery{
//init 能调用 fn,fn 能调用 init
// fn:{// animate:function(){},
// init:function(){},
// // init.prototype=fn
// },
// prototype:{// animate:function(){},
// }
// }
return chenQuery;
}());
const A = document.querySelector('#A');
// 在异步调用中,进行同步调用
// 动画是异步的
A.onclick = function() {// 就是连续调用 animation.add()
$('#A').animate({'width': '500'}).animate({'width': '300'}).animate({'width': '1000'});
};
</script>
</body>
</html>
运行结果:
解析:
(1)单个动画本身也有循环,也就是利用 requestAnimationFrame
循环动画帧,从而绘制动画
(2)当percent
<1 时,即动画运行时间小于总体时间,就不断运行动画帧;当percent
=1 时,表示动画结束,通知动画队列,运行下个动画,如此循环即可
(完)