Nhiều sự kiện tự động dẫn đến các hành động nhất định do trình duyệt thực hiện. Đó là chủ đề hôm nay mà cafedev chia sẻ cho ace, các hành động mặc định của trình duyệt.

Ví dụ:

  • Một cú nhấp chuột vào một liên kết – bắt đầu điều hướng đến URL của nó.
  • Một cú nhấp chuột vào nút gửi biểu mẫu – bắt đầu gửi biểu mẫu đến máy chủ.
  • Nhấn nút chuột trên văn bản và di chuyển văn bản đó – chọn văn bản.

Nếu chúng ta xử lý một sự kiện trong JavaScript, chúng ta có thể không muốn hành động trình duyệt tương ứng xảy ra và thay vào đó, chúng ta muốn triển khai một hành vi khác.

1. Ngăn chặn các hành động của trình duyệt

Có hai cách để cho trình duyệt biết rằng chúng ta không muốn trình duyệt hoạt động:

  • Cách chính là sử dụng đối tượng event. Có một phương thức event.preventDefault().
  • Nếu trình xử lý được chỉ định bằng cách sử dụng on<event>(không phải bởi addEventListener), thì việc trả về false cũng hoạt động tương tự.

Trong HTML này, nhấp chuột vào liên kết không dẫn đến điều hướng, trình duyệt không thực hiện bất kỳ điều gì:

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

Trong ví dụ tiếp theo, chúng ta sẽ sử dụng kỹ thuật này để tạo một menu hỗ trợ JavaScript.

Trở về false từ một trình xử lý là một ngoại lệ

Giá trị được trả về bởi một trình xử lý sự kiện thường bị bỏ qua.

Ngoại lệ duy nhất là return false từ một trình xử lý được chỉ định sử dụng on<event>.

Trong tất cả các trường hợp khác, return giá trị bị bỏ qua. Đặc biệt, không có ý nghĩa trong việc quay trở lại true.

1.1. Ví dụ: menu

Hãy xem xét một menu trang web, như sau:

<!--
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
-->

<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>

Đây là cách nó trông như thế nào với một số CSS:

Các mục menu được triển khai dưới dạng liên kết HTML <a>, không phải nút <button>. Có một số lý do để làm như vậy, ví dụ:

  • Nhiều người thích sử dụng “nhấp chuột phải” – “mở trong cửa sổ mới”. Nếu chúng ta sử dụng <button> hoặc <span>, điều đó không hoạt động.
  • Công cụ tìm kiếm theo <a href="...">liên kết trong khi lập chỉ mục.

Vì vậy, chúng ta sử dụng <a> trong đánh dấu. Nhưng thông thường chúng ta định xử lý các nhấp chuột trong JavaScript. Vì vậy, chúng ta nên ngăn chặn hành động mặc định của trình duyệt.

Như đây:

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

  let href = event.target.getAttribute('href');
  alert( href ); // ...can be loading from the server, UI generation etc

  return false; // prevent browser action (don't go to the URL)
};

Nếu chúng ta bỏ qua return false, thì sau khi mã của chúng ta thực thi, trình duyệt sẽ thực hiện “hành động mặc định” – điều hướng đến URL trong href. Và chúng ta không cần điều đó ở đây, vì chúng ta đang tự xử lý nhấp chuột.

Nhân tiện, việc sử dụng ủy nhiệm sự kiện ở đây làm cho menu của chúng ta rất linh hoạt. Chúng ta có thể thêm các danh sách lồng nhau và tạo kiểu cho chúng bằng cách sử dụng CSS để “trượt xuống”.

Các sự kiện tiếp theo

Các sự kiện nhất định nối tiếp nhau. Nếu chúng ta ngăn chặn sự kiện đầu tiên, sẽ không có sự kiện thứ hai.

Ví dụ, mousedown trên một trường <input> dẫn đến việc tập trung vào đó và sự kiện focus. Nếu chúng ta ngăn cản  sự kiện mousedown, sẽ không có trọng tâm.

Hãy thử nhấp vào phần đầu tiên bên dưới <input> – sự kiện focus sẽ xảy ra. Nhưng nếu bạn nhấp vào cái thứ hai, sẽ không có tiêu điểm.

