JavaScript的运动原理2

淡入淡出

首先我们先来看下js当中的一个普遍情况,就是精度问题。

JavaScript精度问题
在js当中,关于小数的运算一向都是不够准确的。

例如:

alert(0.2 + 0.1);// 预期值 0.4  实际值: 0.30000000000000004
alert(0.2 + 0.7);// 预期值: 0.9 实际值: 0.8999999999999999

在上面的代码中,我们可以发现,在js当中的小数运算,精度是存在问题的,并不是很精确。

为什么要说到透明度的问题呢?

因为我们在设置透明度(opacity)的时候,还要考虑到兼容性的问题,另外一种写法(ie)是filter:alpha(opacity=30),里面的具体的值是正常的
透明度100,例如,我们正常设置透明度时,值为0.3,那么filter里面的值就是30。而因为JavaScript中的小数计算不够精准,所以我们在
后面的函数的封装中将采用整数的形式,也就是正常的透明度小数值
100的结果。

知道了上面的内容,我们来正式的写一下代码,首先先来引入一张图片:

<!DOCTYPE html>
<html>
    <head> <meta charset="utf-8"> <title></title> <style> #img1 { width: 200px; height: 200px; opacity: 0.3; filter:alpha(opacity=30); /*为了兼容ie*/ margin-left:300px; } </style> </head> <body> <img src="./01.jpg" id="img1"> </body> <script> // 获取元素 并且绑定事件 和 设置事件处理函数 let oImg1 = document.getElementById('img1'); oImg1.onmouseover = function() { // 调用鼠标移入处理的函数 }; oImg1.onmouseout = function () { // 调用鼠标移出处理的函数 }; </script> </html>

在上面的代码中,我们已经找到图片元素,并且给图片元素绑定了事件以及事件处理函数,下面我们再来更改一下我们之前的startMove函数,让它专门
能够针对当前案例。

function startMove(oDom,iTarget,iSpeed) {
    let iTimer = null; clearInterval(iTimer); let iCur = 0; // 用这个变量来存储透明度处理的值 iTimer = setInterval(()=>{ // 为了让值更加的精确,将结果四舍五入 iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100 if (iCur === iTarget) { // 判断是否等于目标的透明度 clearInterval(iTimer); }else { // 如果没有到达目标值 就让元素的透明度发生变化,需要分别设置ie和非ie oDom.style.opacity = (iCur + iSpeed) / 100; oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; } },30); }

在上面的代码中,我们为了获取图片本身的透明度,我们使用了一个css函数,这是一个我们自定义的函数,目的是为了获取元素的属性。

例如:

/**
 * css 函数,可以用来获取元素的css属性
 * 可以兼容ie和普通的浏览器
 * */
function css(obj,attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; }else { return getComputedStyle(obj,false)[attr]; } }

我们将startMove函数和css函数应用到上面的img案例当中,整体代码如下:

<!DOCTYPE html>
<html>
    <head> <meta charset="utf-8"> <title></title> <style> #img1 { width: 200px; height: 200px; opacity: 0.3; filter:alpha(opacity=30); /*为了兼容ie*/ margin-left:300px; } </style> </head> <body> <img src="./01.jpg" id="img1"> </body> <script> // 获取元素 并且绑定事件 和 设置事件处理函数 let oImg1 = document.getElementById('img1'); oImg1.onmouseover = function() { // 调用鼠标移入处理的函数 startMove(this,100,10); // this 向当前的图片 100 为最终的透明度 10 为单位变化的值 }; oImg1.onmouseout = function () { // 调用鼠标移出处理的函数 startMove(this,30,-10); }; function startMove(oDom,iTarget,iSpeed) { let iTimer = null; clearInterval(iTimer); let iCur = 0; // 用这个变量来存储透明度处理的值 iTimer = setInterval(()=>{ // 为了让值更加的精确,将结果四舍五入 iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100 if (iCur === iTarget) { // 判断是否等于目标的透明度 clearInterval(iTimer); }else { // 如果没有到达目标值 就让元素的透明度发生变化,需要分别设置ie和非ie oDom.style.opacity = (iCur + iSpeed) / 100; oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; } },30); } /** * css 函数,可以用来获取元素的css属性 * 可以兼容ie和普通的浏览器 * */ function css(obj,attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; }else { return getComputedStyle(obj,false)[attr]; } } </script> </html> 

