JavaScript 基礎入門11 - 運動框架的封裝


JavaScript 運動原理

運動基礎

  1. 在JavaScript中,如何讓一個頁面元素動起來?
    首先,我們需要了解的是,在JavaScript中如何讓一個頁面元素動起來。

我們先來實現一個簡單的功能,當我們點擊按鈕之后,讓一個元素動起來。並且到達500的邊界之后立刻停止下來。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style>
			#d1 {
				width: 100px;
				height: 100px;
				background-color: red;
				position: absolute;
				top:100px;
				left: 200px;
			}
		</style>
	</head>
	<body>
		<button id="btn">點擊運動</button>
		<div id="d1"></div>
	</body>
	<script>
		// 點擊按鈕,讓div橫向的運動起來
		
		// 1. 獲取元素
		let oBtn = document.getElementById('btn');
		let oDiv = document.getElementById('d1');
		let iTimer = null;
		// 點擊按鈕,讓元素一直運動 ,需要使用到的知識點:定時器 
		oBtn.onclick = ()=>{
			
			iTimer = setInterval(()=>{
				// 點擊按鈕之后,讓div的位置在當前的基礎之上每次增加10px的距離
				// oDiv.style.left = oDiv.offsetLeft + 10 + 'px';  // 雖然此代碼可以讓div動起來,但是我們需要div在運動之后到達某個邊界就立刻停止,所以需要將此句代碼改為一個判斷
				if (oDiv.offsetLeft === 500) {
					// 清除定時器  
					clearInterval(iTimer);
				}else { // 沒有到達邊界才能繼續運動
					oDiv.style.left = oDiv.offsetLeft + 10 + 'px'; 
				}
			},30);
			
			
		};
	</script>
</html>

在上面的代碼中,我們點擊按鈕之后,元素已經可以直接進行移動,但是卻存在一個問題,什么問題呢?

當我們點擊按鈕之后,元素始終以10px的勻速進行運動,到達500的臨界然后停止。 但是我們的問題是,速度可能會變,例如將速度變為7px,就不能夠
准確的到達500的臨界值。

例如:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style>
			#d1 {
				width: 100px;
				height: 100px;
				background-color: red;
				position: absolute;
				top:100px;
				left: 200px;
			}
		</style>
	</head>
	<body>
		<button id="btn">點擊運動</button>
		<div id="d1"></div>
	</body>
	<script>
		// 點擊按鈕,讓div橫向的運動起來
		
		// 1. 獲取元素
		let oBtn = document.getElementById('btn');
		let oDiv = document.getElementById('d1');
		let iTimer = null;
		// 點擊按鈕,讓元素一直運動 ,需要使用到的知識點:定時器 
		oBtn.onclick = ()=>{
			
			iTimer = setInterval(()=>{
				// 點擊按鈕之后,讓div的位置在當前的基礎之上每次增加10px的距離
				// oDiv.style.left = oDiv.offsetLeft + 10 + 'px';  // 雖然此代碼可以讓div動起來,但是我們需要div在運動之后到達某個邊界就立刻停止,所以需要將此句代碼改為一個判斷
				if (oDiv.offsetLeft === 500) {
					// 清除定時器  
					clearInterval(iTimer);
				}else { // 沒有到達邊界才能繼續運動
					oDiv.style.left = oDiv.offsetLeft + 7 + 'px';  // 一旦將每次的移動距離變為7px ,那么將不能停的下來.元素會一直運動
				}
			},30);
			
			
		};
	</script>
</html>

出現這種情況的原因是因為運動的臨界值必須能夠被運動的速度(也就是oDiv.offsetLeft + 7 + 'px',表示每次執行移動的距離)整除。

上面的代碼當中, 因為臨界值不能夠被速度整除,所以,最終元素始終達到不了臨界值,那么元素就沒有辦法在到達臨界值時停止。

同時在上面的代碼中的另外一個問題是,當我們每點擊一次運動按鈕,元素的速度就會變得更快,原因很簡單,就是我們設置的定時器發生了累加。