<input value="Focus works" onfocus="this.value=''">
<input onmousedown="return false" onfocus="this.value=''" value="Click me">

Đó là vì hành động của trình duyệt bị hủy vào mousedown. Vẫn có thể lấy nét nếu chúng ta sử dụng một cách khác để nhập đầu vào. Ví dụ, phím Tab để chuyển từ đầu vào thứ nhất thành thứ hai. Nhưng không phải với cú nhấp chuột nữa.

2. Tùy chọn trình xử lý “thụ động”

Tùy chọn tùy passive: true chọn addEventListener báo hiệu trình duyệt rằng trình xử lý sẽ không gọi preventDefault().

Tại sao điều đó có thể cần thiết?

Có một số sự kiện như touchmove trên thiết bị di động (khi người dùng di chuyển ngón tay của họ trên màn hình), gây ra việc cuộn theo mặc định, nhưng việc cuộn đó có thể bị ngăn chặn bằng cách sử dụng preventDefault() trong trình xử lý.

Vì vậy, khi trình duyệt phát hiện sự kiện như vậy, trước tiên nó phải xử lý tất cả các trình xử lý và sau đó nếu preventDefault không được gọi ở bất kỳ đâu, nó có thể tiến hành cuộn. Điều đó có thể gây ra sự chậm trễ không cần thiết và “giật mình” trong giao diện người dùng.

Các tùy chọn passive: true cho trình duyệt biết rằng trình xử lý sẽ không hủy thao tác cuộn. Sau đó, trình duyệt sẽ cuộn ngay lập tức cung cấp trải nghiệm lưu loát tối đa và sự kiện được xử lý theo cách này.

Đối với một số trình duyệt (Firefox, Chrome), passive là true theo mặc định cho sự kiện touchstart và touchmove.

2.1. event.defaultPrevented

Thuộc tính event.defaultPrevented là true nếu hành động mặc định bị ngăn chặn, và false nếu không.

Có một trường hợp sử dụng thú vị cho nó.

Bạn còn nhớ trong chương Sủi Event.stopPropagation() bọt và bắt chúng ta đã nói đến và tại sao ngừng sủi bọt lại là điều không tốt?

Đôi khi chúng ta có thể sử dụng event.defaultPrevented thay thế để báo hiệu cho các trình xử lý sự kiện khác rằng sự kiện đã được xử lý.

Hãy xem một ví dụ thực tế.

Theo mặc định, trình duyệt khi  sự kiện context menu (nhấp chuột phải) hiển thị menu ngữ cảnh với các tùy chọn tiêu chuẩn. Chúng ta có thể ngăn chặn nó và hiển thị của riêng chúng tôi, như thế này:

<!--
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
-->

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

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

Bây giờ, ngoài menu ngữ cảnh đó, chúng ta muốn triển khai menu ngữ cảnh trên toàn tài liệu.

Khi nhấp chuột phải, menu ngữ cảnh gần nhất sẽ hiển thị.

<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>

Vấn đề là khi chúng ta nhấp vào elem, chúng ta nhận được hai menu: menu cấp độ nút và (sự kiện bong bóng) menu cấp độ tài liệu.

Làm thế nào để sửa chữa nó? Một trong những giải pháp là suy nghĩ như: “Khi chúng ta xử lý nhấp chuột phải vào trình xử lý nút, hãy ngừng sủi bọt” và sử dụng 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>

Bây giờ menu cấp nút hoạt động như dự định. Nhưng giá cao. Chúng ta vĩnh viễn từ chối quyền truy cập vào thông tin về nhấp chuột phải cho bất kỳ code bên ngoài nào, bao gồm cả bộ đếm thu thập số liệu thống kê, v.v. Điều đó khá thiếu khôn ngoan.

Một giải pháp thay thế sẽ là kiểm tra trong trình xử lý document nếu hành động mặc định bị ngăn chặn? Nếu đúng như vậy, thì sự kiện đã được xử lý và chúng ta không cần phải phản ứng với nó.

<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>