上面的代码中,我们通过更改的startMove函数和css函数实现了我们的需求,图片淡入淡出。

不同属性的设置

函数再升级:针对元素的不同属性效果

上面我们实现了分享到和图片的淡入淡出功能,我们为了实现功能,对我们的函数进行更改。而如果一个网页中同时存在着分享到和图片淡入淡出,或者
说在一个网页中同时存在需要运动的元素和需要淡入淡出的元素,根据我们上面的模式,只能在网页中同时设置两个函数:

<!DOCTYPE html>
<html>
    <head> <meta charset="utf-8"> <title></title> <style> #div1 { width: 100px; height: 200px; background:red; position: absolute; left: -100px; top: 200px; } #div2 { width: 30px; height: 70px; background: black; position:absolute; right:-30px; top: 70px;; color:#fff; text-align: center; } #img1 { width: 200px; height: 200px; opacity: 0.3; filter:alpha(opacity=30); /*为了兼容ie*/ margin-left:300px; } </style> </head> <body> <div id="div1"> <div id="div2">分享到</div> </div> <img src="./01.jpg" id="img1"> </body> <script> // 获取元素 并且绑定事件 和 设置事件处理函数 let oImg1 = document.getElementById('img1'); oImg1.onmouseover = function() { // 调用鼠标移入处理的函数 startMove1(this,100,10); // this 向当前的图片 100 为最终的透明度 10 为单位变化的值 }; oImg1.onmouseout = function () { // 调用鼠标移出处理的函数 startMove1(this,30,-10); }; function startMove1(oDom,iTarget,iSpeed) { let iTimer = null; clearInterval(iTimer); let iCur = 0; // 用这个变量来存储透明度处理的值 iTimer = setInterval(()=>{ // 为了让值更加的精确,将结果四舍五入 iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100 if (iCur === iTarget) { // 判断是否等于目标的透明度 clearInterval(iTimer); }else { // 如果没有到达目标值 就让元素的透明度发生变化,需要分别设置ie和非ie oDom.style.opacity = (iCur + iSpeed) / 100; oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; } },30); } /** * css 函数,可以用来获取元素的css属性 * 可以兼容ie和普通的浏览器 * */ function css(obj,attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; }else { return getComputedStyle(obj,false)[attr]; } } // 1. 首先获取两个元素 let oDiv1 = document.getElementById('div1'); let oDiv2 = document.getElementById('div2'); let iTimer = null; // 给为父级的div绑定mouseover 和 mouseout事件 oDiv1.onmouseover = function() { // this.style.left = 0 + 'px'; // 鼠标移入,让div元素出现 startMove2(0,10); }; oDiv1.onmouseout = function() { // this.style.left = -100 + 'px'; // 鼠标移出,让div隐藏 startMove2(-100,-10); }; function startMove2(iTarget,iSpeed) { clearInterval(iTimer); iTimer = setInterval(()=>{ if (oDiv1.offsetLeft === iTarget) { // 清除定时器 clearInterval(iTimer); }else { // 没有到达边界才能继续运动 oDiv1.style.left = oDiv1.offsetLeft + iSpeed + 'px'; } },30); } </script> </html>

我们发现,在上面的代码中,网页里同时存在分享到和图片淡入淡出的两个功能。

而且我们发现startMove1和startMove2两个函数的相似代码度非常高,那么我们是否可以将两个函数进行合并呢?

合并函数的第一步,找到两个函数当中不同的地方,然后将其替换成函数的参数。

运动属性的不同:
经过比对之后,我们可以发现,在上面的两个函数当中,存在一个不同之处,就是运动属性的不同,所以,我们可以再原本函数的基础上再来提取一个
运动属性

那么更改之后的函数的调用方式就变成了类似下面这种:

