定時呼叫與延時呼叫
定時呼叫與延時呼叫
1、定時呼叫
JS 的程式的執行速度是非常非常快的如果希望一段程式,可以每間隔一段時間執行一次,可以使用定時呼叫
setInterval()
定時呼叫,可以將一個函式,每隔一段時間執行一次
引數:
- 回撥函式,該函式會每隔一段時間被呼叫一次
- 每次呼叫間隔的時間,單位是毫秒
返回值:返回一個Number
型別的資料,這個數字用來作為定時器的唯一標識
JAVASCRIPTvar num = 1; info = document.getElementById("info"); setInterval(function(){ info.innerHTML = num++; }, 1000);
setInterval(function(){
info.innerHTML = num++;
}, 100);
clearInterval()
可以用來關閉一個定時器,方法中需要一個定時器的標識作為引數,這樣將關閉標識對應的定時器
JAVASCRIPTvar timer = setInterval(function(){
info.innerHTML = num++;
if(num > 100){
clearInterval(timer);
}
}, 10);
<練習1:定時圖片切換>
HTML 程式碼
HTML<img src="img/1.jpg" id="img" /><br> <button type="button" id="btnStart">開始</button> <button type="button" id="btnEnd">結束</button>
JS 程式碼
JAVASCRIPTvar btnStart = document.getElementById("btnStart"); var btnEnd = document.getElementById("btnEnd"); var img = document.getElementById("img"); // 設定輪播圖片陣列 var imgArr = ["img/1.jpg", "img/2.jpg", "img/3.jpg", "img/4.jpg", "img/5.jpg"]; // 設定輪播圖片索引 var index = 0; // 為開始按鈕繫結單擊響應函式 var timer; btnStart.onclick = function() { // 清除上一個定時器 clearInterval(timer); // 設定定時器 timer = setInterval(function() { // 切換圖片 img.src = imgArr[index++]; // 判斷索引是否超過最大索引 index %= imgArr.length; }, 500); }; // 為結束按鈕繫結單擊響應函式 btnEnd.onclick = function() { clearInterval(timer); };
效果
注意點一:迴圈切換圖片
當索引超過最大索引時,需要將索引重置,以達到輪播圖片之目的
JAVASCRIPT// if(index >= imgArr.length){
// index = 0;
// }
index %= imgArr.length;
注意點二:不點選開始,而直接點選結束
clearInterval()
可以接收任意引數
- 如果引數是一個有效的定時器的標識,則停止對應的定時器
- 如果引數不是一個有效的標識,則什麼也不做
即使沒有點開始,timer 為 undefined 也不會報錯,可以放心大膽的去使用
注意點三:多次點選開始按鈕導致切換速度過快問題
目前,我們每點選一次按鈕,就會開啟一個定時器,點選多次就會開啟多個定時器
這就導致圖片的切換速度過快,並且我們只能關閉最後一次開啟的定時器
在開啟定時器之前,需要將當前元素上的其他定時器關閉
<練習2:div移動優化>
JAVASCRIPT// 定義速度
var speed = 20;
// 定義方向
var direct = 0;
// 定時器只控制方向
setInterval(function(){
switch (direct) {
case 37:
box1.style.left = box1.offsetLeft - speed + "px";
break;
case 38:
box1.style.top = box1.offsetTop - speed + "px";
break;
case 39:
box1.style.left = box1.offsetLeft + speed + "px";
break;
case 40:
box1.style.top = box1.offsetTop + speed + "px";
break;
default:
break;
}
}, 50);
// 鍵盤按下控制速度
var box1 = document.getElementById("box1");
document.onkeydown = function(event) {
event = event || window.event;
// 修改速度
speed = event.ctrlKey ? 50 : 20;
// 捕獲方向
direct = event.keyCode;
return false;
};
// 鍵盤松開清空速度和方向
document.onkeyup = function(event){
direct = 0;
}
效果
優化思路
- 定時器控制方向,鍵盤按下控制速度和捕獲方向,鍵盤松開清空速度和方向
這就好比一輛汽車,速度就像汽車的油門,定時器就像汽車的方向盤,而鍵盤就像汽車的離合和檔位
油門一直在踩著,發動機就一直勻速運轉,就能保證速度一直存在,啟動或轉向就不會出現卡頓的現象
當鍵盤按下時,就是鬆離合換擋位;當鍵盤松開時,就是踩離合
不過,跟現實世界不同的是,JS 的世界沒有慣性,所以只要鬆離合,div 就不會再移動了
2、延時呼叫
setTimeout()、clearTimeout()
延時呼叫,延時呼叫一個函式不馬上執行,而是隔一段時間以後在執行,而且只會執行一次
延時呼叫和定時呼叫的區別:定時呼叫會執行多次,而延時呼叫只會執行一次
延時呼叫和定時呼叫實際上是可以互相代替的,在開發中可以根據自己需要去選擇
JAVASCRIPTvar num = 1;
var timer = setInterval(function(){
console.log(num++); // 1 2 3 4 5 ...
}, 1000);
var timer = setTimeout(function(){
console.log(num++); // 1
}, 1000);
clearTimeout(timer);
3、定時器的應用(一)
<練習:點選按鈕div移動>
HTML 程式碼
HTML<button type="button" id="btn1">點選按鈕box1向右移動</button>
<button type="button" id="btn2">點選按鈕box1向左移動</button>
<br><br>
<div id="box1"></div>
<div id="line"></div>
CSS 程式碼
CSS* {
margin: 0;
padding: 0;
}
#box1 {
width: 100px;
height: 100px;
background-color: red;
/* 開啟定位 */
position: absolute;
left: 0;
top: 0;
}
#line {
width: 0;
height: 1000px;
border: 1px solid black;
position: absolute;
top: 0;
left: 800px;
left: 0;
top: 200px;
}
JS 程式碼
JAVASCRIPT// 自定義相容所有瀏覽器獲取元素樣式的方法
function getStyle(obj, name) {
return window.getComputedStyle ? getComputedStyle(obj, null)[name] : obj.currentStyle[name];
}
window.onload = function() {
var timer;
var speed = 19;
var btn1 = document.getElementById("btn1");
var btn2 = document.getElementById("btn2");
var box1 = document.getElementById("box1");
btn1.onclick = function() {
// 清空上一個定時器
clearInterval(timer);
// 設定定時器
timer = setInterval(function() {
// 獲取舊值
var oldValue = parseInt(getStyle(box1, "left"));
// 獲取新值
var newValue = oldValue + speed;
// 當達到一定值時停下來
newValue = newValue > 800 ? 800 : newValue;
// 賦新值
box1.style.left = newValue + "px";
// 當值不再變化時,清空定時器
if(newValue == 800){
clearInterval(timer);
}
}, 50);
};
btn2.onclick = function() {
// 清空上一個定時器
clearInterval(timer);
// 設定定時器
timer = setInterval(function() {
// 獲取舊值
var oldValue = parseInt(getStyle(box1, "left"));
// 獲取新值
var newValue = oldValue - speed;
// 當達到一定值時停下來
newValue = newValue < 0 ? 0 : newValue;
// 賦新值
box1.style.left = newValue + "px";
// 當值不再變化時,清空定時器
if(newValue == 0){
clearInterval(timer);
}
}, 50);
};
}
優化1:封裝移動方法
JAVASCRIPT// 封裝移動方法
// obj:要執行動畫的物件
// target:執行動畫的目標位置
// speed:移動的速度(正數向右移動,負數向左移動)
var timer;
function move(obj, target, speed) {
clearInterval(timer);
timer = setInterval(function() {
var oldValue = parseInt(getStyle(obj, "left"));
var newValue = oldValue + speed;
newValue = speed > 0 ? (newValue > target ? target : newValue) : (newValue < target ? target : newValue);
obj.style.left = newValue + "px";
if (newValue == target) {
clearInterval(timer);
}
}, 50);
}
優化2:智慧判斷方向
JAVASCRIPTfunction move(obj, target, speed) {
clearInterval(timer);
var current = parseInt(getStyle(obj, "left"));
// 智慧判斷方向
speed = target < current ? -speed : speed;
timer = setInterval(function() {
var oldValue = parseInt(getStyle(obj, "left"));
var newValue = oldValue + speed;
newValue = speed > 0 ? (newValue > target ? target : newValue) : (newValue < target ? target : newValue);
obj.style.left = newValue + "px";
if (newValue == target) {
clearInterval(timer);
}
}, 50);
}
優化3:消除多個div影響
目前我們的定時器的標識由全域性變數 timer 儲存,所有的執行正在執行的定時器都在這個變數中儲存
那麼我們就不能定義全域性的了,而是需要向執行動畫的物件中新增一個 timer 屬性,用來儲存它自己的定時器的標識
JAVASCRIPTfunction move(obj, target, speed) {
clearInterval(obj.timer);
var current = parseInt(getStyle(obj, "left"));
// 智慧判斷速度的方向
speed = target < current ? -speed : speed;
obj.timer = setInterval(function() {
var oldValue = parseInt(getStyle(obj, "left"));
var newValue = oldValue + speed;
newValue = speed > 0 ? (newValue > target ? target : newValue) : (newValue < target ? target :
newValue);
obj.style.left = newValue + "px";
if (newValue == target) {
clearInterval(obj.timer);
}
}, 50);
}
這樣,執行動畫的物件之間就不會再互相產生影響了
優化4:支援多屬性
只需要將left
相關的屬性改為變數傳入
// obj:要執行動畫的物件
// attr:要執行動畫的樣式
// target:執行動畫的目標位置
// speed:移動的速度(正數向右移動,負數向左移動)
function move(obj, attr, target, speed) {
clearInterval(obj.timer);
var current = parseInt(getStyle(obj, attr));
// 智慧判斷速度的方向
speed = target < current ? -speed : speed;
obj.timer = setInterval(function() {
var oldValue = parseInt(getStyle(obj, attr));
var newValue = oldValue + speed;
newValue = speed > 0 ? (newValue > target ? target : newValue) : (newValue < target ? target :
newValue);
obj.style[attr] = newValue + "px";
if (newValue == target) {
clearInterval(obj.timer);
}
}, 50);
}
呼叫修改後的函式
JAVASCRIPTbtn1.onclick = function() {
move(box1, "left", 800, speed);
};
btn2.onclick = function() {
move(box1, "left", 0, speed);
};
btn3.onclick = function() {
move(box2, "top", 500, speed);
};
btn4.onclick = function() {
move(box3, "height", 500, speed);
};
優化5:添加回調函式
JAVASCRIPT// obj:要執行動畫的物件
// attr:要執行動畫的樣式
// target:執行動畫的目標位置
// speed:移動的速度(正數向右移動,負數向左移動)
// callback:回撥函式
function move(obj, attr, target, speed, callback) {
clearInterval(obj.timer);
var current = parseInt(getStyle(obj, attr));
// 智慧判斷速度的方向
speed = target < current ? -speed : speed;
obj.timer = setInterval(function() {
var oldValue = parseInt(getStyle(obj, attr));
var newValue = oldValue + speed;
newValue = speed > 0 ? (newValue > target ? target : newValue) : (newValue < target ? target :
newValue);
obj.style[attr] = newValue + "px";
if (newValue == target) {
clearInterval(obj.timer);
callback && callback(); // 即使不傳回調函式也不會報錯
}
}, 50);
}
呼叫回撥函式
JAVASCRIPTbtn4.onclick = function() {
move(box3, "height", 500, speed, function(){
move(box3, "width", 500, speed, function(){
move(box3, "height", 100, speed, function(){
move(box3, "width", 100, speed, function(){
});
});
});
});
};
優化6:封裝JS檔案
新建 js 資料夾,新建 tools.js 檔案,複製 move 相關方法
JAVASCRIPT// 自定義相容所有瀏覽器獲取元素樣式的方法
function getStyle(obj, name) {
return window.getComputedStyle ? getComputedStyle(obj, null)[name] : obj.currentStyle[name];
}
// 封裝移動方法
// obj:要執行動畫的物件
// attr:要執行動畫的樣式
// target:執行動畫的目標位置
// speed:移動的速度
// callback:回撥函式
function move(obj, attr, target, speed, callback) {
clearInterval(obj.timer);
var current = parseInt(getStyle(obj, attr));
speed = target < current ? -speed : speed;
obj.timer = setInterval(function() {
var oldValue = parseInt(getStyle(obj, attr));
var newValue = oldValue + speed;
newValue = speed > 0 ? (newValue > target ? target : newValue) : (newValue < target ? target :
newValue);
obj.style[attr] = newValue + "px";
if (newValue == target) {
clearInterval(obj.timer);
callback && callback();
}
}, 50);
}
最後再引入 js 檔案,大功告成
HTML<script src="js/tools.js" type="text/javascript" charset="utf-8"></script>
4、定時器應用(二)
<練習:輪播圖>
HTML 程式碼
HTML<div id="outer">
<ul id="pic-list">
<li><img src="img/1.jpg" /></li>
<li><img src="img/2.jpg" /></li>
<li><img src="img/3.jpg" /></li>
<li><img src="img/4.jpg" /></li>
<li><img src="img/5.jpg" /></li>
<li><img src="img/1.jpg" /></li>
</ul>
<ul id="nav-list">
<li><a href="javascript:;"></a></li>
<li><a href="javascript:;"></a></li>
<li><a href="javascript:;"></a></li>
<li><a href="javascript:;"></a></li>
<li><a href="javascript:;"></a></li>
</ul>
</div>
CSS 程式碼
CSS/* 去除瀏覽器預設樣式 */
* {
margin: 0;
padding: 0;
}
ul {
list-style: none;
}
a{
text-decoration: none;
}
/* 總體佈局 */
#outer {
width: 500px;
height: 332px;
margin: 100px auto;
box-shadow: 10px 10px 5px rgba(0, 0, 0, .2);
position: relative;
overflow: hidden;
}
/* 輪播圖片 */
#pic-list {
width: 2550px;
position: absolute;
left: 0;
top: 0;
/* 新增過渡效果 */
/* transition: left 0.3s; */
}
#pic-list li {
float: left;
margin-right: 10px;
}
/* 輪播按鈕 */
#nav-list {
position: absolute;
left: 187.5px;
bottom: 10px;
}
#nav-list li{
float: left;
width: 15px;
height: 15px;
background-color: red;
opacity: 0.5;
margin: 0 5px;
}
#nav-list a:hover{
background-color: black;
}
#nav-list li a{
display: block;
height: 100%;
line-height: 100%;
}
JS 程式碼
JSwindow.onload = function() {
var outer = document.getElementById("outer");
var picList = document.getElementById("pic-list");
var imgArr = document.getElementsByTagName("img");
var navList = document.getElementById("nav-list");
var aArr = document.getElementsByTagName("a");
var index = 0;
// 動態調整picList的寬度,以自適應圖片的數量變化
picList.style.width = (outer.clientWidth + 10) * imgArr.length + "px";
// 動態調整navList的水平偏移量
navList.style.left = (outer.clientWidth - 25 * aArr.length) / 2 + "px";
// 設定第一個輪播按鈕懸浮顏色
aArr[index].style.backgroundColor = "black";
// 點選按鈕切換圖片
for (var i = 0; i < aArr.length; i++) {
aArr[i].index = i;
aArr[i].onclick = function() {
// 設定index
index = this.index;
// 清空定時器
clearInterval(timer);
move(picList, "left", -(outer.clientWidth + 10) * index, 100, function() {
// 開啟定時器
autoSwitch();
});
setColor();
};
}
autoSwitch();
// 自動切換圖片
var timer;
function autoSwitch() {
timer = setInterval(function() {
index++;
index %= imgArr.length;
move(picList, "left", -(outer.clientWidth + 10) * index, 100, function() {
if (index >= imgArr.length - 1) {
picList.style.left = "0px";
}
setColor();
});
}, 3000);
}
// 設定輪播按鈕懸浮顏色
function setColor() {
// 重置所有輪播按鈕顏色:由於修改的是內聯樣式,優先順序較高,會把css樣式覆蓋,導致懸浮效果失效
// 那麼這裡不使用內聯樣式,將其置為空,這樣就會找css樣式
for (var i = 0; i < aArr.length; i++) {
aArr[i].style.backgroundColor = "";
}
index %= aArr.length;
aArr[index].style.backgroundColor = "black";
}
};
5、類的操作
修改class屬性
HTML 程式碼
HTML<button type="button" id="btn1">點選按鈕修改box1樣式</button>
<br><br>
<div id="box1" class="b1"></div>
CSS 程式碼
CSS.b1{
width: 100px;
height: 100px;
background-color: red;
}
JS 程式碼
JSbox1.style.width = "200px";
box1.style.height = "200px";
box1.style.backgroundColor = "yellow";
通過style
屬性來修改元素的樣式,每修改一個樣式,瀏覽器就需要重新渲染一次頁面
這樣執行的效能是比較差的,而且這種形式當我們要修改多個樣式時,也不太方便
那怎麼辦呢?
我們可以先事先定義好一個 class 屬性,裡面寫好我們需要變化的樣式
CSS.b2{
width: 200px;
height: 200px;
background-color: yellow;
}
然後在 JS 中修改className
屬性即可
box1.className = "b2";
效果是一樣的
我們可以通過修改元素的class
屬性來間接的修改樣式
這樣一來,我們只需要修改一次,即可同時修改多個樣式
瀏覽器只需要重新渲染頁面一次,效能比較好,並且這種方式,可以使表現和行為進一步的分離
新增class屬性
我們可以在此樣式基礎之上,定義一個函式,用來向一個元素中新增指定的 class 屬性值
JS// 引數:
// obj 要新增class屬性的元素
// cn 要新增的class值
function addClass(obj, cn){
obj.className += " " + cn;
}
JS
//.b3{
// position: absolute;
// left: 100px;
//}
addClass(box1, "b3");
但是也存在一個問題,雖然從效果上來看沒有什麼不同,但多次點選後會重複新增相同的 class 屬性,而這個操作是多餘的
我們就需要在寫一個函式來判斷是否已經存在 class 屬性
JSfunction hasClass(obj, cn) {
// return obj.className.indexOf(cn) != -1;
var reg = new RegExp("\\b"+cn+"\\b");
return reg.test(obj.className);
}
function addClass(obj, cn) {
if (!hasClass(obj, cn)) {
obj.className += " " + cn;
}
}
刪除class屬性
刪除一個元素中的指定的 class 屬性
JSfunction removeClass(obj, cn) {
var reg = new RegExp("\\b" + cn + "\\b");
obj.className = obj.className.replace(reg, "");
}
切換class屬性
JS// toggleClass可以用來切換一個類
// 如果元素中具有該類,則刪除
// 如果元素中沒有該類,則新增
function toggleClass(obj, cn) {
if (hasClass(obj, cn)) {
removeClass(obj, cn);
} else {
addClass(obj, cn);
}
}
<練習:二級選單>
HTML 程式碼
HTML<div id="my_menu" class="sdmenu">
<div>
<span class="menuSpan">線上工具</span>
<a href="#">影象優化</a>
<a href="#">收藏夾圖示生成器</a>
<a href="#">郵件</a>
<a href="#">htaccess密碼</a>
<a href="#">梯度影象</a>
<a href="#">按鈕生成器</a>
</div>
<div class="collapsed">
<span class="menuSpan">支援我們</span>
<a href="#">推薦我們</a>
<a href="#">連結我們</a>
<a href="#">網路資源</a>
</div>
<div class="collapsed">
<span class="menuSpan">合作伙伴</span>
<a href="#">JavaScript工具包</a>
<a href="#">CSS驅動</a>
<a href="#">CodingForums</a>
<a href="#">CSS例子</a>
</div>
<div class="collapsed">
<span class="menuSpan">測試電流</span>
<a href="#">Current or not</a>
<a href="#">Current or not</a>
<a href="#">Current or not</a>
<a href="#">Current or not</a>
</div>
</div>
CSS 程式碼
CSS@charset "utf-8";
/* sdmenu */
div.sdmenu {
width: 150px;
margin: 0 auto;
font-family: Arial, sans-serif;
font-size: 12px;
padding-bottom: 10px;
background: url(bottom.gif) no-repeat right bottom;
color: #fff;
}
div.sdmenu div {
background: url(title.gif) repeat-x;
overflow: hidden;
}
div.sdmenu div:first-child {
background: url(toptitle.gif) no-repeat;
}
div.sdmenu div.collapsed {
height: 25px;
}
div.sdmenu div span {
display: block;
height: 15px;
line-height: 15px;
overflow: hidden;
padding: 5px 25px;
font-weight: bold;
color: white;
background: url(expanded.gif) no-repeat 10px center;
cursor: pointer;
border-bottom: 1px solid #ddd;
}
div.sdmenu div.collapsed span {
background-image: url(collapsed.gif);
}
div.sdmenu div a {
padding: 5px 10px;
background: #eee;
display: block;
border-bottom: 1px solid #ddd;
color: #066;
}
div.sdmenu div a.current {
background: #ccc;
}
div.sdmenu div a:hover {
background: #066 url(linkarrow.gif) no-repeat right center;
color: #fff;
text-decoration: none;
}
JS 程式碼
JS// 為相容IE8,用querySelectorAll
var menuSpan = document.querySelectorAll(".menuSpan");
var myMenu = document.getElementById("my_menu");
var thisNode;
// 定義一個變數,來儲存當前開啟的選單
var openNode = menuSpan[0].parentNode;
for (var i = 0; i < menuSpan.length; i++) {
// 一級選單繫結單擊響應函式
menuSpan[i].onclick = function() {
thisNode = this.parentNode;
// 切換collapsed的class屬性
toggleClass(thisNode, "collapsed");
// 開啟選單以後,應該關閉之前開啟的選單
if (openNode != thisNode && !hasClass(openNode, "collapsed")) {
// 為了可以統一處理動畫過渡效果,我們希望在這將addClass改為toggleClass
// addClass(openNode, "collapsed");
// 此處toggleClass()不需要有移除的功能
toggleClass(openNode, "collapsed");
}
openNode = thisNode;
};
}
新增動畫的過渡效果
JSvar beginHeight;
var endHeight;
for (var i = 0; i < menuSpan.length; i++) {
menuSpan[i].onclick = function() {
thisNode = this.parentNode;
// 切換前高度
beginHeight = thisNode.offsetHeight;
// 切換
toggleClass(thisNode, "collapsed");
// 切換後高度
endHeight = thisNode.offsetHeight;
// 動畫執行前內聯高度
thisNode.style.height = beginHeight + "px";
// 設定動畫效果
move(thisNode, "height", endHeight, 30, function(){
});
if (openNode != thisNode && !hasClass(openNode, "collapsed")) {
toggleClass(openNode, "collapsed");
}
openNode = thisNode;
};
}
因為我們執行動畫前添加了一個內聯高度,而內聯屬性的優先順序是最高的
當新增collapsed
的 class 屬性後不會起作用,因此同時需要在動畫執行完畢後去除內聯樣式
move(thisNode, "height", endHeight, 30, function(){
// 動畫執行後內聯高度
thisNode.style.height = "";
});
我們只對展開添加了動畫效果,摺疊時並沒有新增動畫
因為新增動畫的邏輯是一致的,所以這裡我們可以封裝一個函式,用來執行帶有動畫效果的摺疊和展開動作
JS// 帶有動畫效果的摺疊和展開動作
function toggleMenu(obj) {
// 切換前高度
beginHeight = obj.offsetHeight;
// 切換
toggleClass(obj, "collapsed");
// 切換後高度
endHeight = obj.offsetHeight;
// 動畫執行前內聯高度
obj.style.height = beginHeight + "px";
// 設定動畫效果
move(obj, "height", endHeight, 30, function() {
// 動畫執行後內聯高度
obj.style.height = "";
});
}
呼叫 toggleMenu 函式
JSfor (var i = 0; i < menuSpan.length; i++) {
menuSpan[i].onclick = function() {
thisNode = this.parentNode;
// 切換
toggleMenu(thisNode);
// 關閉其他div
if (openNode != thisNode && !hasClass(openNode, "collapsed")) {
toggleMenu(openNode);
}
openNode = thisNode;
};
}