JS特效
前言
经过前面几篇文章的讲解,相信大家已经会操作DOM
和BOM
了。为什么前面要花那么多精力去讲DOM
呢?因为在后面的学习、工作中,会大量的使用DOM
操作,一个表格需要增、删、改、查,一个图片需要改变大小..等,如果你想要动态的改变这些,必须要学会使用DOM
。
为了巩固前面的知识点,并且能够熟练地使用它们,这里单独写了一篇《JavaScript 进阶知识 - 特效篇》。本篇文章作为进阶篇很重要,不单单是对前面知识点的运用,期间也会有大量的新知识点注入,所以希望小伙伴们继续加油,认真阅读。
在本篇文章中主要会讲解一些案例,比如我们平时在页面中碰到的一些特效,一些动画效果。
注意: 所有的案例都在这里链接: 提取密码密码: 70ny
,文章中的每个案例后面都有对应的序号。
1. offset 系列
offset
系列用于用于获取元素自身的大小和位置,在网页特效中有广泛应用。offset
系列主要有:offsetHeight
、offsetWidth
、offsetParent
、offsetLeft
、offsetTop
。
1.1 offsetWidth 和 offsetHeight
offsetWidth
和offsetHeight
获取的是元素的真实宽高
- 获取的是元素真实的高度和宽度
- 获取到的是数值类型,方便计算
-
offsetHeight
与offsetWidth
是只读属性,不能设置。
示例代码:获取一个盒子的真实宽高 [01-offset系列-offsetWidth&Height.html]
offsetWidth
是一个通过计算后得到的值, padding
+ border
+ width
思考: 之前我们不是也可以通过style
来获取样式吗?他们有什么不同
style.height
与style.width
只能获取到行内样式里的width
和height
- 获取的是字符串类型,还需要转换成数值类型
- 写在css样式里的宽高是获取不到的,只能获取行内样式
总结:
- 设置宽度高度使用
style.width
与style.height
- 获取宽度和高度
offsetWidth
与offsetHeight
-
offset
获取的宽高包括padding
、border
1.2 offsetParent
parentNode
和offsetParent
-
parentNode
始终是父元素 -
offsetParent
是离当前元素最近的定位元素(absolute
、relative
),如果没有,那就找body
示例代码: [02-offset系列-offsetParent.html]
1.3 offsetLeft与offsetTop
offsetLeft
: 自身左侧到offsetParent
左侧的距离:left + margin
offsetTop
: 自身顶部到offsetParent
顶部的距离 :top + margin
- 元素自身与
offsetParent
真实的距离 - 获取到的是数值类型,方便计算
- 只读属性,只能获取,不能设置
示例代码:获取一个盒子距父盒子的距离 [03-offset系列-offsetTop&Left.html]
思考: 之前我们不是也可以通过style
来获取样式吗?他们有什么不同
style.top
与style.left
只能获取到行内样式里的top
和left
- 获取的是字符串类型,还需要转换成数值类型
- 写在css样式里的宽高是获取不到的,只能获取行内样式
总结:
- 设置定位
left/top
使用style.left
与style.top
- 获取定位
left/top
使用offsetLeft
与offsetTop
-
offset
获取的位置包括margin
- 如果父元素没有定位,获取的就是相对
body
的
一张图看清offset系列
2. 匀速动画框架
2.1 匀速动画初体验
如何让一个物体动起来?动画函数的实现原理其实就是利用间歇定时器每隔一段时间执行一次的原理实现的。
1、让一个物体动起来
点击按钮让一个盒子匀速往右执行一段距离:[04-匀速动画初体验(一).html]
效果图:
BUG: 不知道细心的小伙伴有没有发现两个问题
- 现在执行的时候是不会停下来的,一直往右跑
- 点击按钮之后再去点击,会发现,按钮点击次数越多,盒子速度越快
2、让一个物体动起来,解决bug
我们让盒子运动到500px
的位置停下来 [05-匀速动画初体验(二).html]
var btn = document.getElementById('btn');
var box = document.getElementById('box');
var timer = null;
/**
为什么会越点越快?
点击一次就会调用一次定时器,点击的次数越多,调用的就越多
距离叠加的就会越来越大 视觉效果上看起来就跑的越来越快
只要在每次点击后,定时器执行前清除上一次定时器,就不会出现越点越快的效果了
*/
btn.onclick = function() {
// 一进来就清除定时器
clearInterval(timer);
timer = setInterval(function() {
// 定义一个距离 相当于每一次要跑的距离 step
var step = 5;
// 定义一个当前位置 leader
var leader = box.offsetLeft;
/**
当移动的位置在500px内的时候,执行动画函数
否则就清除定时器,让盒子停下来
*/
if (leader < 500) {
// 每次执行的时候 让leader都走step距离
leader = leader + step;
// 将距离赋值给box
box.style.left = leader + "px";
} else {
clearInterval(timer);
}
}, 15);
}
效果图:
总结:
-
setInterval
间歇定时器,如果不手动清除,它就会一直运行下去 - 点击事件触发定时器一定要注意,一进来就清除一次,否则会越点越快
2.2 匀速动画函数封装
函数需要独立,就不能使用全局变量。timer
之前是一个全局变量,如果不独立,页面只有一个定时器在运作。封装的函数里将timer
绑定给调用定时器的元素,这样就独立了。
1、封装一个动画函数 [06-封装一个匀速动画函数.html]
注意: 上面的案例我们只是简单的实现了一个动画的封装效果,但是作为一个以后会经常用的函数,上面的代码还有很多需要优化的地方
- 1、上面的函数只能往正方向跑,也就是说去到
1000
,想让它回到500
是不好实现的; - 2、如果每次走的距离是
5
,目标距离是500
,正好能整除。假如每次走的是9
呢?每次走9
,是不能被500
整除的,所以最后停下里的距离会偏多一点。
2、封装一个动画函数完整版 [07-封装一个匀速动画函数完整版.html]
- 先说说第二个问题,距离的问题。如果走的距离不能被目标距离整除的话,最后会多出来一点距离,我们可以不用管这个距离,直接在清除定时器,停下里的时候让它的距离等于目标距离。
clearInterval(element.timer); // 清除前位置在504,直接在下面设置让它位置等于500
element.style.left = target + "px"; // 500
- 现在说说第一个问题,盒子到
1000
的时候不能回到500
。假设现在盒子在1000
,我们点击按钮1
的时候想要让他回到500
,这个时候我们可以发现时的leader = 1000
,目标距离target为500
,就是说当leader>target
的时候,盒子是可以往回走的,这时候只要将步数设置为负数
,盒子就是往回跑的。
var leader = element.offsetLeft;
// 当目标距离大于当前位置 说明往正方向走 step的值就是正的
var step = target > leader? 9 : -9;
- 此时就不能再根据
if (leader < target){}, else { clearInterval(element.timer); }
去判断,让盒子运动了。这时的判断条件应该是目标距离target
与盒子目前距离leader
之间差的绝对值大于等于一步距离step
绝对值的时候,让他们执行leader = leader + step;
否则的话清除清除定时器,并将最后的距离直接设置为target
的距离。
var distance = Math.abs(target - leader);
// 通过判断此时的差如果大于或者等于一步的距离step的时候,就应该执行动画
if (distance >= Math.abs(step)) {
leader = leader + step;
element.style.left = leader + "px";
}
完整代码:
效果图:
如上,这就是封装的一个完美的动画函数了,下次有需要用到动画的地方,直接引用即可——[ js/animate.js ]
3. 轮播图
基本上每个网站都会用到轮播图,轮播图的使用可以说是必不可少的。以后我们用的最多的可能是插件,原生的可能并不常用,但是轮播图的原理我们必须知道,并且能够写出来。(之前一次面试就是让我讲出轮播图的具体实现步骤)
3.1 简单轮播图
现在我们先来学习下简单的轮播图实现原理。
轮播图样式的特点:
-
ul
要足够的宽,要求能够一行放下所有的li
- 父盒子的宽高和图片的宽高一样
- 父盒子要有一个
overflow:hidden
,仅显示一张图片,不多不少
要求ul
很宽很宽,因为所有的li
要左浮动,要保证所有的li
在一行上显示,定义一个盒子,盒子的宽高要和显示的单张图片宽高一样,然后设置overflow:hidden
这样其他的li
就会被隐藏在下面,通过改变ul的位置就能实现图片的切换了
示例代码: [08-实现简单的轮播图.html]
效果图:
从上面效果图中,我们可以看到,一个最简单的轮播图已经成型了,但是需要去用手点击,而且如果跨点数去点击,会发现图片要一张张滑过去,这里后面我们会优化。
3.2 左右焦点轮播图
左右焦点轮播图,就是在显示图片的两端添加两个按钮,一个向左,一个向右,点击的时候图片会根据点击的方向滑动。并且当鼠标悬停在显示区域的时候,两个按钮显示。鼠标离开显示区域,,两个按钮隐藏。
示例代码: [09-左右焦点轮播图.html]
效果图:
3.3 无缝轮播图
上图可以看到,当滑到最左边或者最右边的时候,再点击就没有用了,正常的轮播图肯定不是这样的,点击到最后一张后再点击肯定是接着滑动的。下面我们接着看,如何实现一个无缝轮播图
示例代码:无缝轮播(可以一直点击) [10-左右焦点轮播图-无缝滚动.html]
何谓无缝滚动?
无缝滚动就是图片能够循环切换,就算是最后一张,点击之后也会跳到第一张
原理:
- 效果就像上面所说的一样,主要实现原理就是,在最后面一张图片,再加上一张图片,这张图片就是第一张图片
- 当滑动到最后一张图片的时候(看下图),此时的视觉效果就是停在第一张图片上
- 这时只需要在程序上判断,当在最后一张的时候,直接跳到第一张图片即可
示例代码:无缝滚动的简单原理 [10-无缝滚动原理.html]
-
-
-
-
-
-
效果图:
左右焦点无缝轮播图: [11-左右焦点无缝轮播图.html]
效果图:
3.4 完整版轮播图
前面我们已经可以通过点击对应的小点、左右焦点和无缝滚动来实现轮播图了,不过都是单独分开来的,现在我们做个整合,实现一个完整的轮播图。
功能概述:
-
简单轮播功能
- 给
circle
下的所有的li注册点击事件 - 排他
- 移动
Ul
- 给
-
左右焦点功能
- 需要定义一个变量
count
来记录移动的图片的张数。
- 需要定义一个变量
-
点击右箭头功能
- 如果当前图片是最后一张(假图片),需要瞬间变成真图片
- 点击一次,需要让图片往右移动一张
- 同步小圆点,干掉所有小圆点,复活对应
count
的小圆点。 - 最后一张假图片对应的小圆点是第一个,需要做特殊处理
点击左箭头的功能和右箭头基本一致。
-
自动轮播的功能
- 开启定时器,每隔两秒点击一次右箭头
- 鼠标经过盒子,停止定时器(箭头乱闪的问题解释)触发事件的一定要是外面的大盒子,不能是
ul
,如果给ul
注册事件,就会出现乱闪的问题 - 鼠标离开盒子,开启定时器
-
同步功能
- 点击小圆点时需要同步
- 淘宝
bug
解决方法(当一圈过后回到第一个小圆点的时候,再点击它会发现他会再跑一圈)
- 淘宝bug图:
完整代码: [12-完整版轮播图.html]
效果图:
完美了吗?并没有,这里有个小bug:
可能会有小伙伴不理解,有问题你上面直接讲一下不就得了,还特地卖关子在下面重新写一遍。我想跟大家说的一点就是,如果在上面我直接告诉你这里有个问题有个bug的话,你一眼看过,可能都不当回事,我在这里拿出来讲一下,相信这个知识点你会记得更深。
小bug:明明设置的是600
,怎么会是596.4px
呢?
原因:
-
offsetLeft
获取值的时候,只会获取整数,会对小数部分会四舍五入处理,比如step = (target - leader)/10
当step
的值出现小数的时候,leader+= step
之后,offsetLeft
在获取leader
位置的时候就会把小数部分四舍五入,这样就会造成最后距离的误差。
解决方法:
- 对
step
向上取整处理(Math.ceil()
),保证每一次都至少跑1px
的距离,只要不出现小数offsetLeft
就不会出现四舍五入。
完整代码: [14-缓动动画初体验(二).html]
var box = document.getElementById('box');
var btn = document.getElementById('btn');
var timer = null;
btn.onclick = function() {
clearInterval(timer);
timer = setInterval(function() {
// 定义一个目标距离
var target = 600;
// 获得当前盒子的位置
var leader = box.offsetLeft;
// 每次运动的距离
var step = (target - leader) / 10;
// 对step进行向上取整
step = Math.ceil(step);
// leader = leader + step 动起来
leader += step;
// 将距离给盒子
box.style.left = leader + "px";
// 当当前距离等于目标距离的时候清除定时器
if (leader == target) {
clearInterval(timer);
}
}, 15);
}
4.2 缓动动画函数封装
前面匀速动画那里已经讲过封装一个函数的好处与重要性,现在我们将缓动动画也封装成一个函数。
示例代码: [15-缓动动画函数封装.html]
效果图:
又到了找bug的时候了:
上面的代码从0-500
,从500-1000
都没有问题,经过向上取整后都能到达目标距离:500
和1000
。但是小伙伴可以看下,当从1000
回到500
的时候,是正好回到500
的吗?答案肯定不是的,为什么呢?
step
为正数的时候,向上取整是完全没有问题的,但是当从1000
到500
的时候,step
就是负数了,负数向上取整后就会变得更大,比如原本是-33.3
,向上取整后就是-33
了,-0.3
就会舍去,所有就不会到500
的位置。
解决方法: 判断step的正负,为正的时候,向上取整。为负的时候,向下取整。
缓动函数封装完整版: [16-缓动动画函数封装完整版.html]
function slowAnimate(element, target, num) {
// 一进来就要清除定时器,防止越点越快
clearInterval(element.timer);
element.timer = setInterval(function() {
// 获得元素当前位置
var leader = element.offsetLeft;
// 定义每次运动的距离
var step = (target - leader) / num;
//如果step是正数,对step向上取整,
//如果step是负数,对step向下取整
// 保证每一次最少都走1px
step = step > 0 ? Math.ceil(step) : Math.floor(step);
leader += step;
// 设置元素的位置
element.style.left = leader + 'px';
// 当元素的位置 等于 目标位置的时候 清除定时器
if (leader == target) {
clearInterval(element.timer);
}
}, 15);
};
4.3 获取元素计算后的样式
获取元素计算后的样式指的是元素经过层叠后真正生效的样式,不管样式写在哪,计算后的样式指的就是最终的样式。
通过style
只能获取到写在行内的样式,那么想要获取其他的样式怎么办呢?
- js提供了一个方法:
window.getComputedStyle(element, null)[attr];
,它返回的是一个对象CSSStyleDeclaration
,[attr]
就是这个对象里面就是计算后的所有的样式的属性名(关联数组取对象的值)。element
指的是当前参数,null
这里可以不用深究-官方解释。这个方法需要window
调用。
/**
element :获取样式的当前元素
null :这里可以传一个伪元素,如果不是伪元素的话必须是null
attr :后面可以写具体的属性,比如boderRadius 就会获取这个元素的border-radius样式信息
*/
window.getComputedStyle(element,null)[attr];
示例代码: [17-获取元素计算后的样式.html]
效果图:
兼容性处理:
-
window.getComputedStyle(element, null)[attr];
只适用于现代浏览器中 -
IE678
有自己的方法:element.currentStyle[attr];
// 获取元素计算后的样式
function getStyle(element,attr){
if(window.getComputedStyle){
return window.getComputedStyle(element, null)[attr];
}else{
return element.currentStyle[attr];
}
}
// 注意:调用函数的时候 获取的属性名是一个字符串
alert(getStyle(box, "width"));
[18-获取元素计算后的样式兼容性处理.html]
注意: 上面的封装函数中,调用的时候,属性名是一个字符串类型。
4.4 缓动动画修改多个样式
不管是上面的匀速动画函数,还是这里的缓动动画函数,都只能左右运动,但是一个真正完整的动画函数,只改变左右位置肯定是不够的,我们可能需要改变它的宽高等。在上面一节中,我们知道了如何获取到元素计算后的样式,而且只要是元素有的样式都能获取到,有了这个方法我们就可以让动画去执行更多的事情了。
1、对获取到的样式返回值进行处理:
在上面的一节中,我们可以看到,获取的返回值都是字符串格式,比如获取宽度的时候,返回的是一个"300px"
的字符串,因为缓动动画函数里面是需要计算的,这里是个字符串肯定不行,所以我们需要对其进行parseInt
取整处理。
[19-缓动动画修改多个样式-处理返回值.html]:
function getStyle(element, attr) {
if (window.getComputedStyle) {
return window.getComputedStyle(element, null)[attr];
} else {
return element.currentStyle[attr];
}
}
function animate(element, attr, target) {
clearInterval(element.timer);
element.timer = setInterval(function() {
// getStyle 返回的是样式属性的值 我们用一个变量将它储存起来
var leader = getStyle(element, attr);
// 因为返回值是一个字符串,并且带有字符px,所以我们对返回值进行取整转换
leader = parseInt(leader) || 0; // 这里或 0的目的就是,当parseInt取整失败的话,给一个默认值0
var step = (target - leader) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
leader += step;
// 设置指定样式
element.style[attr] = leader + "px";
if (leader == target) {
clearInterval(element.timer);
}
}, 15);
}
animate(box, "left", 800);
上面的代码我们对它的返回值进行了处理,而且还可以对它设置其他的样式,只要单位是px
的属性都可以设置。但是这里每次还是只能设置一个样式,下面我们来实现修改多个样式。
注意: leader = parseInt(leader) || 0;
"或"上0
的目的就是:当有些属性设置的值不是数字的时候,比如:auto
,这时候parseInt
转换的结果是NaN
。当"或"上0
之后,转换失败后,leader
,就会默认是0
。
2、遍历一个对象:
让我们来复习一下,js基础的时候,我们接触到了对象,并且知道了可以用for..in
的方法来遍历对象。我们知道getComputedStyle
方法,获取计算后样式的时候,返回的是一个名叫CSSStyleDeclaration
的对象,这个对象里面是所有的样式属性,我们想要对这些属性进行多个操作的时候,就可以通过遍历的方法。
for(k in obj){
// k :就是相当于对象的键
// obj :就是需要遍历的对象
}
3、同时修改多个样式:
同时修改多个样式,就是将要修改的多个属性以对象的形式作为参数传进函数中。
[20-缓动动画修改多个样式.html]
var box = document.getElementById('box');
var btn = document.getElementById('btn');
// 封装一个函数,element 表示执行动画的元素 obj传的是一个对象,里面可以设置多个属性和值
function animate(element, obj) {
clearInterval(element.timer);
element.timer = setInterval(function() {
// 遍历外部传进来的对象
for (k in obj) {
//attr : 要做动画的样式
//target : 目标值
var attr = k;
var target = obj[k];
// 获取元素开始时计算后的样式
var leader = getStyle(element, attr);
leader = parseInt(leader) || 0;
// 缓动动画函数原理
var step = (target - leader) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
leader += step;
// 给元素设置以样式属性名为attr的值
// 这个封装的动画函数只能改值是px单位的样式
element.style[attr] = leader + "px";
if (leader == target) {
clearInterval(element.timer);
}
}
}, 15);
}
// 处理兼容性
function getStyle(element, attr) {
if (window.getComputedStyle) {
return window.getComputedStyle(element, null)[attr];
} else {
return element.currentStyle[attr];
}
}
// 调用函数 设置了五个样式属性
btn.onclick = function() {
animate(box, {
width: 200,
height: 200,
left: 300,
top: 300,
// bprder-radius 应该转为驼峰命名法 并且值只能是100px的格式 不能是百分比
borderRadius: 100
});
}
效果图:
通过上面封装的函数我们可以改变多个样式,但是效果图中我们可以看到一个问题,就是当到达设定值后,点击按钮还会慢慢的抖动。原因是修改多个样式的时候,所有的样式并不能都到同时达终点。
4.5 缓动动画修复定时器bug
出现这个bug的原因:在for循环中判断是否到达目标值,到达后就清除定时器,但是我们同时修改了5个样式,可能有的样式到达目标值后就清楚定时器了,但是有的样式还没到达目标值,所以就出现了上面的
bug
。
解决方法:假设成立法
- 假设成立
- 想办法推翻假设
- 如果推翻不了,说明假设成立
示例代码: [21-缓动动画修改多个样式-修复定时器bug.html]
function animate(element, obj) {
clearInterval(element.timer);
element.timer = setInterval(function() {
// 1-假设都到达了终点
var flag = true;
for (k in obj) {
var attr = k;
var target = obj[k];
var leader = getStyle(element, attr);
leader = parseInt(leader) || 0;
var step = (target - leader) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
leader += step;
element.style[attr] = leader + "px";
// 2- 必须要等到所有的样式都到达终点才清除定时器
// 只要有一个样式没有到达设定值,说明假设失败
if (leader != target) {
flag = false;
}
}
// 所有的样式都到达终点后 清除定时器
if (flag) {
clearInterval(element.timer);
}
}, 15);
}
4.6 缓动动画兼容其它样式属性
经过前面几小节的学习,我们已经可以实现同时修改多个样式的缓动动画了。但是细心的小伙伴不知道有没有发现,目前只能设置跟px
有关系的样式,包括设置border-radiu
也不算完善。这是因为我们缓动动画封装的时后,设置的element.style[attr] = leader + "px";
,所以只能实现跟px
有关的样式。
设置兼容其他属性的时候,要注意两点,第一获取的时候要进行判断,设置的时候也要进行判断
1、兼容opacity属性: [22-缓动动画修改多个样式-兼容opacity.html]
function animate(element, obj) {
clearInterval(element.timer);
element.timer = setInterval(function() {
var flag = true;
for (k in obj) {
var attr = k;
var target = obj[k];
// 判断获得的属性是不是“opacity”,是的话单独处理
var leader;
// 获得当前值
if (attr === "opacity") {
// 获取的时候是个小数,将它乘以100 运算时不会出现精度丢失
leader = getStyle(element, attr) * 100 || 1;
} else {
leader = getStyle(element, attr);
leader = parseInt(leader) || 0;
}
var step = (target - leader) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
leader += step;
// 赋值
// 判断是不是opacity属性 是的话 单独赋值
if (attr === "opacity") {
// 因为开始的时候leader扩大了100倍 设置的时候 opacity只能是0-1
element.style[attr] = leader / 100;
// opacity 还需要单独处理,因为IE678 不支持opacity
element.style.filter = "alpha(opacity=" + leader + ")";
} else {
element.style[attr] = leader + "px";
}
if (leader != target) {
flag = false;
}
}
if (flag) {
clearInterval(element.timer);
}
}, 15);
}
// 处理获取样式兼容性
function getStyle(element, attr) {
if (window.getComputedStyle) {
return window.getComputedStyle(element, null)[attr];
} else {
return element.currentStyle[attr];
}
}
// 调用这个函数
btn.onclick = function() {
animate(box, {
width: 200,
height: 200,
left: 300,
top: 300,
// 这里是按照 0-100 设置不透明度的,因为小数计算的时候会出现精度丢失
opacity: 50
});
}
2、兼容zIndex属性: [23-缓动动画修改多个样式-兼容zIndex.html]
zIndex这个属性不需要缓动的执行改变层级,直接获得传进来的值设置即可
// 赋值
if (attr === "opacity") {
element.style[attr] = leader / 100;
element.style.filter = "alpha(opacity=" + leader + ")";
// 判断设置的时候是否是zIndex属性
} else if (attr === "zIndex") {
element.style.zIndex= leader;
} else {
element.style[attr] = leader + "px";
}
示例代码: [24-缓动动画淡入淡出效果.html]
btn1.onclick = function() {
animate(box, {
opacity: 100
})
}
btn2.onclick = function() {
animate(box, {
opacity: 0
})
}
效果图:
4.7 缓动动画添加回调函数
程序执行完毕,再次执行的函数。
示例代码: [25-缓动动画添加回调函数.html]
var box = document.getElementById('box');
var btn = document.getElementById('btn');
function animate(element, obj, fn) {
clearInterval(element.timer);
element.timer = setInterval(function() {
var flag = true;
for (k in obj) {
var attr = k;
var target = obj[k];
var leader = getStyle(element, attr);
leader = parseInt(leader) || 0;
var step = (target - leader) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
leader += step;
element.style[attr] = leader + "px";
if (leader != target) {
flag = false;
}
}
if (flag) {
clearInterval(element.timer);
// 所有程序执行完毕了,现在可以执行回调函数了
// 只有传递了回调函数,才能执行,所以这里要判断一下
if (fn) {
fn();
}
/* fn&&fn(); */
}
}, 15);
}
// 处理兼容性
function getStyle(element, attr) {
if (window.getComputedStyle) {
return window.getComputedStyle(element, null)[attr];
} else {
return element.currentStyle[attr];
}
}
// 调用函数
btn.onclick = function() {
animate(box, {
left: 600
}, function() {
animate(box, {
top: 500,
borderRadius: 50
}, function() {
animate(box, {
width: 400,
borderRadius: 50
});
});
});
}
效果图:
5. 筋斗云案例
直接看效果图:
效果如上图,当我们鼠标经过某一项时,小方块会缓动移过去,当离开列表栏时,小方块会回到最初的位置。当点击某一项时小方块的初始位置就会停留在该项上。
示例代码: [26-筋斗云案例.html]
6. 右下角关闭广告案例
在网页中经常会出现广告,我们举个例子让关闭广告的时候有一个动画效果。
实现原理:
- 图片其实被切成了两个部分,看到的效果是一张图片,其实是两张。
- 点击关闭按钮的时候,调用缓动动画函数,将下半部分的盒子高度等于
0
,所以会出现一个向下的效果 - 在刚刚的动画函数的回调函数里面继续调用缓动动画,将整个大盒子的宽度等于
0
,所以出现一个向右的效果
示例代码: [27-右下角关闭广告案例.html]
x
效果图:
7. 手风琴案例
手风琴效果在网页中用的也特别的多,下面我们会介绍两种实现的方法,当然个人比较偏好第二种。
1、浮动版手风琴
实现原理:
- 用
ul,li
进行布局,li
左浮动,并且设置等分的宽度; - 给每个
li
注册鼠标经过事件,当鼠标经过的时候利用排他原理,将所有的li
宽度设置成最小宽度,将当前经过的li
宽度设置一个最大宽度; - 然后再去设置鼠标离开事件,当鼠标离开时让所有的
li
再恢复到等分的宽度。
示例代码: [28-手风琴-浮动版.html]
效果图:
2、定位版手风琴
实现原理:
- 给外部大盒子设置一个与图片大小一致的宽高,并且设置相对定位
- 还是采用
ul,li
结构,li
设置宽高,与图片大小一致,设置绝对定 - 动态的给
li
添加背景图片,因为li
绝对定位的原因,此时所有的li
都叠在一起 - 动态的给每个
li
设置left
值(left*i
),这时候li
就会依次排开 - 大盒子还要设置一个
overflow-hidden
属性,将多余的隐藏掉 - 给每个
li
注册鼠标鼠标经过事件,然后根据下面推算出的规律(当前鼠标经过的索引index
,他之前包括他自己的left
值都是,设定的最小值乘以对应的索引。而他后面的会将设定的最小值乘以对应的索引后再加上450
,这里的450
不是一个固定值,根据规律找出来的)进行判断,设置各自的left
值; - 鼠标离开的时候再让所有的盒子恢复到一开始的位置,每个li显示等分的宽度
大盒子没有overflow-hidden
的时候:
画个图,理解一下:
找规律:
结合上面的图片,我们可以找到一个规律
-
当鼠标在第1个li上的时候,li下标index为0:
- index:0 left:0
- index:1 left:500px
- index:2 left:550px
- index:3 left:600px
- index:4 left:650px
-
当鼠标在第2个li上的时候,li下标index为1:
- index:0 left:0
- index:1 left:50px
- index:2 left:550px
- index:3 left:600px
- index:4 left:650px
-
当鼠标在第3个li上的时候,li下标index为2:
- index:0 left:0
- index:1 left:50px
- index:2 left:100px
- index:3 left:600px
- index:4 left:650px
看出规律了吗?
- 当对应li的下标
<=
鼠标悬停的的下标上的时候left
值 是50*i
- 当对应li的下标
>
鼠标悬停的的下标上的时候left
值 是50*i + ,450
(450不是固定的值,是经过计算出来的)
示例代码: 29-手风琴-定位版.html]
效果图:
8.旋转木马案例
旋转木马也叫旋转轮播图,在效果上它就是旋转版的轮播图,但是在实现原理上却一点一不一样
旋转木马原理:
- 利用
ul
、li
方式将图片包裹在li
里,并且对每个li
的大小、层级、不透明度以及定位的位置设置好 - 样式上可能比较繁琐,我们将上面的每个参数再以对象的方式存到数组
datas
中 - 之前封装过一个缓动动画函数,可以改变层级和不透明度,这里正好用得到
- 其实抛开上面样式上的细节,旋转木马最核心的就是运用到几个数组常用的方法
pop
、unshift
、shift
、push
- 点击右按钮的时候,将
datas
里的最后一项利用pop
删除掉,并且返回这个删除的数据,再将这个数据unshift
到数组的最前面。重新遍历数组,执行一遍动画 - 点击左箭头的时候,将
datas
里的最前面一项利用shift
删除掉,并且返回这个删除的数据,再将这个数据push
到数组的最后面。重新遍历数组,执行一遍动画 - 再给按钮添加一个节流阀,没加之前不停地点击按钮,图片就会不停切换,加上之后,点一次执行完才可以再次点击。
示例代码: [30-旋转木马轮播图案例.html]
效果图: