2022 年 6 月 19 日

滑鼠事件

在本章節中,我們將深入探討滑鼠事件及其屬性。

請注意:此類事件可能不僅來自「滑鼠裝置」,也來自其他裝置,例如手機和平板電腦,這些裝置會模擬這些事件以確保相容性。

滑鼠事件類型

我們已經看過其中一些事件

mousedown/mouseup
滑鼠按鈕在某個元素上按一下/放開。
mouseover/mouseout
滑鼠指標移到/移出某個元素。
mousemove
每個滑鼠移到元素上的動作都會觸發該事件。
click
在同一個元素上使用滑鼠左鍵後,觸發mousedown,然後觸發mouseup
dblclick
在短時間內,同一個元素上連續點擊兩次。現在很少使用。
contextmenu
按下滑鼠右鍵時觸發。還有其他開啟內容選單的方法,例如使用特殊鍵盤鍵,這種情況下也會觸發,所以它不完全是滑鼠事件。

…還有其他幾個事件,我們稍後會介紹。

事件順序

從上面的清單中可以看到,使用者的動作可能會觸發多個事件。

例如,左鍵點擊會先觸發mousedown,當按鈕按下時,然後在釋放時觸發mouseupclick

當一個動作啟動多個事件時,它們的順序是固定的。也就是說,處理程序會按mousedownmouseupclick的順序呼叫。

點擊下面的按鈕,你會看到這些事件。也可以嘗試雙擊。

在下面的測試台上,會記錄所有滑鼠事件,如果它們之間的延遲超過 1 秒,就會以水平線分隔。

此外,我們可以看到button屬性,它允許我們偵測滑鼠按鈕;說明如下。

滑鼠按鈕

與點擊相關的事件總是會有button屬性,它允許取得確切的滑鼠按鈕。

我們通常不會將它用於clickcontextmenu事件,因為前者只會在左鍵點擊時發生,而後者只會在右鍵點擊時發生。

另一方面,mousedownmouseup處理程序可能需要event.button,因為這些事件會在任何按鈕上觸發,所以button允許區分「右鍵按下」和「左鍵按下」。

event.button可能的數值為

按鈕狀態 event.button
左鍵(主鍵) 0
中鍵(輔助鍵) 1
右鍵(次要鍵) 2
X1 按鈕(返回) 3
X2 按鈕(前進) 4

大多數滑鼠裝置只有左右鍵,所以可能的數值是02。觸控裝置在使用者點選時也會產生類似的事件。

另外還有event.buttons屬性,它將所有目前按下的按鈕作為整數,每個按鈕一個位元。實際上,這個屬性很少使用,如果你需要,可以在MDN找到詳細資訊。

過時的 event.which

舊程式碼可能會使用 event.which 屬性,這是取得按鈕的舊式非標準方式,可能的數值為

  • event.which == 1 – 左鍵,
  • event.which == 2 – 中鍵,
  • event.which == 3 – 右鍵。

目前 event.which 已不建議使用,我們不應使用它。

修飾鍵:shift、alt、ctrl 和 meta

所有滑鼠事件都包含已按下的修飾鍵資訊。

