W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
事件 是某事發(fā)生的信號。所有的 DOM 節(jié)點都生成這樣的信號(但事件不僅限于 DOM)。
這是最有用的 DOM 事件的列表,你可以瀏覽一下:
鼠標事件:
click
? —— 當鼠標點擊一個元素時(觸摸屏設(shè)備會在點擊時生成)。contextmenu
? —— 當鼠標右鍵點擊一個元素時。mouseover
? / ?mouseout
? —— 當鼠標指針移入/離開一個元素時。mousedown
? / ?mouseup
? —— 當在元素上按下/釋放鼠標按鈕時。mousemove
? —— 當鼠標移動時。鍵盤事件:
keydown
? 和 ?keyup
? —— 當按下和松開一個按鍵時。表單(form)元素事件:
submit
? —— 當訪問者提交了一個 ?<form>
? 時。focus
? —— 當訪問者聚焦于一個元素時,例如聚焦于一個 ?<input>
?。Document 事件:
DOMContentLoaded
? —— 當 HTML 的加載和處理均完成,DOM 被完全構(gòu)建完成時。CSS 事件:
transitionend
? —— 當一個 CSS 動畫完成時。還有很多其他事件。我們將在下一章中詳細介紹具體事件。
為了對事件作出響應(yīng),我們可以分配一個 處理程序(handler)—— 一個在事件發(fā)生時運行的函數(shù)。
處理程序是在發(fā)生用戶行為(action)時運行 JavaScript 代碼的一種方式。
有幾種分配處理程序的方法。讓我們來看看,從最簡單的開始。
處理程序可以設(shè)置在 HTML 中名為 on<event>
的特性(attribute)中。
例如,要為一個 input
分配一個 click
處理程序,我們可以使用 onclick
,像這樣;
<input value="Click me" onclick="alert('Click!')" type="button">
在鼠標點擊時,onclick
中的代碼就會運行。
請注意,在 onclick
中,我們使用單引號,因為特性本身使用的是雙引號。如果我們忘記了代碼是在特性中的,而使用了雙引號,像這樣:onclick="alert("Click!")"
,那么它就無法正確運行。
HTML 特性不是編寫大量代碼的好位置,因此我們最好創(chuàng)建一個 JavaScript 函數(shù),然后在 HTML 特性中調(diào)用這個函數(shù)。
我們知道,HTML 特性名是大小寫不敏感的,所以 ONCLICK
和 onClick
以及 onCLICK
都一樣可以運行。但是特性通常是小寫的:onclick
。
我們可以使用 DOM 屬性(property)on<event>
來分配處理程序。
例如 elem.onclick
:
<input id="elem" type="button" value="Click me">
<script>
elem.onclick = function() {
alert('Thank you');
};
</script>
如果一個處理程序是通過 HTML 特性(attribute)分配的,那么隨后瀏覽器讀取它,并從特性的內(nèi)容創(chuàng)建一個新函數(shù),并將這個函數(shù)寫入 DOM 屬性(property)。
因此,這種方法實際上與前一種方法相同。
這兩段代碼工作相同:
<input type="button" onclick="alert('Click!')" value="Button">
<input type="button" id="button" value="Button">
<script>
button.onclick = function() {
alert('Click!');
};
</script>
在第一個例子中,button.onclick
是通過 HTML 特性(attribute)初始化的,而在第二個例子中是通過腳本初始化的。這是它們唯一的不同之處。
因為這里只有一個 onclick
屬性,所以我們無法分配更多事件處理程序。
在下面這個示例中,我們使用 JavaScript 添加了一個處理程序,覆蓋了現(xiàn)有的處理程序:
<input type="button" id="elem" onclick="alert('Before')" value="Click me">
<script>
elem.onclick = function() { // 覆蓋了現(xiàn)有的處理程序
alert('After'); // 只會顯示此內(nèi)容
};
</script>
要移除一個處理程序 —— 賦值 elem.onclick = null
。
處理程序中的 this
的值是對應(yīng)的元素。就是處理程序所在的那個元素。
下面這行代碼中的 button
使用 this.innerHTML
來顯示它的內(nèi)容:
<button onclick="alert(this.innerHTML)">Click me</button>
如果你剛開始寫事件 —— 請注意一些細微之處。
我們可以將一個現(xiàn)存的函數(shù)用作處理程序:
function sayThanks() {
alert('Thanks!');
}
elem.onclick = sayThanks;
但要注意:函數(shù)應(yīng)該是以 sayThanks
的形式進行賦值,而不是 sayThanks()
。
// 正確
button.onclick = sayThanks;
// 錯誤
button.onclick = sayThanks();
如果我們添加了括號,那么 sayThanks()
就變成了一個函數(shù)調(diào)用。所以,最后一行代碼實際上獲得的是函數(shù)執(zhí)行的 結(jié)果,即 undefined
(因為這個函數(shù)沒有返回值)。此代碼不會工作。
……但在標記(markup)中,我們確實需要括號:
<input type="button" id="button" onclick="sayThanks()">
這個區(qū)別很容易解釋。當瀏覽器讀取 HTML 特性(attribute)時,瀏覽器將會使用 特性中的內(nèi)容 創(chuàng)建一個處理程序。
所以,標記(markup)會生成下面這個屬性:
button.onclick = function() {
sayThanks(); // <-- 特性(attribute)中的內(nèi)容變到了這里
};
不要對處理程序使用 setAttribute
。
這樣的調(diào)用會失效:
// 點擊 <body> 將產(chǎn)生 error,
// 因為特性總是字符串的,函數(shù)變成了一個字符串
document.body.setAttribute('onclick', function() { alert(1) });
DOM 屬性是大小寫敏感的。
將處理程序分配給 elem.onclick
,而不是 elem.ONCLICK
,因為 DOM 屬性是大小寫敏感的。
上述分配處理程序的方式的根本問題是 —— 我們不能為一個事件分配多個處理程序。
假設(shè),在我們點擊了一個按鈕時,我們代碼中的一部分想要高亮顯示這個按鈕,另一部分則想要顯示一條消息。
我們想為此事件分配兩個處理程序。但是,新的 DOM 屬性將覆蓋現(xiàn)有的 DOM 屬性:
input.onclick = function() { alert(1); }
// ...
input.onclick = function() { alert(2); } // 替換了前一個處理程序
Web 標準的開發(fā)者很早就了解到了這一點,并提出了一種使用特殊方法 addEventListener
和 removeEventListener
來管理處理程序的替代方法。它們沒有這樣的問題。
添加處理程序的語法:
element.addEventListener(event, handler[, options]);
?event
?
事件名,例如:?"click"
?。
?handler
?
處理程序。
?options
?
具有以下屬性的附加可選對象:
once
?:如果為 ?true
?,那么會在被觸發(fā)后自動刪除監(jiān)聽器。capture
?:事件處理的階段,我們稍后將在 冒泡和捕獲 一章中介紹。由于歷史原因,?options
? 也可以是 ?false/true
?,它與 ?{capture: false/true}
? 相同。passive
?:如果為 ?true
?,那么處理程序?qū)⒉粫{(diào)用 ?preventDefault()
?,我們稍后將在 瀏覽器默認行為 一章中介紹。要移除處理程序,可以使用 removeEventListener
:
element.removeEventListener(event, handler[, options]);
移除需要相同的函數(shù)
要移除處理程序,我們需要傳入與分配的函數(shù)完全相同的函數(shù)。
這不起作用:
elem.addEventListener( "click" , () => alert('Thanks!')); // .... elem.removeEventListener( "click", () => alert('Thanks!'));
處理程序不會被移除,因為
removeEventListener
獲取了另一個函數(shù) —— 使用相同的代碼,但這并不起作用,因為它是一個不同的函數(shù)對象。
下面是正確方法:
function handler() { alert( 'Thanks!' ); } input.addEventListener("click", handler); // .... input.removeEventListener("click", handler);
請注意 —— 如果我們不將函數(shù)存儲在一個變量中,那么我們就無法移除它。由
addEventListener
分配的處理程序?qū)o法被“讀回”。
多次調(diào)用 addEventListener
允許添加多個處理程序,如下所示:
<input id="elem" type="button" value="Click me"/>
<script>
function handler1() {
alert('Thanks!');
};
function handler2() {
alert('Thanks again!');
}
elem.onclick = () => alert("Hello");
elem.addEventListener("click", handler1); // Thanks!
elem.addEventListener("click", handler2); // Thanks again!
</script>
正如我們在上面這個例子中所看到的,我們可以 同時 使用 DOM 屬性和 addEventListener
來設(shè)置處理程序。但通常我們只使用其中一種方式。
對于某些事件,只能通過 ?
addEventListener
? 設(shè)置處理程序有些事件無法通過 DOM 屬性進行分配。只能使用
addEventListener
。
例如,
DOMContentLoaded
事件,該事件在文檔加載完成并且 DOM 構(gòu)建完成時觸發(fā)。
// 永遠不會運行 document.onDOMContentLoaded = function() { alert("DOM built"); };
// 這種方式可以運行 document.addEventListener("DOMContentLoaded", function() { alert("DOM built"); });
所以
addEventListener
更通用。雖然這樣的事件是特例而不是規(guī)則。
為了正確處理事件,我們需要更深入地了解發(fā)生了什么。不僅僅是 “click” 或 “keydown”,還包括鼠標指針的坐標是什么?按下了哪個鍵?等等。
當事件發(fā)生時,瀏覽器會創(chuàng)建一個 event
對象,將詳細信息放入其中,并將其作為參數(shù)傳遞給處理程序。
下面是一個從 event
對象獲取鼠標指針的坐標的示例:
<input type="button" value="Click me" id="elem">
<script>
elem.onclick = function(event) {
// 顯示事件類型、元素和點擊的坐標
alert(event.type + " at " + event.currentTarget);
alert("Coordinates: " + event.clientX + ":" + event.clientY);
};
</script>
event
對象的一些屬性:
?event.type
?
事件類型,這里是 ?"click"
?。
?event.currentTarget
?
處理事件的元素。這與 ?this
? 相同,除非處理程序是一個箭頭函數(shù),或者它的 ?this
? 被綁定到了其他東西上,之后我們就可以從 ?event.currentTarget
? 獲取元素了。
?event.clientX
? / ?event.clientY
?
指針事件(pointer event)的指針的窗口相對坐標。
還有很多屬性。其中很多都取決于事件類型:鍵盤事件具有一組屬性,指針事件具有另一組屬性,稍后我們將詳細討論不同事件,那時我們再對其進行詳細研究。
?
event
? 對象在 HTML 處理程序中也可用如果我們在 HTML 中分配了一個處理程序,那么我們也可以使用
event
對象,像這樣:
<input type="button" onclick="alert(event.type)" value="Event type">
這是可能的,因為當瀏覽器讀取特性(attribute)時,它會創(chuàng)建像這樣的處理程序:
function(event) { alert(event.type) }
。也就是說:它的第一個參數(shù)是"event"
,而主體取自于該特性(attribute)。
我們不僅可以分配函數(shù),還可以使用 addEventListener
將一個對象分配為事件處理程序。當事件發(fā)生時,就會調(diào)用該對象的 handleEvent
方法。
例如:
<button id="elem">Click me</button>
<script>
let obj = {
handleEvent(event) {
alert(event.type + " at " + event.currentTarget);
}
};
elem.addEventListener('click', obj);
</script>
正如我們所看到的,當 addEventListener
接收一個對象作為處理程序時,在事件發(fā)生時,它就會調(diào)用 obj.handleEvent(event)
來處理事件。
我們也可以對此使用一個類:
<button id="elem">Click me</button>
<script>
class Menu {
handleEvent(event) {
switch(event.type) {
case 'mousedown':
elem.innerHTML = "Mouse button pressed";
break;
case 'mouseup':
elem.innerHTML += "...and released.";
break;
}
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
這里,同一個對象處理兩個事件。請注意,我們需要使用 addEventListener
來顯式設(shè)置事件,以指明要監(jiān)聽的事件。這里的 menu
對象只監(jiān)聽 mousedown
和 mouseup
,而沒有任何其他類型的事件。
handleEvent
方法不必通過自身完成所有的工作。它可以調(diào)用其他特定于事件的方法,例如:
<button id="elem">Click me</button>
<script>
class Menu {
handleEvent(event) {
// mousedown -> onMousedown
let method = 'on' + event.type[0].toUpperCase() + event.type.slice(1);
this[method](event);
}
onMousedown() {
elem.innerHTML = "Mouse button pressed";
}
onMouseup() {
elem.innerHTML += "...and released.";
}
}
let menu = new Menu();
elem.addEventListener('mousedown', menu);
elem.addEventListener('mouseup', menu);
</script>
現(xiàn)在事件處理程序已經(jīng)明確地分離了出來,這樣更容易進行代碼編寫和后續(xù)維護。
這里有 3 種分配事件處理程序的方式:
onclick="..."
?。elem.onclick = function
?。elem.addEventListener(event, handler[, phase])
? 用于添加,?removeEventListener
? 用于移除。HTML 特性很少使用,因為 HTML 標簽中的 JavaScript 看起來有些奇怪且陌生。而且也不能在里面寫太多代碼。
DOM 屬性用起來還可以,但我們無法為特定事件分配多個處理程序。在許多場景中,這種限制并不嚴重。
最后一種方式是最靈活的,但也是寫起來最長的。有少數(shù)事件只能使用這種方式。例如 transtionend
和 DOMContentLoaded
(上文中講到了)。addEventListener
也支持對象作為事件處理程序。在這種情況下,如果發(fā)生事件,則會調(diào)用 handleEvent
方法。
無論你如何分類處理程序 —— 它都會將獲得一個事件對象作為第一個參數(shù)。該對象包含有關(guān)所發(fā)生事件的詳細信息。
在下一章中,我們將學習更多關(guān)于一般事件和不同類型事件的內(nèi)容。
為 button
添加 JavaScript 代碼,使得 <div id="text">
在我們點擊該按鈕時消失。
創(chuàng)建一個按鈕,在被點擊時,隱藏自己。
可以在處理程序中使用 this
來引用“元素自身”:
<input type="button" onclick="this.hidden=true" value="Click to hide">
在變量中有一個按鈕。它上面沒有處理程序。
執(zhí)行以下代碼之后,哪些處理程序會在按鈕被點擊時運行?會顯示哪些 alert?
button.addEventListener("click", () => alert("1"));
button.removeEventListener("click", () => alert("1"));
button.onclick = () => alert(2);
答案:1
和 2
。
第一個處理程序會觸發(fā),因為它沒有被 removeEventListener
移除。要移除處理程序,我們需要傳遞正確的所分配的函數(shù)。在代碼中,傳遞了一個新的函數(shù),該函數(shù)看起來相同,但仍然是另一個函數(shù)。
要移除一個函數(shù)對象,我們需要存儲對它的引用,像這樣:
function handler() {
alert(1);
}
button.addEventListener("click", handler);
button.removeEventListener("click", handler);
無論 addEventListener
怎樣,button.onclick
處理程序都會觸發(fā)。
點擊球場中任意一點,讓球在球場中移動。
要求:
注意:
event.clientX/event.clientY
? 屬性來獲取點擊坐標。首先,我們需要選擇一種定位球的方法。
我們不能使用 position:fixed
,因為滾動頁面會造成球被移出球場。
所以我們應(yīng)該使用 position:absolute
,并且要使定位真正可靠,應(yīng)該使 field
自身具有 position:absolute
。
然后,球?qū)⑾鄬τ谇驁龆ㄎ唬?
#field {
width: 200px;
height: 150px;
position: relative;
}
#ball {
position: absolute;
left: 0; /* 相對于最接近的祖先(field) */
top: 0;
transition: 1s all; /* left/top 的 CSS 動畫,使球飛起來 */
}
接下來我們需要指定正確的 ball.style.left/top
。它們現(xiàn)在包含相對于球場的坐標。
這是示意圖:
我們有 event.clientX/clientY
—— 點擊位置的窗口相對坐標。
要獲取點擊位置的球場相對坐標 left
,我們可以減去球場左邊緣和邊框的寬度:
let left = event.clientX - fieldCoords.left - field.clientLeft;
通常情況下,ball.style.left
表示“元素的左邊緣”(球)。因此,如果我們將其指定為 left
,那么球的邊緣而非球的中心將位于鼠標光標下方。
我們需要將球向左移動球?qū)挾鹊囊话?,向上移動球高度的一半,以使其居中?nbsp;
所以,最后的 left
將是:
let left = event.clientX - fieldCoords.left - field.clientLeft - ball.offsetWidth/2;
使用相同的邏輯來計算垂直坐標。
請注意,球的寬度/高度必須在我們訪問 ball.offsetWidth
時就已知。應(yīng)該在 HTML 或 CSS 中指定。
創(chuàng)建一個在點擊時打開/折疊的菜單:
P.S. 源文檔的 HTML/CSS 將被修改。
首先,讓我們創(chuàng)建 HTML/CSS。
菜單是頁面上的一個獨立圖形組件,所以最好把它放入一個單獨的 DOM 元素中。
菜單項的列表可以被作為列表 ul/li
列出。
下面是示例結(jié)構(gòu):
<div class="menu">
<span class="title">Sweeties (click me)!</span>
<ul>
<li>Cake</li>
<li>Donut</li>
<li>Honey</li>
</ul>
</div>
我們對標題使用 <span>
,因為 <div>
有一個隱式的 display:block
,它會占據(jù) 100% 的水平寬度。
就像這樣:
<div style="border: solid red 1px" onclick="alert(1)">Sweeties (click me)!</div>
因此,如果我們在它上面設(shè)置 onclick
,那么它也會捕獲文本右側(cè)的點擊。
……由于 <span>
有一個隱式的 display: inline
,它恰好占據(jù)了足以容納所有文本的位置:
<span style="border: solid red 1px" onclick="alert(1)">Sweeties (click me)!</span>
切換菜單應(yīng)更改箭頭并顯示/隱藏菜單列表。
所有這些更改都可以通過 CSS 完美處理。在 JavaScript 中,我們應(yīng)該通過添加/移除 .open
類來標記菜單的當前狀態(tài)。
沒有它,菜單就會被關(guān)閉:
.menu ul {
margin: 0;
list-style: none;
padding-left: 20px;
display: none;
}
.menu .title::before {
content: '? ';
font-size: 80%;
color: green;
}
……有 .open
后,箭頭會改變,列表會出現(xiàn):
.menu.open .title::before {
content: '▼ ';
}
.menu.open ul {
display: block;
}
有一個消息列表。
使用 JavaScript 在每條消息的右上角添加一個關(guān)閉按鈕。
結(jié)果應(yīng)該如下所示:
我們可以使用 position:absolute
(并使窗格 position:relative
)或者 float:right
來添加按鈕。float:right
的好處是按鈕永遠都不會與文本重疊,但是 position:absolute
則提供了更大的自由度。選擇權(quán)在你自己手上。
然后,對于每個窗格(pane),代碼可以像這樣:
pane.insertAdjacentHTML("afterbegin", '<button class="remove-button">[x]</button>');
然后 <button>
變成了 pane.firstChild
,因此我們可以像這樣為它添加處理程序:
pane.firstChild.onclick = () => pane.remove();
重要程度: 4
創(chuàng)建一個“輪播圖(carousel)” —— 一條可以通過點擊箭頭來滾動圖像的圖像帶。
之后,我們可以為其添加更多功能:無限滾動,動態(tài)加載等。
P.S. 對于這個任務(wù),HTML/CSS 結(jié)構(gòu)實際上占解決方案的 90%。
圖像帶可以表示為圖像 <img>
的 ul/li
列表。
通常,這樣的圖像帶是很寬的,但我們在其周圍放置了一個固定大小的 <div>
來“剪切”它,因此,只有圖像帶的一部分是可見的:
為了使列表水平顯示,我們需要為 <li>
應(yīng)用正確的 CSS 屬性,例如 display: inline-block
。
對于 <img>
來說,我們應(yīng)該調(diào)整 display
,因為默認情況下它是 inline
。在 inline
元素下方為 “l(fā)etter tails” 保留了額外的空間,因此,我們可以使用 display:block
來將其刪除。
我們可以移動 <ul>
來進行滾動。有很多方法可以實現(xiàn)這一點,例如,通過修改 margin-left
或者使用 transform: translateX()
(性能更好):
外部的 <div>
具有固定的寬度,因此,會裁剪掉“多余”的圖像。
整個輪播圖是頁面上的一個獨立的“圖形組件”,因此我們最好將其包裝到一個單獨的 <div class="carousel">
中,并在其中對其進行樣式設(shè)置。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: