Javascript 瀏覽器默認行為

2023-02-17 10:54 更新

許多事件會自動觸發(fā)瀏覽器執(zhí)行某些行為。

例如:

  • 點擊一個鏈接 —— 觸發(fā)導航(navigation)到該 URL。點擊表單的提交按鈕 —— 觸發(fā)提交到服務器的行為。
  • 在文本上按下鼠標按鈕并移動 —— 選中文本。

如果我們使用 JavaScript 處理一個事件,那么我們通常不希望發(fā)生相應的瀏覽器行為。而是想要實現其他行為進行替代。

阻止瀏覽器行為

有兩種方式來告訴瀏覽器我們不希望它執(zhí)行默認行為:

  • 主流的方式是使用 ?event? 對象。有一個 ?event.preventDefault()? 方法。
  • 如果處理程序是使用 ?on<event>?(而不是 ?addEventListener?)分配的,那返回 ?false? 也同樣有效。

在下面這個示例中,點擊鏈接不會觸發(fā)導航(navigation),瀏覽器不會執(zhí)行任何操作:

<a href="/" onclick="return false">Click here</a>
or
<a href="/" onclick="event.preventDefault()">here</a>

在下一個示例中,我們將使用此技術來創(chuàng)建 JavaScript 驅動的菜單。

從處理程序返回 ?false? 是一個例外

事件處理程序返回的值通常會被忽略。

唯一的例外是從使用 on<event> 分配的處理程序中返回的 return false。

在所有其他情況下,return 值都會被忽略。并且,返回 true 沒有意義。

示例:菜單

考慮一個網站菜單,如下所示:

<ul id="menu" class="menu">
  <li><a href="/html">HTML</a></li>
  <li><a href="/javascript">JavaScript</a></li>
  <li><a href="/css">CSS</a></li>
</ul>

下面經過 CSS 渲染的外觀:


菜單項是通過使用 HTML 鏈接 <a> 實現的,而不是使用按鈕 <button>。這樣做有幾個原因,例如:

  • 許多人喜歡使用“右鍵單擊” —— “在一個新窗口打開鏈接”。如果我們使用 ?<button>? 或 ?<span>?,這個效果就無法實現。
  • 搜索引擎在建立索引時遵循 ?<a href="...">? 鏈接。

所以我們在標記(markup)中使用了 <a>。但通常我們打算處理 JavaScript 中的點擊。因此,我們應該阻止瀏覽器默認行為。

像這樣:

menu.onclick = function(event) {
  if (event.target.nodeName != 'A') return;

  let href = event.target.getAttribute('href');
  alert( href ); // ...可以從服務器加載,UI 生成等

  return false; // 阻止瀏覽器行為(不前往訪問 URL)
};

如果我們省略 return false,那么在我們的代碼執(zhí)行完畢后,瀏覽器將執(zhí)行它的“默認行為” —— 導航至在 href 中的 URL。

順便說一句,這里使用事件委托會使我們的菜單更靈活。我們可以添加嵌套列表并使用 CSS 對其進行樣式設置來實現 “slide down” 的效果。

后續(xù)事件

某些事件會相互轉化。如果我們阻止了第一個事件,那就沒有第二個事件了。

例如,在 <input> 字段上的 mousedown 會導致在其中獲得焦點,以及 focus 事件。如果我們阻止 mousedown 事件,在這就沒有焦點了。

這是因為瀏覽器行為在 mousedown 上被取消。如果我們用另一種方式進行輸入,則仍然可以進行聚焦。例如,可以使用 ?Tab? 鍵從第一個輸入切換到第二個輸入。但鼠標點擊則不行。

處理程序選項 “passive”

addEventListener 的可選項 passive: true 向瀏覽器發(fā)出信號,表明處理程序將不會調用 preventDefault()。

為什么需要這樣做?

移動設備上會發(fā)生一些事件,例如 touchmove(當用戶在屏幕上移動手指時),默認情況下會導致滾動,但是可以使用處理程序的 preventDefault() 來阻止?jié)L動。

因此,當瀏覽器檢測到此類事件時,它必須首先處理所有處理程序,然后如果沒有任何地方調用 preventDefault,則頁面可以繼續(xù)滾動。但這可能會導致 UI 中不必要的延遲和“抖動”。