事件屬性

  • shiftKeyShift
  • altKeyAlt(或 Mac 的 Opt
  • ctrlKeyCtrl
  • metaKey:Mac 的 Cmd

如果在事件期間按下對應的按鍵,它們會為 true

例如,以下按鈕只會在 Alt+Shift+點擊時運作

<button id="button">Alt+Shift+Click on me!</button>

<script>
  button.onclick = function(event) {
    if (event.altKey && event.shiftKey) {
      alert('Hooray!');
    }
  };
</script>
注意:在 Mac 上通常是 Cmd 取代 Ctrl

在 Windows 和 Linux 上有修飾鍵 AltShiftCtrl。在 Mac 上還有一個:Cmd,對應於 metaKey 屬性。

在大部分應用程式中,當 Windows/Linux 使用 Ctrl 時,Mac 會使用 Cmd

也就是說:Windows 使用者按下 Ctrl+EnterCtrl+A 時,Mac 使用者會按下 Cmd+EnterCmd+A,以此類推。

因此,如果我們想要支援 Ctrl+點擊等組合鍵,那麼對於 Mac 來說,使用 Cmd+點擊是有意義的。這對 Mac 使用者來說比較方便。

即使我們想強制 Mac 使用者使用 Ctrl+點擊,這也是很困難的。問題是:在 MacOS 上,使用 Ctrl 的左鍵點擊會被解釋為右鍵點擊,它會產生 contextmenu 事件,而不是像 Windows/Linux 一樣的 click

因此,如果我們希望所有作業系統的使用者都能感到舒適,那麼我們應該與 ctrlKey 一起檢查 metaKey

對於 JS 程式碼,這表示我們應該檢查 if (event.ctrlKey || event.metaKey)

還有行動裝置

鍵盤組合鍵作為工作流程的補充很好用。這樣,如果訪客使用鍵盤,它們就會運作。

但是,如果他們的裝置沒有鍵盤,那麼應該有辦法在沒有修飾鍵的情況下使用。

座標:clientX/Y、pageX/Y

所有滑鼠事件都提供兩種不同形式的座標

  1. 相對於視窗:clientXclientY
  2. 相對於文件:pageXpageY

我們已經在 座標 章節中介紹過兩者之間的差異。

簡而言之,相對於文件的座標 pageX/Y 是從文件的左上角開始計算的,而且不會隨著頁面捲動而改變,而 clientX/Y 則會隨著頁面捲動而改變。

例如,如果我們有一個 500x500 大小的視窗,而且滑鼠位於左上角,那麼 clientXclientY 都是 0,無論頁面如何捲動。

如果滑鼠位於中心,那麼 clientXclientY 都是 250,無論滑鼠位於文件的哪個位置。它們在這個方面類似於 position:fixed

將滑鼠移到輸入欄位上以查看 clientX/clientY(範例位於 iframe 中,因此座標是相對於該 iframe 的)

<input onmousemove="this.value=event.clientX+':'+event.clientY" value="Mouse over me">

防止在 mousedown 時選取

雙擊滑鼠會產生一個副作用,這可能會在某些介面中造成困擾:它會選取文字。

例如,雙擊以下文字會選取它,而且會額外執行我們的處理常式

<span ondblclick="alert('dblclick')">Double-click me</span>

如果按下滑鼠左鍵,而且不放開,然後移動滑鼠,這也會選取文字,而且通常是不需要的。

有多種方法可以防止選取,你可以在 選取和範圍 章節中閱讀。

在這個特定情況下,最合理的方法是在 mousedown 時防止瀏覽器動作。這可以防止這兩種選取

Before...
<b ondblclick="alert('Click!')" onmousedown="return false">
  Double-click me
</b>
...After

現在,雙擊時不會選取粗體元素,而且在它上面按下左鍵不會開始選取。

請注意:它裡面的文字仍然可以選取。但是,選取不應從文字本身開始,而應從文字之前或之後開始。這通常對使用者來說沒問題。

防止複製

如果我們想要停用選取以保護我們的頁面內容不被複製貼上,那麼我們可以使用另一個事件:oncopy

<div oncopy="alert('Copying forbidden!');return false">
  Dear user,
  The copying is forbidden for you.
  If you know JS or HTML, then you can get everything from the page source though.
</div>

如果你嘗試複製 <div> 中的一段文字,這將無法執行,因為預設動作 oncopy 已被防止。

使用者當然可以取得網頁的 HTML 原始碼,並從中取得內容,但並非所有人都知道如何這麼做。

摘要

滑鼠事件具有下列屬性

  • 按鈕:button

  • 修改鍵(若按下則為 true):altKeyctrlKeyshiftKeymetaKey(Mac)。

    • 若要處理 Ctrl,請別忘了 Mac 使用者,他們通常使用 Cmd,因此最好檢查 if (e.metaKey || e.ctrlKey)
  • 相對於視窗的座標:clientX/clientY

  • 相對於文件座標:pageX/pageY

mousedown 的瀏覽器預設動作為選取文字,若這對介面不好,則應加以避免。

在下一章節中,我們將看到更多關於追蹤指標移動的事件,以及如何追蹤其下的元素變更。

作業

重要性:5

建立一個清單,其中元素可選取,就像檔案管理員一樣。

  • 按一下清單元素只會選取該元素(新增類別 .selected),取消選取所有其他元素。
  • 若按一下時按著 Ctrl(Mac 為 Cmd),則會在該元素上切換選取,但不會修改其他元素。

示範

附註:對於此作業,我們可以假設清單項目只有文字。沒有巢狀標籤。

附註:防止按一下時瀏覽器原生選取文字。

開啟作業沙盒。

教學課程地圖

留言

留言前請先閱讀…
  • 若您有改進建議,請 提交 GitHub 議題或提交 Pull Request,而不是留言。
  • 如果您無法理解文章中的內容,請詳細說明。
  • 若要插入少量的程式碼,請使用 <code> 標籤,對於多行程式碼,請將它們包覆在 <pre> 標籤中,對於超過 10 行的程式碼,請使用沙盒 (plnkrjsbincodepen…)