那么該如何解決定時器累加的問題呢?

我們可以在每次開始運動之前先清楚一次定時器。

oBtn.onclick = ()=>{
			/*
			* 為了防止定時器累加,在每次開始定時器之前,先清楚掉一個定時器  
			* */
			clearInterval(iTimer);
			
			iTimer = setInterval(()=>{
				// 點擊按鈕之后,讓div的位置在當前的基礎之上每次增加10px的距離
				// oDiv.style.left = oDiv.offsetLeft + 10 + 'px';  // 雖然此代碼可以讓div動起來,但是我們需要div在運動之后到達某個邊界就立刻停止,所以需要將此句代碼改為一個判斷
				if (oDiv.offsetLeft === 500) {
					// 清除定時器  
					clearInterval(iTimer);
				}else { // 沒有到達邊界才能繼續運動
					oDiv.style.left = oDiv.offsetLeft + 7 + 'px';  // 一旦將每次的移動距離變為7px ,那么將不能停的下來.元素會一直運動
				}
			},30);	
		};

總結:在上面的代碼中,是我們一般讓一個元素運動起來的基本流程。下面進行一個簡單的總結:

  1. 首先是存在的問題:處於勻速運動的元素沒有辦法進行在不整除的情況下在臨界點停止。
  2. 在上面的代碼中,可以將整個過程大致分為三個步驟:
    • 清除定時器,保證只有一個定時器在執行
    • 開啟定時器
    • 開始運動(需要同時加入一個判斷,以便在需要的時候或者滿足某個要求時停止運動)

簡單運動的封裝

為了讓我們上面的代碼可以具備更高的復用性,下面我們把上面的代碼進行一個簡單的封裝。

示例:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style>
			#d1 {
				width: 100px;
				height: 100px;
				background-color: red;
				position: absolute;
				top:100px;
				left: 200px;
			}
		</style>
	</head>
	<body>
		<button id="btn">點擊運動</button>
		<div id="d1"></div>
	</body>
	<script>
		// 點擊按鈕,讓div橫向的運動起來
		
		// 1. 獲取元素
		let oBtn = document.getElementById('btn');
		let oDiv = document.getElementById('d1');
		let iTimer = null;
		// 點擊按鈕,讓元素一直運動 ,需要使用到的知識點:定時器 
		oBtn.onclick = ()=>{	
			startMove(); 
			// 將運動相關的內容全部放到startMove這個函數中,然后調用既可以讓元素進行運動
			function startMove() {
				clearInterval(iTimer);
				iTimer = setInterval(()=>{
					if (oDiv.offsetLeft === 500) {
						// 清除定時器  
						clearInterval(iTimer);
					}else { // 沒有到達邊界才能繼續運動
						oDiv.style.left = oDiv.offsetLeft + 10 + 'px';  // 一旦將每次的移動距離變為7px ,那么將不能停的下來.元素會一直運動
					}
				},30);
			}
		};
	</script>
</html>

在上面的代碼中,我們將運動相關的內容放到了一個函數startMove中,並且調用了這個函數,下面來根據這個函數進行案例的開發。

案例1:分享到功能

首先,先來實現基本的功能:

<!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;
			}
		</style>
	</head>
	<body>
		<div id="div1">
			<div id="div2">分享到</div>
		</div>
	</body>
	<script>
		// 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元素出現
		};
		
		oDiv1.onmouseout = function() {
			this.style.left = -100 + 'px'; // 鼠標移出,讓div隱藏
		}; 
		
	</script>
</html>

上面的代碼中,我們鼠標移入,元素出現。鼠標移出,元素消失。
下面我們來使用我們的startMove函數,給元素出現和消失加上一個過渡的效果。

我們的startMove函數如下:

function startMove() {
	clearInterval(iTimer);
	iTimer = setInterval(()=>{
		if (oDiv.offsetLeft === 500) {
			// 清除定時器  
			clearInterval(iTimer);
		}else { // 沒有到達邊界才能繼續運動
			oDiv.style.left = oDiv.offsetLeft + 10 + 'px';  // 一旦將每次的移動距離變為7px ,那么將不能停的下來.元素會一直運動
		}
	},30);
}

我們想要在分享到功能里使用這個函數,我們需要對我們的函數根據分享到的需求進行一定程度的更改。
首先是,將我們函數中的oDvi更改為oDiv1

其次是我們分享到案例的需求在鼠標移入時需要將元素逐漸的顯示,而鼠標移出時,需要將元素逐漸的隱藏。所以我們需要將startMove函數創建兩個
,並且當鼠標移出時,速度應該將函數中的+10變為-10

當然,也別忘了去更該元素邊界的值。當移出時,邊界為0,移入時,邊界為-100.

如下:

鼠標移入時調用的startMove1函數:

function startMove1() {
	clearInterval(iTimer);
	iTimer = setInterval(()=>{
		if (oDiv1.offsetLeft === 0) {
			// 清除定時器  
			clearInterval(iTimer);
		}else { // 沒有到達邊界才能繼續運動
			oDiv1.style.left = oDiv.offsetLeft + 10 + 'px';  // 一旦將每次的移動距離變為7px ,那么將不能停的下來.元素會一直運動
		}
	},30);
}

鼠標移出時調用的startMove2函數:

function startMove() {
	clearInterval(iTimer);
	iTimer = setInterval(()=>{
		if (oDiv1.offsetLeft === -100) {
			// 清除定時器  
			clearInterval(iTimer);
		}else { // 沒有到達邊界才能繼續運動
			oDiv1.style.left = oDiv.offsetLeft - 10 + 'px';  // 一旦將每次的移動距離變為7px ,那么將不能停的下來.元素會一直運動
		}
	},30);
}

我們先來將這兩個函數放在代碼中,進行測試。

<!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;
			}
		</style>
	</head>
	<body>
		<div id="div1">
			<div id="div2">分享到</div>
		</div>
	</body>
	<script>
		// 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元素出現
			startMove1();
		};
		
		oDiv1.onmouseout = function() {
			// this.style.left = -100 + 'px'; // 鼠標移出,讓div隱藏
			startMove2();
		}; 
		
		function startMove1() {
			clearInterval(iTimer);
			iTimer = setInterval(()=>{
				if (oDiv1.offsetLeft === 0) {
					// 清除定時器  
					clearInterval(iTimer);
				}else { // 沒有到達邊界才能繼續運動
					oDiv1.style.left = oDiv1.offsetLeft + 10 + 'px';  // 一旦將每次的移動距離變為7px ,那么將不能停的下來.元素會一直運動
				}
			},30);
		}
		function startMove2() {
			clearInterval(iTimer);
			iTimer = setInterval(()=>{
				if (oDiv1.offsetLeft === -100) {
					// 清除定時器  
					clearInterval(iTimer);
				}else { // 沒有到達邊界才能繼續運動
					oDiv1.style.left = oDiv1.offsetLeft - 10 + 'px';  // 一旦將每次的移動距離變為7px ,那么將不能停的下來.元素會一直運動
				}
			},30);
		}
	</script>
</html>

上面的代碼中,我們通過創建兩個startMove函數,並且對相應的參數進行修改,從而實現了給我們的分享到功能添加了過渡效果。

進一步升級:
當然,我們上面的代碼中使用的函數其實是非常不靈活的,所以我們下面要做到事就是對之前的函數進行升級,從而讓我們的函數具備更強的實用性。

首先我們再回過頭來看下我們剛才寫的兩個函數,你會發現,大部分的代碼其實都是相同的,只有個別的值是不同的,例如元素移動的邊界,例如元素
單位時間內移動的距離。

我們將上面的兩個函數合並成一個函數,只需要將不一樣的值提取出來當做參數即可。

