缓动动画封装详细讲解
---来自一个前端妹子
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 7 8 <!-- 9 先快后慢 10 一开始的步子要迈的大,后面的步子要小 11 12 步子要越来越小 13 14 被除数 / 10 ,被除数的值越大,结果就越大,被除数越小。值就越小 15 16 所以我们要找到一个越来越小的数,再去除以10,把结果作为步长,就能起到步子越来越小 17 18 到底什么是越来越小的值?到目标的距离,是越来越小的 19 20 21 当前距离 目标 基数(除数) 22 0 400 10 400 - 0 = 400 / 10 = 40 这步走40 23 40 400 10 400 - 40 = 360 / 10 = 36 这步走36 24 25 76 400 10 400 - 76 = 324 / 10 = 32.4 向上取整变成了33 这步走33 26 109 400 10 400 - 109 = 291 / 10 = 29.1 取整变 30 27 ……………………………………………………………………………… 28 390 400 10 400 - 390 = 10 / 10 = 1 这步走1 29 391 400 10 400 - 391 = 9 / 10 = 0.9 向上取整走1 30 越到后面越慢,而且到最后那几步就是1个像素1个像素的走,能走到,而且绝对不会超 31 32 399 400 10 400 - 399 = 1 / 10 = 0.1 向上取整变1 33 34 --> 35 36 37 <style> 38 39 #box { 40 width: 100px; 41 height: 100px; 42 background-color: red; 43 position: absolute; 44 } 45 46 </style> 47 </head> 48 <body> 49 50 <input type="button" value="移动到400" id="move400"/> 51 <input type="button" value="移动到800" id="move800"/> 52 53 <div id="box"></div> 54 55 </body> 56 </html> 57 58 <script> 59 60 //找到div 61 var box = document.getElementById("box"); 62 63 //移动到400的点击事件 64 document.getElementById("move400").onclick = function () { 65 66 animateSlow(400); 67 } 68 69 70 document.getElementById("move800").onclick = function () { 71 72 animateSlow(800); 73 } 74 75 76 var timerID; 77 function animateSlow(target) { 78 79 clearInterval(timerID); 80 81 timerID = setInterval(function () { 82 83 //获取到当前的位置 84 var current = box.offsetLeft; 85 86 //用目标 - 当前位置 得到距离 ,再用距离除以10,再向上取整得到步长 87 var step = Math.ceil((target - current) / 10); 88 89 //因为绝对不会超,所以算出步长后直接走这一步就行了 90 current += step; 91 92 //设置给要移动的元素 93 box.style.left = current + "px"; 94 95 console.log("当前位置:" + current + "------步长:" + step); 96 97 //如果到了目的地,就停止计时器 98 if (current == target) { 99 100 clearInterval(timerID); 101 } 102 103 }, 50 104 ) 105 } 106 107 108 </script>
先快后慢
一开始的步子要迈的大,后面的步子要小
步子要越来越小
被除数 / 10 ,被除数的值越大,结果就越大,被除数越小。值就越小
所以我们要找到一个越来越小的数,再去除以10,把结果作为步长,就能起到步子越来越小
到底什么是越来越小的值?到目标的距离,是越来越小的
当前距离 目标 基数(除数)
0 400 10 400 - 0 = 400 / 10 = 40 这步走40
40 400 10 400 - 40 = 360 / 10 = 36 这步走36
76 400 10 400 - 76 = 324 / 10 = 32.4 向上取整变成了33 这步走33
109 400 10 400 - 109 = 291 / 10 = 29.1 取整变 30
………………………………………………………………………………
390 400 10 400 - 390 = 10 / 10 = 1 这步走1
391 400 10 400 - 391 = 9 / 10 = 0.9 向上取整走1
越到后面越慢,而且到最后那几步就是1个像素1个像素的走,能走到,而且绝对不会超
399 400 10 400 - 399 = 1 / 10 = 0.1 向上取整变1
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 7 8 <style> 9 div { 10 width: 100px; 11 height: 100px; 12 background-color: red; 13 position: absolute; 14 } 15 16 #blue { 17 background-color: blue; 18 top: 150px; 19 } 20 21 </style> 22 23 </head> 24 <body> 25 26 <input type="button" value="移动到红" id="moveRed"/> 27 <input type="button" value="移动到蓝" id="moveBlue"/> 28 29 <div id="red"></div> 30 <div id="blue"></div> 31 32 </body> 33 </html> 34 35 <script> 36 var red = document.getElementById("red"); 37 var blue = document.getElementById("blue"); 38 39 document.getElementById("moveRed").onclick = function () { 40 animateSlow(red, 400); 41 }; 42 43 document.getElementById("moveBlue").onclick = function () { 44 45 animateSlow(blue, 400); 46 }; 47 48 49 50 /* 51 52 第二重封装: 53 解决了可以移动不同元素的问题 54 以及移动第二个会停止第一个元素的问题(自己的只能自己停) 55 56 */ 57 58 function animateSlow(obj, target) { 59 60 clearInterval(obj.timerID); 61 62 obj.timerID = setInterval(function () { 63 64 //获取到当前的位置 65 var current = obj.offsetLeft; 66 67 //用目标 - 当前位置 得到距离 ,再用距离除以10,再向上取整得到步长 68 var step = Math.ceil((target - current) / 10); 69 70 //因为绝对不会超,所以算出步长后直接走这一步就行了 71 current += step; 72 73 //设置给要移动的元素 74 obj.style.left = current + "px"; 75 76 console.log("当前位置:" + current + "------步长:" + step); 77 78 //如果到了目的地,就停止计时器 79 if (current == target) { 80 81 clearInterval(obj.timerID); 82 } 83 84 }, 50 85 ) 86 } 87 </script>
第二重封装: 解决了可以移动不同元素的问题 以及移动第二个会停止第一个元素的问题(自己的只能自己停)
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 7 <!-- 8 当前位置 目标位置 基数 9 800 400 10 400 -800 = -400 /10 = -40 10 760 400 10 400 - 760 = -360 / 10 = -36 11 ..................... 12 13 409 400 10 400 - 409 = -9 / 10 = -0.9 向下取整变-1 14 408 400 15 407 16 406 17 400 18 19 如果是往右走 20 21 791 800 10 800-791 = 9 /10 = 0.9 要向上取整才正确 22 23 我们发现有的时候要向上,有的时候要向下 24 25 如果最终算出的结果为负数,一定要向下,如果算出的结果为正数,要向上 26 --> 27 28 29 <style> 30 div { 31 width: 100px; 32 height: 100px; 33 background-color: red; 34 position: absolute; 35 } 36 37 38 </style> 39 40 </head> 41 <body> 42 43 <input type="button" value="移动到400" id="move400"/> 44 <input type="button" value="移动到800" id="move800"/> 45 46 <div id="red"></div> 47 48 </body> 49 </html> 50 51 <script> 52 var red = document.getElementById("red"); 53 var blue = document.getElementById("blue"); 54 55 document.getElementById("move400").onclick = function () { 56 57 animateSlow(red, 400); 58 }; 59 60 document.getElementById("move800").onclick = function () { 61 62 animateSlow(red, 800); 63 }; 64 65 66 /* 67 68 第三重封装: 69 解决了既可以往右走,也可以往走的问题 70 71 发现:只要看算出的结果,如果是正就要向上取整,如果是负就要向下取整 72 73 74 75 */ 76 77 function animateSlow(obj, target) { 78 79 clearInterval(obj.timerID); 80 81 obj.timerID = setInterval(function () { 82 83 //获取到当前的位置 84 var current = obj.offsetLeft; 85 86 //先用目标-当前位置,再除以基数得到结果 87 var result = (target - current) / 10; 88 89 //如果结果是正就用向上取整,如果是负就用 向下取整 90 var step = result > 0 ? Math.ceil(result) : Math.floor(result); 91 92 //因为绝对不会超,所以算出步长后直接走这一步就行了 93 current += step; 94 95 //设置给要移动的元素 96 obj.style.left = current + "px"; 97 98 console.log("当前位置:" + current + "------步长:" + step); 99 100 //如果到了目的地,就停止计时器 101 if (current == target) { 102 103 clearInterval(obj.timerID); 104 } 105 106 }, 50 107 ) 108 } 109 </script>
当前位置 目标位置 基数
800 400 10 400 -800 = -400 /10 = -40
760 400 10 400 - 760 = -360 / 10 = -36
.....................
409 400 10 400 - 409 = -9 / 10 = -0.9 向下取整变-1
408 400
407
406
400
如果是往右走
791 800 10 800-791 = 9 /10 = 0.9 要向上取整才正确
我们发现有的时候要向上,有的时候要向下
如果最终算出的结果为负数,一定要向下,如果算出的结果为正数,要向上
第三重封装:
解决了既可以往右走,也可以往走的问题
发现:只要看算出的结果,如果是正就要向上取整,如果是负就要向下取整
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 7 <style> 8 #box { 9 width: 100px; 10 height: 100px; 11 background-color: red; 12 position: absolute; 13 } 14 15 /* 16 第四重封装: 17 解决了可以改动上下移动和左右移动以及宽高变化的问题 18 19 加一个参数:参数传入一个你需要改动的属性 20 21 然后再通过我们封装的获取样式的方法,获取到当前的值 22 算法不变 23 24 只不过赋值时,就根据你传入的参数来赋值到对应的属性 25 26 */ 27 </style> 28 </head> 29 <body> 30 31 <input type="button" value="向下走500" id="down500"/> 32 <input type="button" value="向右走500" id="right500"/> 33 <input type="button" value="变宽到400" id="width400"/> 34 <input type="button" value="变高到400" id="height400"/> 35 36 <div id="box"></div> 37 38 </body> 39 </html> 40 41 42 <script src="common.js"></script> 43 <script> 44 //找到div 45 var box = document.getElementById("box"); 46 47 document.getElementById("down500").onclick = function () { 48 49 animateSlow(box, 500, "top"); 50 }; 51 52 53 document.getElementById("right500").onclick = function () { 54 55 animateSlow(box, 500, "left"); 56 }; 57 58 document.getElementById("width400").onclick = function () { 59 60 animateSlow(box, 400, "width"); 61 }; 62 63 document.getElementById("height400").onclick = function () { 64 65 animateSlow(box, 400, "height"); 66 }; 67 68 69 function animateSlow(obj, target, attr) {//可能传left,也可能传top 70 71 clearInterval(obj.timerID); 72 73 obj.timerID = setInterval(function () { 74 75 //获取到当前的位置 76 //传入的是什么属性就获取什么属性的样式,因为得到的是带单位的字符串,所以要用parseInt做一个转换 77 var current = parseInt(getStyle(obj, attr)); 78 79 //先用目标-当前位置,再除以基数得到结果 80 var result = (target - current) / 10; 81 82 //如果结果是正就用向上取整,如果是负就用 向下取整 83 var step = result > 0 ? Math.ceil(result) : Math.floor(result); 84 85 //因为绝对不会超,所以算出步长后直接走这一步就行了 86 current += step; 87 88 //设置给要移动的元素 89 obj.style[attr] = current + "px"; 90 91 console.log("当前位置:" + current + "------步长:" + step); 92 93 //如果到了目的地,就停止计时器 94 if (current == target) { 95 96 clearInterval(obj.timerID); 97 } 98 99 }, 50 100 ) 101 } 102 </script>
第四重封装:
解决了可以改动上下移动和左右移动以及宽高变化的问题
加一个参数:参数传入一个你需要改动的属性
然后再通过我们封装的获取样式的方法,获取到当前的值
算法不变
只不过赋值时,就根据你传入的参数来赋值到对应的属性
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 7 <style> 8 #box { 9 width: 100px; 10 height: 100px; 11 background-color: red; 12 position: absolute; 13 } 14 15 /* 16 第五重封装: 17 解决了可以同时改多个属性的问题 18 19 1.把函数的参数精简为2个,1个为接受要改的元素,第二个就是接收对象 20 对象里面放你要改的属性(对象的属性)和它对应的目标(属性的值) 21 22 2.在计时器里遍历接收对象的那个参数,取到key,key就是我们要取到的样式的属性,以及要改的属性 23 算法不变,只是取属性时用key来取了 24 25 3.不能哪一个到了目标就停止计时器,而应该是全部到了才停止计时器 26 用一个标记来标记,只要有一个没到,就改成false,循环外面判断是否还是为true,还是为true就代表都到了,都到了就停止计时器 27 28 */ 29 </style> 30 </head> 31 <body> 32 33 <input type="button" value="同时向右走到500向下走到400" id="btn"/> 34 35 36 <div id="box"></div> 37 38 </body> 39 </html> 40 41 42 <script src="common.js"></script> 43 <script> 44 45 46 //找到div 47 var box = document.getElementById("box"); 48 49 document.getElementById("btn").onclick = function () { 50 51 /* 52 我们现在需要同时改多个属性,而且多个属性可能有不同的不表 53 那么如果用以前的那种加参数方式不行,因为假如说要改3个属性,就需要6个,到时再临时加参数肯定不行 54 我想用一个参数,既可以让你传入一个属性和一个目标 55 也可以传入多个属性加多个目标 56 */ 57 58 var att = { 59 60 left: 500, 61 top: 400, 62 width: 400, 63 height: 400, 64 } 65 66 animateSlow(box, att); 67 }; 68 69 70 function animateSlow(obj, attrs) {//可能传left,也可能传top 71 72 clearInterval(obj.timerID); 73 74 obj.timerID = setInterval(function () { 75 76 //我先默认认为所有的都到了目的地(标记为true),在循环里,我判断一下,只要有一个不到,我就把标记改为false 77 78 //默认先认为都到了 79 var flag = true; 80 81 //把所有的属性名取出来 82 for (var key in attrs) { // left: 500 top: 400 83 84 //获取到当前的位置 85 //传入的是什么属性就获取什么属性的样式,因为得到的是带单位的字符串,所以要用parseInt做一个转换 86 var current = parseInt(getStyle(obj, key)); 87 88 //先用目标-当前位置,再除以基数得到结果 89 //注意:此时目标是它的key所对应的值 90 var result = (attrs[key] - current) / 10; 91 92 //如果结果是正就用向上取整,如果是负就用 向下取整 93 var step = result > 0 ? Math.ceil(result) : Math.floor(result); 94 95 //因为绝对不会超,所以算出步长后直接走这一步就行了 96 current += step; 97 98 //设置给要移动的元素 99 obj.style[key] = current + "px"; 100 101 console.log("当前位置:" + current + "------步长:" + step); 102 103 //只要有一个没到,就不要停计时器 104 if (current != attrs[key]) { 105 106 flag = false; 107 } 108 } 109 110 //如果这个值还是为true,就代表所有的属性都到了目的地 111 if (flag) { 112 113 clearInterval(obj.timerID); 114 } 115 116 }, 50) 117 } 118 </script>
第五重封装:
我们现在需要同时改多个属性,而且多个属性可能有不同的不表
那么如果用以前的那种加参数方式不行,因为假如说要改3个属性,就需要6个,到时再临时加参数肯定不行
我想用一个参数,既可以让你传入一个属性和一个目标
也可以传入多个属性加多个目标
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 7 8 <!-- 9 这一重解决了: 10 1.透明度不可以改的问题(解决为可以改) 11 1.1 因为透明度特殊,所以单独在forin里用一个if判断,如果key等于opacity,那么就走opacity的代码,else就走以前写的代码 12 1.2 在opacity里,代码跟以前的代码几乎一样,只改动以下几个部分 13 1.2.1 把parseInt改成parseFloat ,再取到值以后给当前位置和目标位置先乘以100 14 1.2.2 在赋值到元素的opacity之前,先除以100 15 1.2.3 在赋值时去掉px 16 17 18 2.增加动画完成后可以执行你想做的事的代码(回调函数) 19 2.1 先增加一个参数,用来接收用户传入的函数 20 2.2 在清除计时器的代码后面,先判断是不是函数,如果是,就调用 21 22 --> 23 24 <style> 25 #box { 26 width: 100px; 27 height: 100px; 28 background-color: red; 29 position: absolute; 30 } 31 32 </style> 33 </head> 34 <body> 35 36 <input type="button" value="同时向右走到500向下走到400" id="btn"/> 37 38 39 <div id="box"></div> 40 41 </body> 42 </html> 43 44 45 <script src="common.js"></script> 46 <script> 47 48 49 //找到div 50 var box = document.getElementById("box"); 51 52 document.getElementById("btn").onclick = function () { 53 54 /* 55 我们现在需要同时改多个属性,而且多个属性可能有不同的不表 56 那么如果用以前的那种加参数方式不行,因为假如说要改3个属性,就需要6个,到时再临时加参数肯定不行 57 我想用一个参数,既可以让你传入一个属性和一个目标 58 也可以传入多个属性加多个目标 59 */ 60 61 var att = { 62 63 opacity: 0, 64 left: 500, 65 top: 400, 66 width: 300, 67 height: 300 68 } 69 70 animateSlow(box, att, function () { 71 72 var attrs = { 73 74 top: 31, 75 left: 1000, 76 width: 100, 77 height: 100, 78 opacity: 1 79 } 80 animateSlow(box, attrs); 81 }); 82 }; 83 84 85 function animateSlow(obj, attrs, fn) {//可能传left,也可能传top 86 87 clearInterval(obj.timerID); 88 89 obj.timerID = setInterval(function () { 90 91 //我先默认认为所有的都到了目的地(标记为true),在循环里,我判断一下,只要有一个不到,我就把标记改为false 92 93 //默认先认为都到了 94 var flag = true; 95 96 //把所有的属性名取出来 97 for (var key in attrs) { // left: 500 top: 400 98 99 if (key == "opacity") { 100 101 //获取到当前的位置 102 //传入的是什么属性就获取什么属性的样式,因为得到的是带单位的字符串,所以要用parseInt做一个转换 103 var current = parseFloat(getStyle(obj, key)) * 100; 104 105 //它的值太小了 106 //先用目标-当前位置,再除以基数得到结果 107 //注意:此时目标是它的key所对应的值 108 var result = (attrs[key] * 100 - current) / 10; 109 110 //如果结果是正就用向上取整,如果是负就用 向下取整 111 var step = result > 0 ? Math.ceil(result) : Math.floor(result); 112 113 //因为绝对不会超,所以算出步长后直接走这一步就行了 114 current += step; 115 116 //之前你是先乘以100做的运算,所以后面应该把再除以100 117 current /= 100; 118 //设置给要移动的元素 119 obj.style[key] = current; 120 121 console.log("当前位置:" + current + "------步长:" + step); 122 123 //只要有一个没到,就不要停计时器 124 if (current != attrs[key]) { 125 126 flag = false; 127 } 128 129 } else { 130 131 //获取到当前的位置 132 //传入的是什么属性就获取什么属性的样式,因为得到的是带单位的字符串,所以要用parseInt做一个转换 133 var current = parseInt(getStyle(obj, key)); 134 135 //先用目标-当前位置,再除以基数得到结果 136 //注意:此时目标是它的key所对应的值 137 var result = (attrs[key] - current) / 10; 138 139 //如果结果是正就用向上取整,如果是负就用 向下取整 140 var step = result > 0 ? Math.ceil(result) : Math.floor(result); 141 142 //因为绝对不会超,所以算出步长后直接走这一步就行了 143 current += step; 144 145 //设置给要移动的元素 146 obj.style[key] = current + "px"; 147 148 console.log("当前位置:" + current + "------步长:" + step); 149 150 //只要有一个没到,就不要停计时器 151 if (current != attrs[key]) { 152 153 flag = false; 154 } 155 } 156 } 157 158 //如果这个值还是为true,就代表所有的属性都到了目的地 159 if (flag) { 160 161 clearInterval(obj.timerID); 162 163 //这里的代码不要写死,就让别人把要结束时执行的代码传进来 164 //你传的是什么代码,我执行的就是什么代码 165 if (fn instanceof Function) { 166 fn(); 167 } 168 169 } 170 171 }, 50) 172 } 173 </script>
这一重解决了:
1.透明度不可以改的问题(解决为可以改)
1.1 因为透明度特殊,所以单独在forin里用一个if判断,如果key等于opacity,那么就走opacity的代码,else就走以前写的代码
1.2 在opacity里,代码跟以前的代码几乎一样,只改动以下几个部分
1.2.1 把parseInt改成parseFloat ,再取到值以后给当前位置和目标位置先乘以100
1.2.2 在赋值到元素的opacity之前,先除以100
1.2.3 在赋值时去掉px
2.增加动画完成后可以执行你想做的事的代码(回调函数)
2.1 先增加一个参数,用来接收用户传入的函数
2.2 在清除计时器的代码后面,先判断是不是函数,如果是,就调用