passive: true 選項告訴瀏覽器,處理程序不會取消滾動。然后瀏覽器立即滾動頁面以提供最大程度的流暢體驗,并通過某種方式處理事件。

對于某些瀏覽器(Firefox,Chrome),默認情況下,touchstart 和 touchmove 事件的 passive 為 true

event.defaultPrevented

如果默認行為被阻止,那么 event.defaultPrevented 屬性為 true,否則為 false。

這兒有一個有趣的用例。

你還記得我們在 冒泡和捕獲 一章中討論過的 event.stopPropagation(),以及為什么停止冒泡是不好的嗎?

有時我們可以使用 event.defaultPrevented 來代替,來通知其他事件處理程序,該事件已經被處理。

我們來看一個實際的例子。

默認情況下,瀏覽器在 contextmenu 事件(單擊鼠標右鍵)時,顯示帶有標準選項的上下文菜單。我們可以阻止它并顯示我們自定義的菜單,就像這樣:

<button>Right-click shows browser context menu</button>

<button oncontextmenu="alert('Draw our menu'); return false">
  Right-click shows our context menu
</button>

現在,除了該上下文菜單外,我們還想實現文檔范圍的上下文菜單。

右鍵單擊時,應該顯示最近的上下文菜單:

<p>Right-click here for the document context menu</p>
<button id="elem">Right-click here for the button context menu</button>

<script>
  elem.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Button context menu");
  };

  document.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Document context menu");
  };
</script>

問題是,當我們點擊 elem 時,我們會得到兩個菜單:按鈕級和文檔級(事件冒泡)的菜單。

如何修復呢?其中一個解決方案是:“當我們在按鈕處理程序中處理鼠標右鍵單擊事件時,我們阻止其冒泡”,使用 event.stopPropagation()

<p>Right-click for the document menu</p>
<button id="elem">Right-click for the button menu (fixed with event.stopPropagation)</button>

<script>
  elem.oncontextmenu = function(event) {
    event.preventDefault();
    event.stopPropagation();
    alert("Button context menu");
  };

  document.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Document context menu");
  };
</script>

現在按鈕級菜單如期工作。但是代價太大,我們拒絕了任何外部代碼對右鍵點擊信息的訪問,包括收集統計信息的計數器等。這是非常不明智的。

另一個替代方案是,檢查 document 處理程序是否阻止了瀏覽器的默認行為?如果阻止了,那么該事件已經得到了處理,我們無需再對此事件做出反應。

<p>Right-click for the document menu (added a check for event.defaultPrevented)</p>
<button id="elem">Right-click for the button menu</button>

<script>
  elem.oncontextmenu = function(event) {
    event.preventDefault();
    alert("Button context menu");
  };

  document.oncontextmenu = function(event) {
    if (event.defaultPrevented) return;

    event.preventDefault();
    alert("Document context menu");
  };
</script>

現在一切都可以正常工作了。如果我們有嵌套的元素,并且每個元素都有自己的上下文菜單,那么這也是可以運行的。只需確保檢查每個 contextmenu 處理程序中的 event.defaultPrevented。

event.stopPropagation() 和 event.preventDefault()

正如我們所看到的,event.stopPropagation() 和 event.preventDefault()(也被認為是 return false)是兩個不同的東西。它們之間毫無關聯。

嵌套的上下文菜單結構

還有其他實現嵌套上下文菜單的方式。其中之一是擁有一個具有 document.oncontextmenu 處理程序的全局對象,以及使我們能夠在其中存儲其他處理程序的方法。

該對象將捕獲任何右鍵單擊,瀏覽存儲的處理程序并運行適當的處理程序。

但是,每段需要上下文菜單的代碼都應該了解該對象,并使用它的幫助,而不是使用自己的 contextmenu 處理程序。

總結

有很多默認的瀏覽器行為:

  • ?mousedown? —— 開始選擇(移動鼠標進行選擇)。
  • 在 ?<input type="checkbox">? 上的 ?click? —— 選中/取消選中的 ?input?。
  • ?submit? —— 點擊 ?<input type="submit">? 或者在表單字段中按下 ?Enter? 鍵會觸發(fā)該事件,之后瀏覽器將提交表單。
  • ?keydown? —— 按下一個按鍵會導致將字符添加到字段,或者觸發(fā)其他行為。
  • ?contextmenu? —— 事件發(fā)生在鼠標右鍵單擊時,觸發(fā)的行為是顯示瀏覽器上下文菜單。
  • ……還有更多……