下面是合並之后的函數:

function startMove(iTarget,iSpeed) {
	clearInterval(iTimer);
	iTimer = setInterval(()=>{
		if (oDiv1.offsetLeft === iTarget) {
			// 清除定時器  
			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: 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;
			}
		</style>
	</head>
	<body>
		<div id="div1">
			<div id="div2">分享到</div>
		</div>
	</body>
	<script>
		// 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(0,10);
		};
		
		oDiv1.onmouseout = function() {
			// this.style.left = -100 + 'px'; // 鼠標移出,讓div隱藏
			startMove(-100,-10);
		}; 
		
		function startMove(iTarget,iSpeed) {
			clearInterval(iTimer);
			iTimer = setInterval(()=>{
				if (oDiv1.offsetLeft === iTarget) {
					// 清除定時器  
					clearInterval(iTimer);
				}else { // 沒有到達邊界才能繼續運動
					oDiv1.style.left = oDiv1.offsetLeft +  iSpeed + 'px';  
				}
			},30);
		}
	</script>
</html>

上面的代碼中,我們順利的通過我們的startMove函數給分享到功能添加了過渡的效果。

圖片的淡入淡出效果:

下面我們再來看另外的一個效果,圖片的淡入淡出,還是通過我們上面定義好的startMove函數來實現效果。

首先,我們上面的函數當中,只是針對oDiv1,為了讓我們的函數可以處理任意的元素,我們將oDiv1替換成函數的形參。

例如:

function startMove(oDom,iTarget,iSpeed) {
	clearInterval(iTimer);
	iTimer = setInterval(()=>{
		if (oDom.offsetLeft === iTarget) {
			// 清除定時器  
			clearInterval(iTimer);
		}else { // 沒有到達邊界才能繼續運動
			oDom.style.left = oDom.offsetLeft +  iSpeed + 'px';  
		}
	},30);
}

上面的代碼當中,我們將函數操作的元素提取出來,變成了函數的形參,這樣做之后,我們就可以讓我們的函數針對任意的元素。

淡入淡出

下面的任務就是讓我們的函數來處理圖片的淡入淡出問題。

首先我們先來看下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>

上面我們弄清楚了緩沖的原理之后,再來將緩沖加到我們之前封裝的運動框架當中。

加入緩沖的運動框架

我們上面了解了緩沖運動的原理,下面來進行一個簡單的總結。

緩沖運動

  • 緩沖運動與摩擦力的區別: 能夠精確的到達目標位置。
  • 同時,速度由距離決定。
  • 速度 = (目標值 - 當前值 ) / 縮放系數
  • 值需要取整
function startMove(oDom, json, fn) {
		clearInterval(oDom.iTimer);
		var iCur = 0;
		var iSpeed = 0;
			
		oDom.iTimer = setInterval(function() {
			
			var iBtn = true;
						
			for ( var attr in json ) {
								
				var iTarget = json[attr];
				
				if (attr === 'opacity') {
					iCur = Math.round(css( oDom, 'opacity' ) * 100);
				} else {
					iCur = parseInt(css(oDom, attr));
				}
				
				iSpeed = ( iTarget - iCur ) / 8;
				iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
				
				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';
					}
				}
				
			}
			
			if (iBtn) {
				clearInterval(oDom.iTimer);
				fn && fn.call(oDom);
			}
			
		}, 30);
	}
	
	function css(oDom, attr) {
		if (oDom.currentStyle) {
			return oDom.currentStyle[attr];
		} else {
			return getComputedStyle(oDom, false)[attr];
		}
	}

我們來通過我們的運動框架來讓元素發生一個緩沖運動。

<!DOCTYPE html>
<html lang="en">
<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 () {
        // startMove(oDiv1,{
        //     width:800,
        //     height:800
        // });
        // 也可以嘗試回調多屬性操作
        startMove(oDiv1,{
            width:200
        },function () {
            startMove(oDiv1,{height:200})
        })
    };

    function startMove(oDom, json, fn) {
        clearInterval(oDom.iTimer);
        var iCur = 0;
        var iSpeed = 0;

        oDom.iTimer = setInterval(function () {

            var iBtn = true;

            for (var attr in json) {

                var iTarget = json[attr];

                if (attr === 'opacity') {
                    iCur = Math.round(css(oDom, 'opacity') * 100);
                } else {
                    iCur = parseInt(css(oDom, attr));
                }

                iSpeed = (iTarget - iCur) / 8;
                iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);

                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';
                    }
                }

            }

            if (iBtn) {
                clearInterval(oDom.iTimer);
                fn && fn.call(oDom);
            }

        }, 30);
    }

    function css(oDom, attr) {
        if (oDom.currentStyle) {
            return oDom.currentStyle[attr];
        } else {
            return getComputedStyle(oDom, false)[attr];
        }
    }
</script>
</html>

案例1 多圖片展開收縮

當我們鼠標放在圖片上的時候,圖片會放大,當我們的鼠標移開圖片的時候,圖片縮小。

首先我們先來把案例的基本布局完成:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        body {
                margin: 0;
                padding: 0;
            }
    
            ul, li {
                list-style: none;
            }
    
            #list {
                padding:0;
                width: 330px;
                margin:100px auto 0;
                position: relative;
            }
    
            #list li {
                width: 100px;
                height: 100px;
                float: left;
                background-color:red;
                margin: 10px 0 0 10px;
            }
            li:nth-of-type(1) {
                background: url("./img/01.jpeg") ;
                background-size: 100%;
            }
            li:nth-of-type(2) {
                background: url("./img/02.jpeg") ;
                background-size: 100%;
            }
            li:nth-of-type(3) {
                background: url("./img/03.jpeg") ;
                background-size: 100%;
            }
            li:nth-of-type(4) {
                background: url("./img/04.jpeg") ;
                background-size: 100%;
            }
            li:nth-of-type(5) {
                background: url("./img/05.jpeg") ;
                background-size: 100%;
            }
            li:nth-of-type(6) {
                background: url("./img/06.jpeg") ;
                background-size: 100%;
            }
            li:nth-of-type(7) {
                background: url("./img/07.jpeg") ;
                background-size: 100%;
            }
            li:nth-of-type(8) {
                background: url("./img/08.jpeg") ;
                background-size: 100%;
            }
            li:nth-of-type(9) {
                background: url("./img/09.jpeg") ;
                background-size: 100%;
            }
    </style>
</head>
<body>
<ul id="list">
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
</ul>
</body>
</html>

上面的代碼,我們完成了基本的布局,下面我們要做的是,讓元素居中放大。

需要注意的是,如果僅僅直接更改圖片的寬度和高度,並不能實現居中布局,因為在網頁中,元素變化的點是以左上角開始的。所以我們想要實現
居中放大,就同時還需要使用定位,更改元素的定位(left,top),才能讓元素實現居中放大。如果圖片放大一倍,那么位移寬度和高度的一半。
並且元素一定是要定位的。但是我們在基本的布局時,應該采用浮動來進行定位,后面再通過js動態的將元素變為定位。

首先為了讓我們的元素在通過js之后又相對參考的點,所以說需要給圖片的父級,也就是ul設置一個相對定位的屬性:

#list {position: relative;}

接下來我們來實現一下js的效果:

let oUl = document.getElementById('list');
let oLis = oUl.getElementsByTagName('li');

for(let i=0;i<oLis.length;i++){
    // 開啟第一個循環來進行設置元素的位置
    // 元素相對於父級的位置其實就是offsetLeft 和 offsetTop的值
    oLis[i].style.left = oLis[i].offsetLeft + 'px';
    oLis[i].style.top = oLis[i].offsetTop + 'px';
}
for(let i = 0;i<oLis.length;i++){
    oLis[i].style.position = 'absolute';
    oLis[i].style.margin = '0px';
}