// 让元素移动
startMove(this,'left',0,10); // this 运动的元素 left 运动的属性 0 目标 10 速度 // 让元素透明度发生变化 startMove(this,'opacity',30,-10); // this 变化的元素 opacity 设置的属性 30 目标 -10 单位变化的值 

当我们弄清楚了函数的调用方式之后,就可以修改我们的函数了。

首先,可以先将我们之前的iTimer变量设置到oDom这个对象身上。

function startMove(oDom,attr,iTarget,iSpeed) {
    clearInterval(oDom.iTimer); 
    // .... oDom.iTimer = setInterval(()=>{ // code ... }; };

我们接下来需要对设置的属性做一下判断,在css当中,透明度的设置需要特殊处理,其他的属性正常设置值就好。

oDom.iTimer = setInterval(()=>{
    // 进行操作判断 
    if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理 iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100 }else { // 其他的值可以直接设置 iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数 } };

当我们对属性做完初步的判断之后,还需要对赋值进行更具体的判断,代码如下:

if (iCur === iTarget) { // 判断当前值是否等于目标值,
    clearInterval(iTimer); // 如果等于,则清除定时
}else { // 如果不等于,就再来具体判断 if(attr === 'opacity'){ // 对透明度的设置需要特殊处理 oDom.style.opacity = (iCur + iSpeed) / 100; oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; }else { // 其他属性值的设置 oDom.style[attr] = iCur + iSpeed + 'px'; } }

完整的函数如下:

/*
        * oDOM 要操作的元素
        * attr 要操作的属性
        * iTarget 变化的目标值
        * iSpeed 变化的速度 
        */
        function startMove(oDom,attr,iTarget,iSpeed) { clearInterval(oDom.iTimer); let iCur = 0; // 用这个变量来存储透明度处理的值 oDom.iTimer = setInterval(()=>{ // 进行操作判断 if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理 iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100 }else { // 其他的值可以直接设置 iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数 } if (iCur === iTarget) { // 判断是否等于目标的透明度 clearInterval(oDom.iTimer); }else { if(attr === 'opacity'){ // 对透明度的设置需要特殊处理 oDom.style.opacity = (iCur + iSpeed) / 100; oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; }else { // 其他属性值的设置 oDom.style[attr] = iCur + iSpeed + 'px'; } } },30); } /** * css 函数,可以用来获取元素的css属性 * 可以兼容ie和普通的浏览器 * */ function css(obj,attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; }else { return getComputedStyle(obj,false)[attr]; } }

将这个函数应用到网页当中,分享到和图片的淡入淡出效果都可以直接调用。

<!DOCTYPE html>
<html>
    <head> <meta charset="utf-8"> <title></title> <style> #div1 { width: 100px; height: 200px; background:red; position: absolute; left: -100px; top: 200px; } #div2 { width: 30px; height: 70px; background: black; position:absolute; right:-30px; top: 70px;; color:#fff; text-align: center; } #img1 { width: 200px; height: 200px; opacity: 0.3; filter:alpha(opacity=30); /*为了兼容ie*/ margin-left:300px; } </style> </head> <body> <div id="div1"> <div id="div2">分享到</div> </div> <img src="./01.jpg" id="img1"> </body> <script> // 获取元素 并且绑定事件 和 设置事件处理函数 let oImg1 = document.getElementById('img1'); oImg1.onmouseover = function() { // 调用鼠标移入处理的函数 startMove(this,'opacity',100,10); // this 向当前的图片 100 为最终的透明度 10 为单位变化的值 }; oImg1.onmouseout = function () { // 调用鼠标移出处理的函数 startMove(this,'opacity',30,-10); }; /* * oDOM 要操作的元素 * attr 要操作的属性 * iTarget 变化的目标值 * iSpeed 变化的速度 */ function startMove(oDom,attr,iTarget,iSpeed) { clearInterval(oDom.iTimer); let iCur = 0; // 用这个变量来存储透明度处理的值 oDom.iTimer = setInterval(()=>{ // 进行操作判断 if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理 iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100 }else { // 其他的值可以直接设置 iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数 } if (iCur === iTarget) { // 判断是否等于目标的透明度 clearInterval(oDom.iTimer); }else { if(attr === 'opacity'){ // 对透明度的设置需要特殊处理 oDom.style.opacity = (iCur + iSpeed) / 100; oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; }else { // 其他属性值的设置 oDom.style[attr] = iCur + iSpeed + 'px'; } } },30); } /** * css 函数,可以用来获取元素的css属性 * 可以兼容ie和普通的浏览器 * */ function css(obj,attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; }else { return getComputedStyle(obj,false)[attr]; } } // 1. 首先获取两个元素 let oDiv1 = document.getElementById('div1'); let oDiv2 = document.getElementById('div2'); let iTimer = null; // 给为父级的div绑定mouseover 和 mouseout事件 oDiv1.onmouseover = function() { // this.style.left = 0 + 'px'; // 鼠标移入,让div元素出现 startMove(this,'left',0,10); }; oDiv1.onmouseout = function() { // this.style.left = -100 + 'px'; // 鼠标移出,让div隐藏 startMove(this,'left',-100,-10); }; </script> </html> 

在上面的案例当中,我们已经能够实现一个页面当中多个元素同时调用一个函数进行运动。

下面我们再来将我们的函数进行升级,升级成,一个元素可以通过函数进行多值同时运动。

多属性值同时运动

多值同时运动:

例如,在网页当中存在一个div,然后当我们点击这个div的时候,让宽度变为200px,高度也变为200px。

首先,我们先来完成页面的基本布局:

<!DOCTYPE html>
<html>
    <head> <meta charset="utf-8"> <title></title> <style> #div1 { width: 100px; height: 100px; background-color: red; position: absolute; top: 100px; left: 200px; } </style> </head> <body> <div id="div1"></div> </body> </html> 

设置完成基本样式之后,我们再来把基本的js代码实现:

let oDiv1 = document.getElementById('div1');
        
oDiv1.onclick = function(){ }; function startMove(oDom,attr,iTarget,iSpeed) { clearInterval(oDom.iTimer); let iCur = 0; // 用这个变量来存储透明度处理的值 oDom.iTimer = setInterval(()=>{ // 进行操作判断 if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理 iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100 }else { // 其他的值可以直接设置 iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数 } if (iCur === iTarget) { // 判断是否等于目标的透明度 clearInterval(oDom.iTimer); }else { if(attr === 'opacity'){ // 对透明度的设置需要特殊处理 oDom.style.opacity = (iCur + iSpeed) / 100; oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; }else { // 其他属性值的设置 oDom.style[attr] = iCur + iSpeed + 'px'; } } },30); }

在上面的代码中,我们给div元素绑定了单击事件,下面我们再来处理一下moveStart函数。

为了能够一次性操作多个值,我们可以将操作的属性以json的形式来传入。

// 以下面的方式进行传值
startMove(this,{ // this 操作的元素
    width:200, // 操作的属性1 height:200 // 操作的属性2 },10); // 速度 

为了实现上面的传递值的方式,函数的参数需要变成下面的样式:

function startMove(oDom,json,iSpeed){
    // code ... }

在更改具体的代码之前,我们先来回顾之前的代码
我们之前的代码在设置属性时,采用的是下面的写法:

oDom.iTimer = setInterval(()=>{
        
    // 进行操作判断 
    if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理 iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100 }else { // 其他的值可以直接设置 iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数 } // code ... },30);

上面的代码是我们之前的代码,但是为了实现多值同时变化,需要将上面的判断代码更改成如下的样子:

function startMove(oDom,json,iSpeed) { 
            
    clearInterval(oDom.iTimer);
    let iCur = 0; // 用这个变量来存储透明度处理的值 oDom.iTimer = setInterval(()=>{ // 因为传入的参数是json,为了逐一操作,需要开启循环 for(let attr in json) { // 设置目标值 let iTarget = json[attr]; // 当前属性要运动的值就是目标值 // 进行操作判断 if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理 iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100 }else { // 其他的值可以直接设置 iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数 } if (iCur === iTarget) { // 判断是否等于目标的透明度 clearInterval(oDom.iTimer); }else { if(attr === 'opacity'){ // 对透明度的设置需要特殊处理 oDom.style.opacity = (iCur + iSpeed) / 100; oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; }else { // 其他属性值的设置 oDom.style[attr] = iCur + iSpeed + 'px'; } } } },30); } function css(obj,attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; }else { return getComputedStyle(obj,false)[attr]; } } 

将更改之后的函数放在单击事件处理函数里执行。

<!DOCTYPE html>
<html>
    <head> <meta charset="utf-8"> <title></title> <style> #div1 { width: 100px; height: 100px; background-color: red; position: absolute; top: 100px; left: 200px; } </style> </head> <body> <div id="div1"></div> </body> <script> let oDiv1 = document.getElementById('div1'); oDiv1.onclick = function(){ startMove(this,{ width:200, // 不能加px height:200 },10); }; function startMove(oDom,json,iSpeed) { clearInterval(oDom.iTimer); let iCur = 0; // 用这个变量来存储透明度处理的值 oDom.iTimer = setInterval(()=>{ // 因为传入的参数是json,为了逐一操作,需要开启循环 for(let attr in json) { // 设置目标值 let iTarget = json[attr]; // 当前属性要运动的值就是目标值 // 进行操作判断 if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理 iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100 }else { // 其他的值可以直接设置 iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数 } if (iCur === iTarget) { // 判断是否等于目标的透明度 clearInterval(oDom.iTimer); }else { if(attr === 'opacity'){ // 对透明度的设置需要特殊处理 oDom.style.opacity = (iCur + iSpeed) / 100; oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; }else { // 其他属性值的设置 oDom.style[attr] = iCur + iSpeed + 'px'; } } } },30); } function css(obj,attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; }else { return getComputedStyle(obj,false)[attr]; } } </script> </html> 

我们上面的代码运行经过测试之后,点击div,已经可以实现宽度和高度同时变化。

但是我们上面的代码却存在一个问题。

例如,将宽度变为200,高度变为300.

startMove(this,{width:200,height:300},10);

这个时候,我们发现点击元素之后,宽度变为了200,但是高度却没有变成指定的300。原因也很简单,多个变化属性的速度是一致的,当其中一个属性
到达目标值时,定时器就会被清除,这个时候个别属性就不再发生变化。

所以这个时候我们就要思考一个问题,到底什么时候停止定时器?

答案其实很简单,就是当所有属性全部达到目标值时才停止运动。

我们再回来分析一下之前的代码,我们每一次循环,其实都相当于变化了一个属性,在循环的过程中,其中一个属性变化的过程中并没有办法知道另外一个
属性是否完成了变化。

所以我们应该在循环之外判断,当循环一轮之后,我们就立刻在循环之外看下每个属性是否到达了目标值。

首先,我们在代码之前设置一个变量,存储一个布尔值,用来表示是否达到目标点。
在每次进入定时器时,都把属性变为true,然后在后续的判断中,如果属性没到达目标点,就把这个变量的值变为false。

oDom.iTimer = setInterval(()=>{
    // 设置变量存储初始值
    let iBtn = true; // code ... for(let attr in json) { // code ... // 将原来的iCur === iTarget 变为下面的判断 if (iCur !== iTarget) { // 如果其中的一个属性没到,就将iBtn 变为false iBtn = false; if(attr === 'opacity'){ // 对透明度的设置需要特殊处理 oDom.style.opacity = (iCur + iSpeed) / 100; oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; }else { // 其他属性值的设置 oDom.style[attr] = iCur + iSpeed + 'px'; } } } // 通过判断iBtn的值来决定清除定时器 if (iBtn){ clearInterval(oDom.iTimer); } },30} 

完整的函数代码如下:

function startMove(oDom,json,iSpeed) { 
            
    clearInterval(oDom.iTimer);
    let iCur = 0; // 用这个变量来存储透明度处理的值 oDom.iTimer = setInterval(()=>{ // 设置一个变量,用来存储状态,表示元素是否到达目标值 let iBtn = true; // 因为传入的参数是json,为了逐一操作,需要开启循环 for(let attr in json) { // 设置目标值 let iTarget = json[attr]; // 当前属性要运动的值就是目标值 // 进行操作判断 if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理 iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100 }else { // 其他的值可以直接设置 iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数 } if (iCur !== iTarget) { // 判断是否等于目标的透明度 iBtn = false; if(attr === 'opacity'){ // 对透明度的设置需要特殊处理 oDom.style.opacity = (iCur + iSpeed) / 100; oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; }else { // 其他属性值的设置 oDom.style[attr] = iCur + iSpeed + 'px'; } } } // 判断iBtn是否为true,为true表示元素已经到达了目标值 if (iBtn){ clearInterval(oDom.iTimer); } },30); } function css(obj,attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; }else { return getComputedStyle(obj,false)[attr]; } }

运动回调,链式运动

我们在实际的运动当中,存在一种情况,就是我们需要当一个属性运动完成之后再去执行另外一个属性的变化。

想要实现这种功能,我们需要将我们的代码改成回调的形式。

首先,在我们的函数中,加入一个回调函数的参数

startMove(oDom,json,iSpeed,fn){}

至于调用的时机,我们可以放在一个属性动画完成之后再去执行回调,并且同时需要判断,是否存在回调,如果存在,在执行回调。

// 判断iBtn是否为true,为true表示元素已经到达了目标值
if (iBtn){
    clearInterval(oDom.iTimer);
    fn && fn.call(oDom); // 通过call改变this指向,方便我们后续的回调函数的调用。
}
        

下面我们来尝试一下,div点击之后,先将宽度变为200,宽度变化完成之后,再讲高度变为300.

<!DOCTYPE html>
<html>
    <head> <meta charset="utf-8"> <title></title> <style> #div1 { width: 100px; height: 100px; background-color: red; position: absolute; top: 100px; left: 200px; } </style> </head> <body> <div id="div1"></div> </body> <script> let oDiv1 = document.getElementById('div1'); oDiv1.onclick = function(){ startMove(this,{ width:200 },10,function(){ // 传入一个匿名函数作为回调函数 startMove(this,{height:300},10); }); }; function startMove(oDom,json,iSpeed,fn) { clearInterval(oDom.iTimer); let iCur = 0; // 用这个变量来存储透明度处理的值 oDom.iTimer = setInterval(()=>{ // 设置一个变量,用来存储状态,表示元素是否到达目标值 let iBtn = true; // 因为传入的参数是json,为了逐一操作,需要开启循环 for(let attr in json) { // 设置目标值 let iTarget = json[attr]; // 当前属性要运动的值就是目标值 // 进行操作判断 if(attr === 'opacity'){ // 因为透明度比较特殊,所以需要进行特殊处理 iCur = Math.round(css(oDom,'opacity') * 100); // 得到的值 标准下:0.3 非标准下:0.3 ,我们为了保证精确度,所以需要将其变成整数,所以*100 }else { // 其他的值可以直接设置 iCur = parseInt(css(oDom,attr)); // 去掉获取css值的单位 变成一个整数 } if (iCur !== iTarget) { // 判断是否等于目标的透明度 iBtn = false; if(attr === 'opacity'){ // 对透明度的设置需要特殊处理 oDom.style.opacity = (iCur + iSpeed) / 100; oDom.style.filter = 'alpha(opacity='+ (iCur + iSpeed) +')'; }else { // 其他属性值的设置 oDom.style[attr] = iCur + iSpeed + 'px'; } } } // 判断iBtn是否为true,为true表示元素已经到达了目标值 if (iBtn){ clearInterval(oDom.iTimer); fn && fn.call(oDom); // 改变this指向 } },30); } function css(obj,attr) { if (obj.currentStyle) { return obj.currentStyle[attr]; }else { return getComputedStyle(obj,false)[attr]; } } </script> </html> 

上面的代码中 ,我们顺利的实现了代码的回调函数以及多属性延迟调用的功能。

缓冲运动

上面的代码中,我们已经基本实现了运动框架的基本构建。但是我们在实际使用的过程中,还存在着一种速度缓冲的现象,什么叫速度缓冲呢?简单的说
就是速度的逐渐变慢或者逐渐变快。

在本案例中将以速度逐渐变慢为例,来完成函数的再次升级。速度的逐渐变慢,也有人称之为摩擦运动。

首先,我们先来完成一个基本的demo。

我们先来在网页中放置一个按钮,一个div。当我们点击按钮的时候,让div发生运动。

<!DOCTYPE html>
<html>
    <head> <meta charset="utf-8"> <title></title> <style> #div1 { width: 100px; height: 100px; background-color: red; position: absolute; top: 100px; left: 100px; } </style> </head> <body> <button id="btn">点击运动</button> <div id="div1"></div> </body> <script> // 获取按钮和元素 let oBtn = document.getElementById('btn'); let oDiv1 = document.getElementById("div1"); // 给按钮绑定单击事件 oBtn.onclick = function () { }; </script> </html> 

上面的代码中,我们点击按钮之后,就会触发单击事件的事件处理函数。

下面来针对这个事件处理函数进行运动处理 。

// 获取按钮和元素
let oBtn = document.getElementById('btn');
let oDiv1 = document.getElementById("div1"); let iTimer = null; let iSpeed = 50; // 定义一个初始速度 // 给按钮绑定单击事件 oBtn.onclick = function () { /*摩擦运动:减速运动,在运动的过程中,运动速度越来越慢*/ clearInterval(iTimer); iTimer = setInterval(function(){ // 进行具体的速度判读 if(oDiv1.offsetLeft === 500 ){ clearInterval(iTimer); }else { oDiv1.style.left = oDiv1.offsetLeft + iSpeed + 'px'; } },30); }; 

上面的代码完毕之后,我们点击按钮之后元素以匀速的形式到达了目标点。

下面我们来处理一下如何让我们的元素以摩擦减速的效果到达目标点。

此时我们应该知道,我们希望得到的减速运动效果,特征是越接近于目标点,速度应该越慢。


let oBtn = document.getElementById('btn');
let oDiv1 = document.getElementById("div1"); let iTimer = null; oBtn.onclick = function () { /*摩擦运动:减速运动,在运动的过程中,运动速度越来越慢*/ clearInterval(iTimer); let iSpeed = 0; iTimer = setInterval(function(){ // 处理速度 iSpeed = (500 - oDiv.offsetLeft) / 8; // 当元素的位置超过了目标位置,那么元素运动就始终到达不了目标位置,所以需要下面这种形式的判断。 iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed); //速度如果大于0,向上取整,如果小于0,向下取整 // 进行具体的速度判读 if(oDiv1.offsetLeft === 500 ){ clearInterval(iTimer); }else { oDiv1.style.left = oDiv1.offsetLeft + iSpeed + 'px'; } },30); };

在上面的代码中,为了实现我们缓冲运动的需求,我们通过距离越短,速度越慢的原理实现了渐变的速度,同时,为了防止速度最终没有
办法达到目标值,所以使用了取整的方式。

下面是案例的完整代码:

<!DOCTYPE html>
<html>
    <head> <meta charset="utf-8"> <title></title> <style> #div1 { width: 100px; height: 100px; background-color: red; position: absolute; top: 100px; left: 100px; } </style> </head> <body> <button id="btn">点击运动</button> <div id="div1"></div> </body> <script> let oBtn = document.getElementById('btn'); let oDiv1 = document.getElementById("div1"); let iTimer = null; oBtn.onclick = function () { /*摩擦运动:减速运动,在运动的过程中,运动速度越来越慢*/ clearInterval(iTimer); let iSpeed = 0; iTimer = setInterval(function(){ // 处理速度 iSpeed = (500 - oDiv1.offsetLeft) / 8; iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed); //速度如果大于0,向上取整,如果小于0,向下取整 // 进行具体的速度判读 if(oDiv1.offsetLeft === 500 ){ clearInterval(iTimer); }else { oDiv1.style.left = oDiv1.offsetLeft + iSpeed + 'px'; } },30); }; </script> </html>

猜你喜欢

转载自www.cnblogs.com/jhflyfish/p/11521588.html