如果我們只想通過 JavaScript 來處理事件,那么所有默認行為都是可以被阻止的。

想要阻止默認行為 —— 可以使用 event.preventDefault() 或 return false。第二個方法只適用于通過 on<event> 分配的處理程序。

addEventListener 的 passive: true 選項告訴瀏覽器該行為不會被阻止。這對于某些移動端的事件(像 touchstart 和 touchmove)很有用,用以告訴瀏覽器在滾動之前不應等待所有處理程序完成。

如果默認行為被阻止,event.defaultPrevented 的值會變成 true,否則為 false。

保持語義,不要濫用

從技術上來說,通過阻止默認行為并添加 JavaScript,我們可以自定義任何元素的行為。例如,我們可以使鏈接 <a> 像按鈕一樣工作,而按鈕 <button> 也可以像鏈接那樣工作(重定向到另一個 URL 等)。

但我們通常應該保留 HTML 元素的語義。例如 <a> 應該表現為導航(navigation),而不是按鈕。

除了“只是一件好事”之外,這還會使你的 HTML 具有更好的可訪問性。

另外,如果我們考慮使用帶有 <a> 的示例,那么請注意:瀏覽器允許我們在新窗口中打開此類鏈接(通過右鍵單擊它們以及其他方式)。大家都喜歡這么做。但是,如果我們使用 JavaScript 讓按鈕行為表現得像鏈接,甚至使用 CSS 將其樣式設置成看起來也像鏈接,即使這樣,但仍然無法在按鈕上使用特定于 <a> 的瀏覽器功能。

任務


為什么 "return false" 不起作用?

重要程度: 3

為什么下面這段代碼中的 return false 不起作用?

<script>
  function handler() {
    alert( "..." );
    return false;
  }
</script>

<a  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank"  onclick="handler()">the browser will go to w3.org</a>

瀏覽器在點擊時會根據 URL 進行跳轉,但這不是我們想要的。

如何修復它?


解決方案

當瀏覽器讀取諸如 onclick 之類的 on* 特性(attribute)時,瀏覽器會根據其內容創(chuàng)建對應的處理程序。

對于 onclick="handler()" 來說,函數是:

function(event) {
  handler() // onclick 的內容
}

現在我們可以看到 handler() 的返回值并沒有被使用,也沒有對結果產生影響。

修復起來很簡單:

<script>
  function handler() {
    alert("...");
    return false;
  }
</script>

<a  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank"  onclick="return handler()">w3.org</a>

我們也可以使用 event.preventDefault(),像這樣:

<script>
  function handler(event) {
    alert("...");
    event.preventDefault();
  }
</script>

<a  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank"  rel="external nofollow" target="_blank"  onclick="handler(event)">w3.org</a>

捕獲元素中的鏈接

重要程度: 5

使所有包含 ?id="contents"? 的元素內的鏈接詢問用戶是否真的要離開。如果用戶不想離開,那就不離開。

細節(jié):

  • 元素內的 HTML 可以被隨時動態(tài)加載或重新生成,因此,我們無法找到所有鏈接并為其添加處理程序。這里使用事件委托。
  • 內容中可能有嵌套的標簽。鏈接中也是,例如 ?<a href=".."><i>...</i></a>?。

打開一個任務沙箱。


解決方案

這是一個很好的使用事件委托模式的案例。

在現實生活中,我們可以向服務器發(fā)送一個 “l(fā)ogging” 請求而不是詢問,該請求會保存關于訪問者離開位置的信息?;蛘?,我們可以加載內容,并將其顯示在頁面中(如果允許的話)。

我們只需要捕獲 contents.onclick,然后使用 confirm 來詢問用戶。一個好主意是使用 link.getAttribute('href') 來代替 link.href。詳情請參見解決方案。

使用沙箱打開解決方案。


圖冊

重要程度: 5

創(chuàng)建一個圖冊,通過點擊縮略圖可以更改主圖片。

P.S. 使用事件委托。

打開一個任務沙箱。


解決方案

解決方案是將處理程序分配給容器,并追蹤點擊。如果點擊在 <a> 鏈接上,則將 #largeImg 的 src 修改為該縮略圖的 href。

使用沙箱打開解決方案。


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號