缓动动画
三个函数
缓慢动画里,我们要用到三个函数,这里先列出来:
-
Math.ceil() 向上取整
-
Math.floor() 向下取整
-
Math.round(); 四舍五入
缓动动画的原理
缓动动画的原理就是:在移动的过程中,步长越来越小。
设置步长为**:目标位置和盒子当前位置的十分之一**。用公式表达,即:
1
| 盒子位置 = 盒子本身位置 + (目标位置 - 盒子本身位置)/ 10;
|
代码举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> div { width: 100px; height: 100px; background-color: pink; position: absolute; } </style> </head> <body> <button>运动到left = 400px</button> <div></div>
<script>
var btn = document.getElementsByTagName("button")[0]; var div = document.getElementsByTagName("div")[0];
btn.onclick = function () { setInterval(function () { div.style.left = div.offsetLeft + (400 - div.offsetLeft) / 10 + "px"; }, 30); }
</script> </body> </html>
|
效果:
缓慢动画的封装(解决四舍五入的问题)
我们发现一个问题,上图中的盒子最终并没有到达400px的位置,而是只到了396.04px就停住了:
原因是:JS在取整的运算时,进行了四舍五入。
我们把打印396.04px这个left值打印出来看看:
我么发现,通过div.style.left
获取的值是精确的,通过div.offsetLeft
获取的left值会进行四舍五入。
此时,我们就要用到取整的函数了。
通过对缓动动画进行封装,完整版的代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> div { width: 100px; height: 100px; background-color: pink; position: absolute; left: 0; } </style> </head> <body> <button>运动到200</button> <button>运动到400</button> <div></div>
<script>
var btn = document.getElementsByTagName("button"); var div = document.getElementsByTagName("div")[0];
btn[0].onclick = function () { animate(div, 200); }
btn[1].onclick = function () { animate(div, 400); }
function animate(ele, target) { clearInterval(ele.timer); ele.timer = setInterval(function () { var step = (target - ele.offsetLeft) / 10; step = step > 0 ? Math.ceil(step) : Math.floor(step); ele.style.left = ele.offsetLeft + step + "px"; console.log(step); console.log("smyhvae"); if (Math.abs(target - ele.offsetLeft) <= Math.abs(step)) { ele.style.left = target + "px"; clearInterval(ele.timer); } }, 30); }
</script> </body> </html>
|
实现效果:
当我们用鼠标滚轮,滚动网页的时候,会触发window.onscroll()方法。效果如下:(注意看控制台的打印结果)
获取盒子的宽高。调用者为节点元素。不包括 border和margin。如下:
scrollHeight有一个特点:如果文字超出了盒子,高度为内容的高(包括超出的内容);不超出,则是盒子本身高度。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> div { width: 100px; height: 100px; padding: 10px; margin: 3px; border: 8px solid red; } </style> </head> <body>
<div class="box"> 静,能寒窗苦守;动,能点石成金。 静,能寒窗苦守;动,能点石成金。 静,能寒窗苦守;动,能点石成金。 静,能寒窗苦守;动,能点石成金。 静,能寒窗苦守;动,能点石成金。 静,能寒窗苦守;动,能点石成金。 </div> <script>
var div = document.getElementsByTagName("div")[0];
console.log(div.scrollWidth); console.log(div.scrollHeight);
</script> </body> </html>
|
打印结果:
网页被卷去的头部和左边的部分。
比如说,一个网页往上滚动的时候,上面的部分自然被浏览器遮挡了,遮挡的高度就是scrollTop。
scrollTop 这个属性的写法要注意兼容性,如下。
(1)如果文档没有 DTD 声明,写法为:
在没有 DTD 声明的情况下,如果不是这种写法,chrome浏览器认不出来。
(2)如果文档有 DTD 声明,写法为:
1
| document.documentElement.scrollTop
|
在有 DTD 声明的情况下,如果不是这种写法,IE678认不出来。
综合上面这两个,就诞生了一种兼容性的写法:
1 2 3
| document.body.scrollTop || document.documentElement.scrollTop
document.body.scrollTop + document.documentElement.scrollTop
|
另外还有一种兼容性的写法:window.pageYOffset
和 window.pageXOffset
。这种写法无视DTD的声明。这种写法支持的浏览器版本是:火狐/谷歌/ie9+。
综合上面的几种写法,为了兼容,不管有没有DTD,最终版的兼容性写法:
1
| window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop;
|
判断是否已经 DTD声明
方法如下:
1 2
| document.compatMode === "CSS1Compat" document.compatMode === "BackCompat"
|
将 scrollTop 和 scrollLeft封装为一个方法,名叫scroll(),返回值为 json。json里的键为 top 和 left。以后就直接调用json.top 和json.left就好。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> body { height: 5000px; width: 5000px; } </style> </head> <body>
<script>
window.onscroll = function () {
console.log(scroll().top); console.log(scroll().left); }
function scroll() { return { "top": window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop, "left": window.pageXOffset || document.body.scrollLeft || document.documentElement.scrollLeft } } </script> </body> </html>
|
上方代码中,函数定义的那部分就是要封装的代码。另外还有一种较为复杂的封装方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function scroll() { if(window.pageYOffset !== undefined) { return { left: window.pageXOffset, top: window.pageYOffset } } else if(document.compatMode === "CSS1Compat") { return { left: document.documentElement.scrollLeft, top: document.documentElement.scrollTop } } return { left: document.body.scrollLeft, top: document.body.scrollTop } }
|
获取 html 文档的方法
获取title、body、head、html标签的方法如下:
document.documentElement
表示文档的html标签。也就是说,基本结构当中的 html 标签
而是通过document.documentElement
访问的,并不是通过 document.html 去访问的。
完整版代码实现:
(1)index.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> * { margin: 0; padding: 0 }
img { vertical-align: top; }
.main { margin: 0 auto; width: 1000px; margin-top: 10px;
}
#Q-nav1 { overflow: hidden; }
.fixed { position: fixed; top: 0; left: 0; } </style>
<script src="tools.js"></script> <script> window.onload = function () {
var topDiv = document.getElementById("top"); var height = topDiv.offsetHeight; var middle = document.getElementById("Q-nav1"); var main = document.getElementById("main");
window.onscroll = function () { if (scroll().top > height) { middle.className += " fixed"; main.style.paddingTop = middle.offsetHeight + "px"; } else { middle.className = ""; main.style.paddingTop = 0; } }
} </script> </head> <body> <div class="top" id="top"> <img src="images/top.png" alt=""/> </div> <div id="Q-nav1"> <img src="images/nav.png" alt=""/> </div> <div class="main" id="main"> <img src="images/main.png" alt=""/> </div> </body> </html>
|
上方代码中,有一个技巧:
1
| main.style.paddingTop = middle.offsetHeight + "px";
|
仔细看注释就好。
(2)tools.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
function scroll() { if (window.pageYOffset !== undefined) { return { left: window.pageXOffset, top: window.pageYOffset } } else if (document.compatMode === "CSS1Compat") { return { left: document.documentElement.scrollLeft, top: document.documentElement.scrollTop } } return { left: document.body.scrollLeft, top: document.body.scrollTop } }
|
实现效果:
工程文件:
- 2018-02-03-scrollTop固定导航栏.rar
(1)index.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
| <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> img { position: fixed; bottom: 100px; right: 50px; cursor: pointer; display: none; border: 1px solid #000; }
div { width: 1210px; margin: 100px auto; text-align: center; font: 500 26px/35px "simsun"; color: red; } </style> <script src="tools.js"></script> <script> window.onload = function () { var img = document.getElementsByTagName("img")[0]; window.onscroll = function () { if (scroll().top > 1000) { img.style.display = "block"; } else { img.style.display = "none"; } leader = scroll().top; } var timer = null; var target = 0; var leader = 0; img.onclick = function () { clearInterval(timer); timer = setInterval(function () { var step = (target - leader) / 10; step = step > 0 ? Math.ceil(step) : Math.floor(step); leader = leader + step; window.scrollTo(0, leader); if (leader === 0) { clearInterval(timer); } }, 25); } } </script> </head> <body> <img src="images/Top.jpg"/> <div> 我是最顶端.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br> 生命壹号,永不止步.....<br>
</div> </body> </html>
|
(2)tools.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
function scroll() { if (window.pageYOffset != null) { return { left: window.pageXOffset, top: window.pageYOffset } } else if (document.compatMode === "CSS1Compat") { return { left: document.documentElement.scrollLeft, top: document.documentElement.scrollTop } } return { left: document.body.scrollLeft, top: document.body.scrollTop } }
|
实现效果:
小火箭的图片资源:
缓动框架封装
1、缓动框架封装:同时设置多个属性
这里我们通过window.getComputedStyle
的方式获取属性值。
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> div { position: absolute; top: 40px; left: 10px; width: 100px; height: 100px; background-color: pink; } </style> </head> <body>
<button>运动到json中设置的位置</button> <div></div>
<script>
var btnArr = document.getElementsByTagName("button"); var div = document.getElementsByTagName("div")[0];
btnArr[0].onclick = function () { var json = {"left": 100, "top": 200, "width": 300, "height": 300}; animate(div, json); }
function animate(ele, json) { clearInterval(ele.timer); ele.timer = setInterval(function () { for (var key in json) { var current = parseInt(getStyle(ele, key)) || 0; var step = (json[key] - current) / 10; step = step > 0 ? Math.ceil(step) : Math.floor(step); current = current + step; ele.style[key] = current + "px"; console.log(1);
} }, 25); }
function getStyle(ele, attr) { if (window.getComputedStyle) { return window.getComputedStyle(ele, null)[attr]; } return ele.currentStyle[attr]; } </script> </body> </html>
|
实现效果:
2、上方的代码改进:清除定时器
上方的代码中,我们还需做一下清除定时器的判断:只有所有的参数都到达指定位置了,我们就清除定时器。
完整版代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> div { position: absolute; top: 40px; left: 10px; width: 100px; height: 100px; background-color: pink; } </style> </head> <body>
<button>运动到json中设置的位置</button> <div></div>
<script>
var btnArr = document.getElementsByTagName("button"); var div = document.getElementsByTagName("div")[0];
btnArr[0].onclick = function () { var json = {"left": 100, "top": 200, "width": 300, "height": 300}; animate(div, json); }
function animate(ele, json) { clearInterval(ele.timer); ele.timer = setInterval(function () { var bool = true;
for (var key in json) { var current = parseInt(getStyle(ele, key)) || 0; var step = (json[key] - current) / 10; step = step > 0 ? Math.ceil(step) : Math.floor(step); current = current + step; ele.style[key] = current + "px"; if (json[key] !== current) { bool = false; } }
console.log(1); if (bool) { clearInterval(ele.timer); } }, 25); }
function getStyle(ele, attr) { if (window.getComputedStyle) { return window.getComputedStyle(ele, null)[attr]; } return ele.currentStyle[attr]; }
</script> </body> </html>
|
运行效果同上。
### 3、进一步深入:如果有要同时执行讴多个动画时,就要用到回调函数(重要):
上面的代码中,我们要做的动画是:
1 2 3 4
| btnArr[0].onclick = function () { var json = {"left": 100, "top": 200, "width": 300, "height": 300}; animate(div, json); }
|
上面的代码是执行这一个动画,可如果要同时执行两个动画呢?一般人自然想到的是下面的写法:(错误的写法)
1 2 3 4 5 6
| btnArr[0].onclick = function () { var json1 = {"left": 100, "top": 200, "width": 300, "height": 300}; var json2 = {"left": 200, "top": 10, "width": 150, "height": 150}; animate(div, json1); animate(div, json2); }
|
但是这样写的话,第二个动画 json2 会把第一个动画 json1 层叠掉。也就是说,第一个动画直接就不执行了。效果如下:
这显然不是我们想看到的。
如果我们想先执行第一个动画fn1(),再执行第二个动画fn2()的话,就要用到回调函数。意思是,将第二个动画fn2()作为回调函数即可。
完整版代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> div { position: absolute; top: 40px; left: 10px; width: 100px; height: 100px; background-color: pink; } </style> </head> <body>
<button>运动到 json 设置的位置</button> <div></div>
<script>
var btnArr = document.getElementsByTagName("button"); var div = document.getElementsByTagName("div")[0];
btnArr[0].onclick = function () { var json1 = {"left": 100, "top": 200, "width": 300, "height": 300}; var json2 = {"left": 300, "top": 10, "width": 100, "height": 100};
animate(div, json1, function () { animate(div, json2); }) }
function animate(ele, json, fn) { clearInterval(ele.timer); ele.timer = setInterval(function () { var bool = true;
for (var key in json) { var current = parseInt(getStyle(ele, key)) || 0; var step = (json[key] - current) / 10; step = step > 0 ? Math.ceil(step) : Math.floor(step); current = current + step; ele.style[key] = current + "px"; if (json[key] !== current) { bool = false; } }
console.log(1); if (bool) { clearInterval(ele.timer); if (fn) { fn(); } } }, 25); }
function getStyle(ele, attr) { if (window.getComputedStyle) { return window.getComputedStyle(ele, null)[attr]; } return ele.currentStyle[attr]; }
</script> </body> </html>
|
效果如下:
上方代码中,如果我们要先后完成两个动画,执行的代码是:
1 2 3
| animate(div, json1, function () { animate(div, json2); })
|
如果想要先后执行两个动画,那就以此类推:
1 2 3 4 5
| animate(div, json1, function () { animate(div, json2,function () { animate(div,json3); }); })
|
举例:仿360的右下角开机效果
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> .box { width: 322px; position: fixed; bottom: 0; right: 0; }
span { position: absolute; top: 0; right: 0; width: 30px; height: 20px; cursor: pointer; } </style> <script> window.onload = function () { var guanbi = document.getElementById("guanbi"); var box = guanbi.parentNode; var b = document.getElementById("b");
guanbi.onclick = function () { animate(b, {"height": 0}, function () { animate(box, {"width": 0}); }); } }
function animate(ele, json, fn) { clearInterval(ele.timer); ele.timer = setInterval(function () { var bool = true;
for (var key in json) { var current = parseInt(getStyle(ele, key)) || 0; var step = (json[key] - current) / 10; step = step > 0 ? Math.ceil(step) : Math.floor(step); current = current + step; ele.style[key] = current + "px"; if (json[key] !== current) { bool = false; } }
console.log(1); if (bool) { clearInterval(ele.timer); if (fn) { fn(); } } }, 1); }
function getStyle(ele, attr) { if (window.getComputedStyle) { return window.getComputedStyle(ele, null)[attr]; } return ele.currentStyle[attr]; }
</script> </head> <body> <div class="box"> <span id="guanbi"></span> <div class="hd" id="t"> <img src="images/1.jpg" alt=""/> </div> <div class="bd" id="b"> <img src="images/2.jpg" alt=""/> </div> </div> </body> </html>
|
效果如下:
工程文件:
4、对 opacity和 z-index 属性进行单独改进
我们以上的代码中,如果要进行动画参数的设置,是直接把参数放到json里面去的。例如:
1 2
| var json1 = {"left": 100, "top": 200, "width": 300, "height": 300}; var json2 = {"left": 300, "top": 10, "width": 100, "height": 100};
|
可是,下面这两个属性,却不能这样放到json里,会出现兼容性的问题:
1 2
| opacity: 0.5; //透明度 z-index: 1;
|
如何改进呢?暂略。