上面的代碼中,當js開始執行之后,圖片就已經從浮動布局變成了定位布局,但是從視覺上看卻沒有任何的改變,這正是我們想要的。

接下來我們來完成具體的變化效果。
把之前封裝的動畫框架存儲到一個獨立的js文件中,命名為move.js,並且引入這個js。引入之后調用我們的js文件並且傳入參數。

let oUl = document.getElementById('list');
let oLis = oUl.getElementsByTagName('li');

for(let i=0;i<oLis.length;i++){
    // 開啟第一個循環來進行設置元素的位置
    // 元素相對於父級的位置其實就是offsetLeft 和 offsetTop的值
    oLis[i].style.left = oLis[i].offsetLeft + 'px';
    oLis[i].style.top = oLis[i].offsetTop + 'px';
}
for(let i = 0;i<oLis.length;i++){
    oLis[i].style.position = 'absolute';
    oLis[i].style.margin = '0px';

    // 當鼠標懸浮到圖片身上的時候
    oLis[i].onmouseover = function(){
        startMove(this,{
            width:200,
            height:200,
            left:this.offsetLeft - 50,
            top : this.offsetTop - 50,
        })
    }

}

上面的代碼中,我們調用函數之后,當我們的鼠標放在圖片身上的時候,圖片已經可以發生變化,但是發現圖片的層級發生了問題,這個時候我們再來處理下層級的問題。
更改之后的代碼如下:

let oUl = document.getElementById('list');
let oLis = oUl.getElementsByTagName('li');

let zIndex = 1; // 設置一個變量用來存儲層級信息


for(let i=0;i<oLis.length;i++){
    // 開啟第一個循環來進行設置元素的位置
    // 元素相對於父級的位置其實就是offsetLeft 和 offsetTop的值
    oLis[i].style.left = oLis[i].offsetLeft + 'px';
    oLis[i].style.top = oLis[i].offsetTop + 'px';
}
for(let i = 0;i<oLis.length;i++){

    oLis[i].style.position = 'absolute';
    oLis[i].style.margin = '0px';

    // 當鼠標懸浮到圖片身上的時候
    oLis[i].onmouseover = function(){
        // 鼠標懸浮時設置層級信息
        this.style.zIndex = zIndex++; // 讓層級遞增
        
        startMove(this,{
            width:200,
            height:200,
            left:this.offsetLeft - 50,
            top : this.offsetTop - 50,
        })
    }

}

設置好層級信息之后,我們經過測試,再一次的發現了一個問題,當我們的鼠標不斷懸浮在相同一個元素的身上,發現元素被我們的鼠標趕走了

原因是,當我們的鼠標第二次懸浮元素身上的時候,元素再一次的在之前的基礎上進行減法,所以才會出現上面的現象。
解決方案:讓我們的元素每次-50不再是在現有位置上減,而是在初始的位置上進行減。

更改之后的代碼如下:

let oUl = document.getElementById('list');
let oLis = oUl.getElementsByTagName('li');

let zIndex = 1; // 設置一個變量用來存儲層級信息
let arr = []; // 創建一個數組,用來存儲元素位置的初始信息

for(let i=0;i<oLis.length;i++){
    // 如果將位置信息存儲到了數組當中,那么就不需要再此處第二次獲取了
    // 在此處將位置的初始信息存儲
    arr.push({ left:oLis[i].offsetLeft,top:oLis[i].offsetTop });
}
for(let i = 0;i<oLis.length;i++){

    // 方便我們在事件處理函數中,使用i這個計數,在此處聲明一個變量
    oLis[i].index = i;

    // 再此處進行賦值
    oLis[i].style.left = arr[i].left + 'px';
    oLis[i].style.top = arr[i].top + 'px';

    oLis[i].style.position = 'absolute';
    oLis[i].style.margin = '0px';

    // 當鼠標懸浮到圖片身上的時候
    oLis[i].onmouseover = function(){
        // 鼠標懸浮時設置層級信息
        this.style.zIndex = zIndex++; // 讓層級遞增

        startMove(this,{
            width:200,
            height:200,
            left:arr[this.index].left - 50,
            top : arr[this.index].top - 50,
        })
    }

}