Bây giờ mọi thứ cũng hoạt động chính xác. Nếu chúng ta có các phần tử lồng nhau và mỗi phần tử có một menu ngữ cảnh riêng, thì điều đó cũng sẽ hoạt động. Chỉ cần đảm bảo kiểm tra event.defaultPrevented từng  trình xử lý contextmenu.

event.stopPropagation () và event.preventDefault ()

Như chúng ta có thể thấy rõ, event.stopPropagation()và event.preventDefault()(còn được gọi là return false) là hai thứ khác nhau. Chúng không liên quan đến nhau.

Kiến trúc menu ngữ cảnh lồng nhau

Ngoài ra còn có những cách thay thế để triển khai các menu ngữ cảnh lồng nhau. Một trong số đó là có một đối tượng toàn cục duy nhất với trình xử lý cho document.oncontextmenu và cả các phương thức cho phép chúng ta lưu trữ các trình xử lý khác trong đó.

Đối tượng sẽ bắt bất kỳ lần nhấp chuột phải nào, xem qua các trình xử lý được lưu trữ và chạy trình xử lý thích hợp.

Nhưng sau đó, mỗi đoạn code muốn có menu ngữ cảnh phải biết về đối tượng đó và sử dụng trợ giúp của nó thay vì trình xử lý contextmenu riêng .

3. Tóm lược

Có nhiều hành động trình duyệt mặc định:

  • mousedown – bắt đầu lựa chọn (di chuyển chuột để chọn).
  • click <input type="checkbox">– kiểm tra / bỏ chọn input.
  • submit- việc nhấp vào <input type="submit">hoặc nhấn Enter vào bên trong trường biểu mẫu khiến sự kiện này xảy ra và trình duyệt gửi biểu mẫu sau đó.
  • keydown – nhấn phím có thể dẫn đến việc thêm một ký tự vào một trường hoặc các hành động khác.
  • contextmenu – sự kiện xảy ra khi nhấp chuột phải, hành động là hiển thị menu ngữ cảnh của trình duyệt.
  • …có nhiều…

Tất cả các hành động mặc định có thể bị ngăn chặn nếu chúng ta muốn xử lý sự kiện độc quyền bằng JavaScript.

Để ngăn hành động mặc định – sử dụng event.preventDefault() hoặc return false. Phương thức  thứ hai chỉ hoạt động cho các trình xử lý được gán với on<event>.

Các tùy chọn passive: true cho trình duyệt addEventListener rằng hành động sẽ không được ngăn chặn. Điều này hữu ích cho một số sự kiện trên thiết bị di động, chẳng hạn như touchstart và touchmove, để nói với trình duyệt rằng nó không nên đợi tất cả các trình xử lý hoàn tất trước khi cuộn.

Nếu hành động mặc định bị ngăn chặn, giá trị của event.defaultPrevented sẽ trở thành true, nếu không thì giá trị đó là false.

Giữ ngữ nghĩa, không lạm dụng

Về mặt kỹ thuật, bằng cách ngăn các hành động mặc định và thêm JavaScript, chúng ta có thể tùy chỉnh hành vi của bất kỳ phần tử nào. Ví dụ: chúng ta có thể làm cho một liên kết <a> hoạt động giống như một nút và một nút <button> hoạt động như một liên kết (chuyển hướng đến một URL khác hoặc tương tự như vậy).

Nhưng nói chung chúng ta nên giữ ý nghĩa ngữ nghĩa của các phần tử HTML. Ví dụ, <a> nên thực hiện điều hướng, không phải một nút.

Bên cạnh việc “chỉ là một điều tốt”, điều đó còn làm cho HTML của bạn tốt hơn về khả năng truy cập.

Ngoài ra, nếu chúng ta xem xét ví dụ với <a>, thì xin lưu ý: trình duyệt cho phép chúng ta mở các liên kết như vậy trong một cửa sổ mới (bằng cách nhấp chuột phải vào chúng và các phương tiện khác). Và những người như thế. Nhưng nếu chúng ta làm cho một nút hoạt động như một liên kết bằng JavaScript và thậm chí trông giống như một liên kết sử dụng CSS, thì các  tính năng <a> cụ thể của trình duyệt vẫn sẽ không hoạt động với nó.

Nguồn và tài liệu tiếng anh tham khảo:

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!