前言:
在上篇的基础上,接入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 时,表示动画结束,通知动画队列,运行下个动画,如此循环即可
(完)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。