經過檢測之后,沒有問題,那么下面再來綁定鼠標移出的事件以及事件處理函數即可。

完整代碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        body {
            margin: 0;
            padding: 0;
        }

        ul, li {
            list-style: none;
        }

        #list {
            padding:0;
            width: 330px;
            margin:100px auto 0;
            position: relative;
        }

        #list li {
            width: 100px;
            height: 100px;
            float: left;
            background-color:red;
            margin: 10px 0 0 10px;
        }
        li:nth-of-type(1) {
            background: url("./img/01.jpeg") ;
            background-size: 100%;
        }
        li:nth-of-type(2) {
            background: url("./img/02.jpeg") ;
            background-size: 100%;
        }
        li:nth-of-type(3) {
            background: url("./img/03.jpeg") ;
            background-size: 100%;
        }
        li:nth-of-type(4) {
            background: url("./img/04.jpeg") ;
            background-size: 100%;
        }
        li:nth-of-type(5) {
            background: url("./img/05.jpeg") ;
            background-size: 100%;
        }
        li:nth-of-type(6) {
            background: url("./img/06.jpeg") ;
            background-size: 100%;
        }
        li:nth-of-type(7) {
            background: url("./img/07.jpeg") ;
            background-size: 100%;
        }
        li:nth-of-type(8) {
            background: url("./img/08.jpeg") ;
            background-size: 100%;
        }
        li:nth-of-type(9) {
            background: url("./img/09.jpeg") ;
            background-size: 100%;
        }
    </style>
</head>
<body>
<ul id="list">
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
</ul>
</body>
<script src="move.js"></script>
<script>
    let oUl = document.getElementById('list');
    let oLis = oUl.getElementsByTagName('li');

    let zIndex = 1; // 設置一個變量用來存儲層級信息
    let arr = []; // 創建一個數組,用來存儲元素位置的初始信息

    for(let i=0;i<oLis.length;i++){
        // 如果將位置信息存儲到了數組當中,那么就不需要再此處第二次獲取了
        // 在此處將位置的初始信息存儲
        arr.push({ left:oLis[i].offsetLeft,top:oLis[i].offsetTop });
    }
    for(let i = 0;i<oLis.length;i++){

        // 方便我們在事件處理函數中,使用i這個計數,在此處聲明一個變量
        oLis[i].index = i;

        // 再此處進行賦值
        oLis[i].style.left = arr[i].left + 'px';
        oLis[i].style.top = arr[i].top + 'px';

        oLis[i].style.position = 'absolute';
        oLis[i].style.margin = '0px';

        // 當鼠標懸浮到圖片身上的時候
        oLis[i].onmouseover = function(){
            // 鼠標懸浮時設置層級信息
            this.style.zIndex = zIndex++; // 讓層級遞增

            startMove(this,{
                width:200,
                height:200,
                left:arr[this.index].left - 50,
                top : arr[this.index].top - 50,
            })
        };
        oLis[i].onmouseout = function(){

            startMove(this,{
                width:100,
                height:100,
                left:arr[this.index].left,
                top : arr[this.index].top,
            })
        }

    }
</script>
</html>

運動的留言本

什么是運動的留言本呢?其實非常簡單,就是當我們在輸入域中輸入內容之后,點擊添加,內容會在右側的留言框內一點一點的出現。

首先,我們先來實現基本的布局。

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>運動的留言本</title>
<style>
	#ul1 {
		margin:0;
		padding:10px;
		position:absolute;
		right:10px;
		top:8px;
		width:700px;
		height:500px;
		border:1px solid #222;
		overflow:auto;
	}
	
	li {
		line-height:28px;
		border-bottom:1px dotted #ccc;
		list-style:none;
		word-break:break-all;
		overflow:hidden;
	}
	


