JavaScript之深入Js运动–搭建完美运动框架
上一篇文章向大家介绍并且实现了最基础的Js运动,包括元素的匀速运动、淡入淡出效果、缓冲运动和多物体运动。在这一篇文章中我们继续深入研究Js运动,最后为大家封装一个完美运动框架,这样我们就能拿着这个框架“胡作非为”!比如去编写一个banner轮播图效果,页面放烟花效果等等~
一、多物体多样式运动
接上篇,我们现在研究一下多物体多样式运动。什么是多物体多样式运动?比如说,我这里有5个相同的元素,我想封装一个startMove()函数让这5个元素做不同的事情:
- 让第一个元素的宽增加到300px;
- 让第二个元素的高增加100px;
- 让第三个元素向右缓冲运动;
- 让第四个元素字体放大;
- 改变第五个元素的透明度。
先上图看看效果!
为了实现上面的效果,那就继续改进改进我们的startMove()框架,这一次我们***把元素的css样式类型作为形参***传递进来,这样就可以任意修改元素样式的运动啦!
首先我们先获取到css样式,这里封装一个getStyle()函数,进行获取当前元素样式。node.style仅能获取到元素的内联样式,获取不到外部样式,所以我们先来学习两个用Js获取到元素的内联样式或者外部样式的方法:node.currentStyle和getComputedStyle()。
node.currentStyle仅支持IE浏览器使用,getComputedStyle()支持非IE浏览器使用,所以这里我们要用if-else做一下兼容处理。
/*
node 元素节点
cssStyle 获取css样式类型
*/
function getStyle(node, cssStyle){
if(node.currentStyle){
return node.currentStyle[cssStyle];//使用js获取元素当前样式,仅兼容IE浏览器
}else{
return getComputedStyle(node)[cssStyle];//使用js获取元素当前样式,兼容非IE浏览器
}
}
获取到元素样式,我们就能改进startMove()函数了:
//多物体多样式的运动
function startMove(node, attr, iTarget){
clearInterval(node.timer);//关闭上一次定时器
node.timer = setInterval(function(){
//1、获取当前值
var iCur = null;
//透明度和其他样式的兼容处理
if(attr == "opacity"){
iCur = parseInt(parseFloat(getStyle(node, "opacity")) * 100);//封装的获取css样式的getStyle()函数用在这里。
}else{
iCur = parseInt(getStyle(node, attr))//封装的获取css样式的getStyle()函数用在这里。
}
//2、计算速度
var speed = (iTarget - iCur) / 8;//最佳缓冲效果
speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);//速度取整
//3、运动和停止分开
if(iCur == iTarget){
clearInterval(node.timer);//到达目的值关闭定时器
}else{
if(attr == "opacity"){//如果修改样式是透明度
iCur += speed;
node.style.opacity = iCur / 100;
node.style.filter = `alpha(opacity=${iCur})`;//透明度兼容写法
}else{//修改样式不是透明度,就执行下面的方法。
node.style[attr] = iCur + speed + 'px';
}
}
}, 30);
}
这样我们就可以使用getStyle()函数和startMove()函数进行多物体多样式运动了。完整代码奉上:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div{width: 100px; height: 50px; background-color: red; font-size: 20px; margin: 50px;}
div.box{opacity: 0.3; filter: alpha(opacity=30);}
</style>
<script>
window.onload = function(){
var aDivs = document.getElementsByTagName("div");
//点击第一个div,让宽100 => 300
aDivs[0].onclick = function(){
startMove(this, "width", 300);
}
//高变成100 => 300
aDivs[1].onclick = function(){
startMove(this, "height", 300);
}
//marginLeft 100 => 300
aDivs[2].onclick = function(){
startMove(this, "marginLeft", 300);
}
//fontSize 20 => 100
aDivs[3].onclick = function(){
startMove(this, "fontSize", 100);
}
//透明度的变化
aDivs[4].onmouseover = function(){
startMove(this, "opacity", 100);
}
aDivs[4].onmouseout = function(){
startMove(this, "opacity", 30);
}
}
//多物体多样式的运动 startMove(this, "width", 300);
function startMove(node, attr, iTarget){
clearInterval(node.timer);
node.timer = setInterval(function(){
//1、获取当前值
var iCur = null;
if(attr == "opacity"){
iCur = parseInt(parseFloat(getStyle(node, "opacity")) * 100);
}else{
iCur = parseInt(getStyle(node, attr))
}
//2、计算速度
var speed = (iTarget - iCur) / 8;
speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
//3、运动和停止分开
if(iCur == iTarget){
clearInterval(node.timer);
}else{
if(attr == "opacity"){
iCur += speed;
node.style.opacity = iCur / 100;
node.style.filter = `alpha(opacity=${iCur})`;
}else{
node.style[attr] = iCur + speed + 'px';
}
}
}, 30);
}
/*
node 元素节点
cssStyle 获取css样式类型
*/
function getStyle(node, cssStyle){
if(node.currentStyle){
return node.currentStyle[cssStyle];
}else{
return getComputedStyle(node)[cssStyle];
}
}
</script>
</head>
<body>
<div></div>
<div></div>
<div></div>
<div>div文本</div>
<div class = 'box'></div>
</body>
</html>
二、链式运动
了解完多物体多样式运动,大家来想一个问题:如何让一个元素进行多样式运动呢?
这里我们就要学习一下链式运动了,即上一个运动完成接着执行下一个运动。学习链式运动之前我们需要先来了解一下 回调函数的概念。回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
我个人理解的回调函数就是一个函数设置为另一个函数的参数。
举个栗子看一下:
<script>
/*
1、先让你选择怎么吃
<1>堂食 <2>外带
2、可以开始点餐
3、取餐 做<1>堂食<2>外带
*/
//编写一个KFC点餐系统
function xiaoming(){
// document.write("小明要了一杯免费的水,吃起了自己喜欢的香辣鸡腿堡");
}
var xiaohua = function(){
// document.write("小花选择了外带,选择了圣代,希望带回去和弟弟一起享用");
}
function KFC(food, eatFunc){ //eatFunc = xiaoming
document.write("您的" + food + "已经准备好了,请您及时取餐。");
eatFunc(); //执行吃的方式
}
KFC("香辣鸡腿堡", xiaoming);
document.write("<br>")
// KFC("两个圣代", xiaohua);
KFC("两个圣代", function(){
document.write("小花选择了外带,选择了圣代,希望带回去和弟弟一起享用");
})
</script>
输出结果
了解完回调函数,我们来看看链式运动,在这里给startMove()函数设置一个叫complete的参数(complete相当于即以后要传进来的函数)。
来看完整版代码,学习链式运动。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#div1{width: 100px; height: 100px; background-color: red;}
</style>
<script>
window.onload = function(){
var oDiv = document.getElementById("div1");
/*
【注】每一个动画都开始在上一个动画结束的时候。
*/
//链式运动 宽100=>300 然后高100=>300 透明度100=>30
oDiv.onmouseover = function(){
startMove(this, "width", 300, function(){
startMove(this, "height", 300, function(){
startMove(this, "opacity", 30);
})
});
}
//链式运动 透明度30=>100 然后高300=>100 宽300=100
oDiv.onmouseout = function(){
startMove(this, "opacity", 100, function(){
startMove(this, "height", 100, function(){
startMove(this, "width", 100);
})
});
}
}
</script>
<script>
//多物体多样式的运动 startMove(this, "width", 300);
function startMove(node, attr, iTarget, complete){ //complete = show;
clearInterval(node.timer);
node.timer = setInterval(function(){
//1、获取当前值
var iCur = null;
//样式
if(attr == "opacity"){
iCur = parseInt(parseFloat(getStyle(node, "opacity")) * 100);
}else{
iCur = parseInt(getStyle(node, attr))
}
//2、计算速度
var speed = (iTarget - iCur) / 8;
speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
//3、运动和停止分开
if(iCur == iTarget){
clearInterval(node.timer);
//这里是上一个动画结束的时候,但是动画结束以后,我们要干什么是不确定的
//一般情况下我们把函数中的不确定值声明成形参,一大段代码不确定,也可以声明成形参
//回调函数:这种直接将函数当做参数的写法,叫做回调函数。
if(complete){
complete.call(node); //执行函数,强制改变this指向
}
}else{
//判断是否是透明度
if(attr == "opacity"){
iCur += speed;
node.style.opacity = iCur / 100;
node.style.filter = `alpha(opacity=${iCur})`;
}else{//样式不是透明度就执行下面的方法
node.style[attr] = iCur + speed + 'px';
}
}
}, 30);
}
// 获取css样式
function getStyle(node, cssStyle){
if(node.currentStyle){
return node.currentStyle[cssStyle];//获取css样式,IE浏览器兼容
}else{
return getComputedStyle(node)[cssStyle];//获取css样式,非IE浏览器兼容
}
}
</script>
</head>
<body>
<div id = 'div1'></div>
</body>
</html>
链式运动效果图:
三、封装完美运动函数
研究完以上内容,我们终于到了本篇文章的重头戏了 => 封装一个完美运动函数。
虽然使用链式运动已经能够达到比较完美的效果,但是在书写的时候需要一直使用回调函数调用函数实现运动效果。
我们改进一下,使用 对象 的方法实现完美运动效果。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#div1{width: 100px; height: 100px; background-color: black; color: white;}
</style>
<script>
window.onload = function(){
var oDiv = document.getElementById("div1");
//将需要到达的运动值设置成对象
//鼠标移入效果
oDiv.onmouseover = function(){
startMove(this, {
//宽 100px => 300px
width: 300,
//高 100px => 200px
height: 200,
//透明度 100 => 30
opacity: 30
}, function(){
this.innerHTML = "移入动画结束";//判断移入动画是否结束
});
}
oDiv.onmouseout = function(){
startMove(this, {
//宽 300px => 100px
width: 100,
//高 200px => 100px
height: 100,
//透明度 30 => 100
opacity: 100
}, function(){
this.innerHTML = "移出动画结束";//判断移出动画是否结束
});
}
}
</script>
<script>
//这里将attr改成了cssObj
function startMove(node, cssObj, complete){ //complete = show;
clearInterval(node.timer);//关闭上一次定时器
/*
需求:想要实现多个css的变化
【注】问题:当其中有一个动画到达目的值,整体就必须关闭掉定时器。
解决:定时器一定是等所有的动画都到达目的值以后才能关闭定时器。声明一个是否到达目的值的变量。
*/
node.timer = setInterval(function(){
var isEnd = true; //假设所有动画都到达了目的值
for(var attr in cssObj){
//取出当前css样式的目的值
var iTarget = cssObj[attr];
//1、获取当前值
var iCur = null;
//判断是否进行透明度运动
if(attr == "opacity"){
//计算透明度的时候,如果遇到这种有不兼容的问题,直接通过中间变量完成计算
iCur = parseInt(parseFloat(getStyle(node, "opacity")) * 100);
}else{
//不是透明度运动将执行下面的运动方式
iCur = parseInt(getStyle(node, attr))
}
//2、计算速度
var speed = (iTarget - iCur) / 8;//完美的缓冲运动速度
speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);//速度值不能带小数,进行取整操作
//判断是否是透明度操作
if(attr == "opacity"){
iCur += speed;
node.style.opacity = iCur / 100;
node.style.filter = `alpha(opacity=${iCur})`;
}else{
node.style[attr] = iCur + speed + 'px';
}
//当前值是否到达目的值
if(iCur != iTarget){
isEnd = false;//当前值不等于目的值,就不关闭定时器
}
}
//判断是否到达目的值
if(isEnd){
//说明都到达目的值,关闭定时器
clearInterval(node.timer);
//执行下一个运动
if(complete){
complete.call(node);
}
}
}, 30); //30ms运动一下
}
/*
获取css样式操作
node 元素节点
cssStyle 获取css样式类型
*/
function getStyle(node, cssStyle){
if(node.currentStyle){
return node.currentStyle[cssStyle];
}else{
return getComputedStyle(node)[cssStyle];
}
}
</script>
</head>
<body>
<div id = 'div1'>11</div>
</body>
</html>
完美运动效果图
完美运动函数已经给大家封装完成,大家可以直接拿去实现轮播图,分享到菜单,贪吃蛇,放烟花等等运动效果了。不过大家要学会如何调用完美运动函数,看看上面的案例,动动脑子自己研究一下,相信那么聪明的你一看就会了!
最后,把已经封装好的startMove()函数直接丢给大家,大家接住!
function startMove(node, cssObj, complete){ //complete = show;
clearInterval(node.timer);//关闭上一次定时器
/*
需求:想要实现多个css的变化
【注】问题:当其中有一个动画到达目的值,整体就必须关闭掉定时器。
解决:定时器一定是等所有的动画都到达目的值以后才能关闭定时器。声明一个是否到达目的值的变量。
*/
node.timer = setInterval(function(){
var isEnd = true; //假设所有动画都到达了目的值
for(var attr in cssObj){
//取出当前css样式的目的值
var iTarget = cssObj[attr];
//1、获取当前值
var iCur = null;
//判断是否进行透明度运动
if(attr == "opacity"){
//计算透明度的时候,如果遇到这种有不兼容的问题,直接通过中间变量完成计算
iCur = parseInt(parseFloat(getStyle(node, "opacity")) * 100);
}else{
//不是透明度运动将执行下面的运动方式
iCur = parseInt(getStyle(node, attr))
}
//2、计算速度
var speed = (iTarget - iCur) / 8;//完美的缓冲运动速度
speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);//速度值不能带小数,进行取整操作
//判断是否是透明度操作
if(attr == "opacity"){
iCur += speed;
node.style.opacity = iCur / 100;
node.style.filter = `alpha(opacity=${iCur})`;
}else{
node.style[attr] = iCur + speed + 'px';
}
//当前值是否到达目的值
if(iCur != iTarget){
isEnd = false;//当前值不等于目的值,就不关闭定时器
}
}
//判断是否到达目的值
if(isEnd){
//说明都到达目的值,关闭定时器
clearInterval(node.timer);
//执行下一个运动
if(complete){
complete.call(node);
}
}
}, 30); //30ms运动一下
}
/*
获取css样式操作
node 元素节点
cssStyle 获取css样式类型
*/
function getStyle(node, cssStyle){
if(node.currentStyle){
return node.currentStyle[cssStyle];
}else{
return getComputedStyle(node)[cssStyle];
}
}
看完本篇文章能Get到知识的小伙伴们,点个赞再走吧~~
顺便再给小编点点关注!