move.js 源码 学习笔记
源码笔记:
1 /* move.js 2 * @author:flfwzgl https://github.com/flfwzgl 3 * @copyright: MIT license 4 * Sorrow.X --- 添加注释,注释纯属个人理解(源码有稍微改动,方便阅读) 5 * */ 6 7 ! function() { 8 9 var PI = Math.PI, 10 sin = Math.sin, 11 cos = Math.cos, 12 pow = Math.pow, 13 abs = Math.abs, 14 sqrt = Math.sqrt; 15 16 var request = window.requestAnimationFrame, 17 stopRequest = window.cancelAnimationFrame; 18 var _move, _stopMove; // 都是函数, 不支持requestAnimationFrame的浏览器就使用定时器 19 20 //初始化运动函数和停止函数 21 if (request) { 22 _move = function(fn, timer) { // fn: 匿名函数, timer: 不同的空对象 23 var step = function() { 24 if (!fn()) { // fn函数返回值为假值则调用requestAnimationFrame方法(true代表运动结束) 25 timer.id = request(step); 26 }; 27 }; 28 step(); // 函数调用 29 }; 30 } else { 31 _move = function(fn, timer) { 32 timer.id = setInterval(fn, 16); // 采用定时器, 时间间隔不能低于16 33 }; 34 }; 35 if (stopRequest) { 36 _stopMove = function(timer) { 37 stopRequest(timer.id); // 停止动画调用 38 }; 39 } else { 40 _stopMove = function(timer) { 41 clearInterval(timer.id); // 关闭定时器 42 }; 43 }; 44 45 var Move = function() {}; // Move构造函数 46 47 var curve = Move.prototype = { // Move原型 48 extend: function(obj) { 49 for (var k in obj) { 50 if (k in curve) { 51 console.warn('扩张的方法名' + k + ': 已经存在, 换个方法名吧!' ); 52 return; 53 }; 54 curve[k] = (function(moveType) { // 给Move原型添加 动画曲线 方法 55 return function() { 56 return _doMove.call(this, arguments, moveType); // 每个动画曲线方法实际调用_doMove函数 57 }; 58 })(obj[k]); 59 }; 60 } 61 }; 62 63 /***** 动画曲线 ******/ 64 curve.extend({ 65 //定义域和值域均为[0, 1], 传入自变量x返回对应值y 66 //先加速后减速 67 ease: function(x) { 68 // return -0.5*cos(PI * (2 - x)) + 0.5; 69 if (x <= 0.5) return 2 * x * x; 70 else if (x > 0.5) return -2 * x * x + 4 * x - 1; 71 }, 72 73 // 初速度为0 ,一直加速 74 easeIn: function(x) { 75 return x * x; 76 }, 77 78 //先慢慢加速1/3, 然后突然大提速, 最后减速 79 ease2: function(x) { 80 return x < 1 / 3 ? x * x : -2 * x * x + 4 * x - 1; 81 }, 82 83 //初速度较大, 一直减速, 缓冲动画 84 easeOut: function(x) { 85 return pow(x, 0.8); 86 }, 87 88 //碰撞动画 89 collision: function(x) { 90 var a, b; //a, b代表碰撞点的横坐标 91 for (var i = 1, m = 20; i < m; i++) { 92 a = 1 - (4 / 3) * pow(0.5, i - 1); 93 b = 1 - (4 / 3) * pow(0.5, i); 94 if (x >= a && x <= b) { 95 return pow(3 * (x - (a + b) / 2), 2) + 1 - pow(0.25, i - 1); 96 } 97 } 98 }, 99 100 //弹性动画 101 elastic: function(x) { 102 return -pow(1 / 12, x) * cos(PI * 2.5 * x * x) + 1; 103 }, 104 105 //匀速动画 106 linear: function(x) { 107 return x; 108 }, 109 110 //断断续续加速减速 111 wave: function(x) { 112 return (1 / 12) * sin(5 * PI * x) + x; 113 }, 114 115 //先向反方向移动一小段距离, 然后正方向移动, 并超过终点一小段, 然后回到终点 116 opposite: function(x) { 117 return (sqrt(2) / 2) * sin((3 * PI / 2) * (x - 0.5)) + 0.5; 118 }, 119 120 // 相反的三次贝塞尔 121 reverseEase: function (x) { 122 return 1 - Math.sqrt(1 - x * x); 123 } 124 }); 125 126 /** 127 * 开始动画函数 128 * arg: 用户要传的([0, 1000], 500, function(v){ ... }, fnEnd) 129 * moveType: 曲线动画函数 130 */ 131 function _doMove(arg, moveType) { 132 var r, // r => 过渡范围, 例如[0, 1000] (必须传, 且传数组) 133 d, // d => 过渡时间, ms, (可不传, 默认500) 134 fn, // fn => 每一帧的回调函数, 传入当前过渡值v (必须传) 135 fnEnd; // fnEnd => 动画结束时回调 (可不传) 136 137 // 严格限制传入参数, 且传入的参数可以没有顺序 138 for (var i = 0; i < 4; i++) { 139 if (typeof arg[i] === 'object' && !r) r = arg[i]; 140 else if (typeof arg[i] === 'number' && !d) d = arg[i]; 141 else if (typeof arg[i] === 'function' && !fn) fn = arg[i]; 142 else if (typeof arg[i] === 'function' && !fnEnd) fnEnd = arg[i]; 143 }; 144 145 if (!r instanceof Array || !fn) return; // 如果r不是数组或者fn不是函数(真值)就return掉 146 147 d = d || 500; // 过渡时间默认500ms 148 149 var from = +new Date, //起始时间 150 x = 0, 151 y, 152 a = r[0], // 过渡范围的起点 153 b = r[1]; // 过度范围的终点 154 155 var timer = 't' + Math.random(); // 随机数 156 157 var self = this; // 存一下Move的实例 158 159 //用于保存定时器ID的对象, requestAnimation递归调用必须传入对象(给实例添加timer属性值为{}) 160 this[timer] = {}; 161 162 // 优先使用requestAnimationFrame否则setInterval定时器 163 _move(function() { 164 x = (+new Date - from) / d; 165 166 if (x >= 1) { // 动画结束 167 fn(b); // 调用外部动画的回调函数且把过度范围的终点值作为参数传过去 168 if (fnEnd) fnEnd(); // 如果有动画结束回调函数就执行回调函数 169 return true; // 返回真值停止调用requestAnimationFrame方法 170 } else { // 动画进行中 171 y = moveType(x); // 调用动画曲线中的函数返回运动数字 172 fn(a + (b - a) * y); // 调用外部动画的回调函数传参为 a + (b - a) * y 173 }; 174 }, self[timer]); 175 176 return function() { 177 _stopMove(self[timer]); // 调用cancelAnimationFrame方法停止动画 178 return a + (b - a) * y; // 返回动画停止后的运动数字 179 }; 180 }; 181 182 // 抛出去 183 if (typeof module === 'object' && module.exports) { 184 module.exports = new Move; 185 } else { 186 if (window.move) { 187 try { 188 console.warn('move has been declared!'); 189 } catch (e) {}; 190 } else { 191 window.move = new Move; // 抛出去的是个Move实例 192 } 193 }; 194 }();
使用姿势:
var box = document.querySelector("#box"); // 扩展运动函数 move.extend({ fast: function(x) { return x * x * x; }, reverseEase: function (x) { // 相反的三次贝塞尔 return 1 - Math.sqrt(1 - x * x); } }) // 使用姿势 var stop = move.reverseEase([0, 500], 1000, function(v){ console.log(v); box.style.left = v + 'px'; }, function(){ console.log('动画完成'); }); setTimeout(function() { var val = stop(); //停止动画 console.log('停止:' + val); }, 500);
个人喜好,我改成了自己喜欢的源码,添加了随机取动画名字:
1 ; (function() { 2 3 var PI = Math.PI, 4 sin = Math.sin, 5 cos = Math.cos, 6 pow = Math.pow, 7 abs = Math.abs, 8 sqrt = Math.sqrt; 9 10 var request = window.requestAnimationFrame, 11 stopRequest = window.cancelAnimationFrame; 12 var _move, _stopMove; // 都是函数, 不支持requestAnimationFrame的浏览器就使用定时器 13 14 //初始化运动函数和停止函数 15 if (request) { 16 _move = function(fn, timer) { // fn: 匿名函数, timer: 不同的空对象 17 var step = function() { 18 if (!fn()) { // fn函数返回值为假值则调用requestAnimationFrame方法(true代表运动结束) 19 timer.id = request(step); 20 }; 21 }; 22 step(); // 函数调用 23 }; 24 } else { 25 _move = function(fn, timer) { 26 timer.id = setInterval(fn, 16); // 采用定时器, 时间间隔不能低于16 27 }; 28 }; 29 if (stopRequest) { 30 _stopMove = function(timer) { 31 stopRequest(timer.id); // 停止动画调用 32 }; 33 } else { 34 _stopMove = function(timer) { 35 clearInterval(timer.id); // 关闭定时器 36 }; 37 }; 38 39 var Move = function() { // Move构造函数 40 this.aCurve = []; // 曲线动画函数名集合 41 this.init(); 42 }; 43 44 45 var curve = Move.prototype = { // Move原型 46 // 初始化动画曲线 47 init: function() { 48 this.extends({ 49 //定义域和值域均为[0, 1], 传入自变量x返回对应值y 50 //先加速后减速 51 ease: function(x) { 52 // return -0.5*cos(PI * (2 - x)) + 0.5; 53 if (x <= 0.5) return 2 * x * x; 54 else if (x > 0.5) return -2 * x * x + 4 * x - 1; 55 }, 56 57 // 初速度为0 ,一直加速 58 easeIn: function(x) { 59 return x * x; 60 }, 61 62 //先慢慢加速1/3, 然后突然大提速, 最后减速 63 ease2: function(x) { 64 return x < 1 / 3 ? x * x : -2 * x * x + 4 * x - 1; 65 }, 66 67 //初速度较大, 一直减速, 缓冲动画 68 easeOut: function(x) { 69 return pow(x, 0.8); 70 }, 71 72 //碰撞动画 73 collision: function(x) { 74 var a, b; //a, b代表碰撞点的横坐标 75 for (var i = 1, m = 20; i < m; i++) { 76 a = 1 - (4 / 3) * pow(0.5, i - 1); 77 b = 1 - (4 / 3) * pow(0.5, i); 78 if (x >= a && x <= b) { 79 return pow(3 * (x - (a + b) / 2), 2) + 1 - pow(0.25, i - 1); 80 } 81 } 82 }, 83 84 //弹性动画 85 elastic: function(x) { 86 return -pow(1 / 12, x) * cos(PI * 2.5 * x * x) + 1; 87 }, 88 89 //匀速动画 90 linear: function(x) { 91 return x; 92 }, 93 94 //断断续续加速减速 95 wave: function(x) { 96 return (1 / 12) * sin(5 * PI * x) + x; 97 }, 98 99 //先向反方向移动一小段距离, 然后正方向移动, 并超过终点一小段, 然后回到终点 100 opposite: function(x) { 101 return (sqrt(2) / 2) * sin((3 * PI / 2) * (x - 0.5)) + 0.5; 102 }, 103 104 // 相反的三次贝塞尔 105 reverseEase: function (x) { 106 return 1 - Math.sqrt(1 - x * x); 107 } 108 }); 109 }, 110 111 // 随机选择一个动画方法名 112 getRd: function () { 113 var preItem = null; 114 115 return function () { 116 var arr = this.aCurve; 117 var index = Math.floor(Math.random() * arr.length), 118 item = arr[index], 119 result; 120 121 if (preItem != item) { 122 preItem = item; 123 result = item; 124 } else { 125 result = this.getRd(arr); 126 }; 127 128 return result; 129 }; 130 }(), 131 132 // 扩张曲线动画 133 extends: function(obj) { 134 for (var k in obj) { 135 if (k in curve) { 136 console.warn('扩张的方法名' + k + ': 已经存在, 换个方法名吧!' ); 137 return; 138 }; 139 this.aCurve.push(k); 140 curve[k] = (function(moveType) { // 给Move原型添加 动画曲线 方法 141 return function() { 142 return _doMove.call(this, arguments, moveType); // 每个动画曲线方法实际调用_doMove函数 143 }; 144 })(obj[k]); 145 }; 146 } 147 }; 148 149 150 /** 151 * 开始动画函数 152 * arg: 用户要传的([0, 1000], 500, function(v){ ... }, fnEnd) 153 * moveType: 曲线动画函数 154 */ 155 function _doMove(arg, moveType) { 156 var r, // r => 过渡范围, 例如[0, 1000] (必须传, 且传数组) 157 d, // d => 过渡时间, ms, (可不传, 默认500) 158 fn, // fn => 每一帧的回调函数, 传入当前过渡值v (必须传) 159 fnEnd; // fnEnd => 动画结束时回调 (可不传) 160 161 // 严格限制传入参数, 且传入的参数可以没有顺序 162 for (var i = 0; i < 4; i++) { 163 if (typeof arg[i] === 'object' && !r) r = arg[i]; 164 else if (typeof arg[i] === 'number' && !d) d = arg[i]; 165 else if (typeof arg[i] === 'function' && !fn) fn = arg[i]; 166 else if (typeof arg[i] === 'function' && !fnEnd) fnEnd = arg[i]; 167 }; 168 169 if (!r instanceof Array || !fn) return; // 如果r不是数组或者fn不是函数(真值)就return掉 170 171 d = d || 500; // 过渡时间默认500ms 172 173 var from = +new Date, //起始时间 174 x = 0, 175 y, 176 a = r[0], // 过渡范围的起点 177 b = r[1]; // 过度范围的终点 178 179 var timer = 't' + Math.random(); // 随机数 180 181 var self = this; // 存一下Move的实例 182 183 //用于保存定时器ID的对象, requestAnimation递归调用必须传入对象(给实例添加timer属性值为{}) 184 this[timer] = {}; 185 186 // 优先使用requestAnimationFrame否则setInterval定时器 187 _move(function() { 188 x = (+new Date - from) / d; 189 190 if (x >= 1) { // 动画结束 191 fn(b); // 调用外部动画的回调函数且把过度范围的终点值作为参数传过去 192 if (fnEnd) fnEnd(); // 如果有动画结束回调函数就执行回调函数 193 return true; // 返回真值停止调用requestAnimationFrame方法 194 } else { // 动画进行中 195 y = moveType(x); // 调用动画曲线中的函数返回运动数字 196 fn(a + (b - a) * y); // 调用外部动画的回调函数传参为 a + (b - a) * y 197 }; 198 }, self[timer]); 199 200 return function() { 201 _stopMove(self[timer]); // 调用cancelAnimationFrame方法停止动画 202 return a + (b - a) * y; // 返回动画停止后的运动数字 203 }; 204 }; 205 206 // 抛出去 207 if (typeof module === 'object' && module.exports) { 208 module.exports = Move; 209 } else { 210 if (window.Move) { 211 try { 212 console.warn('Move has been declared!'); 213 } catch (e) {}; 214 } else { 215 window.Move = Move; // Move构造函数抛出去 216 } 217 }; 218 })();
使用姿势:
var move = new Move(); move.extends({ // 自己自定义扩展 fast: function(x) { return x * x * x; }, reverseEase: function (x) { // 相反的三次贝塞尔 return 1 - Math.sqrt(1 - x * x); } }) document.querySelector('#btn').addEventListener('click', function() { // 使用姿势 var rd = move.getRd(); var stop = move[rd]([0, 500], 1000, function(v){ console.log(v); box.style.left = v + 'px'; }, function(){ console.log('动画完成'); }); setTimeout(function() { var val = stop(); //停止动画 console.log('停止:' + val); }, 500); }, false);
ps:
很喜欢的一个数字运动小型库,便于扩张各种运动。配合dnt的transform.js,真的很不错。
很多ios和安卓的前端动画都能轻松写出来。
比jq提供的动画更加强大和灵活。
我只是个搬运工,喜欢的库自然会贴出原作者地址。
看原作者怎么说明的:https://github.com/flfwzgl/move
开心的做一个无忧无虑的码农,争取每天进步一点。