</style>
</head>

<body>

<textarea id="content" rows="10" cols="50"></textarea>
<button id="btn">點擊留言</button>
<ul id="ul1">
	<li>aaaaaaaa</li>
    <li>aaaaaaaa</li>
    <li>aaaaaaaa</li>
    <li>aaaaaaaa</li>
    <li>aaaaaaaa</li>
    <li>aaaaaaaa</li>
    <li>aaaaaaaa</li>
    <li>aaaaaaaa</li>
</ul>
</body>
</html>

實現了基本的布局之后,我們先來實現js的基本功能,就是點擊留言,添加留言內容。

js代碼如下:

// 先來獲取頁面的元素
let oBtn = document.getElementById("btn");
let oContent = document.getElementById("content");
let oUl = document.getElementById('ul1');

oBtn.onclick = function () {
    // 創建節點
    let oLi = document.createElement('li');
    oLi.innerHTML = oContent.value;

    if(oUl.children[0]) {
        oUl.insertBefore(oLi,oUl.children[0]); // 如果里面存在內容,就把消息插入到最上面
    }else {
        oUl.appendChild(oLi);
    }
};

實現了發布內容的功能,我們再來實現讓內容逐漸的出現。

oBtn.onclick = function () {
    // 創建節點
    let oLi = document.createElement('li');
    oLi.innerHTML = oContent.value;

    if(oUl.children[0]) {
        oUl.insertBefore(oLi,oUl.children[0]); // 如果里面存在內容,就把消息插入到最上面
    }else {
        oUl.appendChild(oLi);
    }

    let iHeight = parseInt( css(oLi,'height')); // 獲取元素實際的高度並且取整

    oLi.style.height = '0px';
    oLi.style.opacity = '0';
    oLi.style.filter = 'alpha(opacity=0)';

    startMove(oLi, {
        height : iHeight,
        opacity : 100
    });
};

完整代碼如下:

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>運動的留言本</title>
    <style>
        #ul1 {
            margin:0;
            padding:10px;
            position:absolute;
            right:10px;
            top:8px;
            width:700px;
            height:500px;
            border:1px solid #222;
            overflow:auto;
        }

        li {
            line-height:28px;
            border-bottom:1px dotted #ccc;
            list-style:none;
            word-break:break-all;
            overflow:hidden;
        }



    </style>
</head>
<script src="move.js"></script>
<script>
    window.onload = function() {
        // 先來獲取頁面的元素
        let oBtn = document.getElementById("btn");
        let oContent = document.getElementById("content");
        let oUl = document.getElementById('ul1');

        oBtn.onclick = function () {
            // 創建節點
            let oLi = document.createElement('li');
            oLi.innerHTML = oContent.value;

            if(oUl.children[0]) {
                oUl.insertBefore(oLi,oUl.children[0]); // 如果里面存在內容,就把消息插入到最上面
            }else {
                oUl.appendChild(oLi);
            }

            let iHeight = parseInt( css(oLi,'height')); // 獲取元素實際的高度並且取整

            oLi.style.height = '0px';
            oLi.style.opacity = '0';
            oLi.style.filter = 'alpha(opacity=0)';

            startMove(oLi, {
                height : iHeight,
                opacity : 100
            });
        };


    };
</script>
<body>

<textarea id="content" rows="10" cols="50"></textarea>
<button id="btn">點擊留言</button>
<ul id="ul1">
    <!--<li>aaaaaaaa</li>-->
    <!--<li>aaaaaaaa</li>-->
    <!--<li>aaaaaaaa</li>-->
    <!--<li>aaaaaaaa</li>-->
    <!--<li>aaaaaaaa</li>-->
    <!--<li>aaaaaaaa</li>-->
    <!--<li>aaaaaaaa</li>-->
    <!--<li>aaaaaaaa</li>-->
</ul>
</body>
</html>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM