Tìm hiểu chuyên sâu về sự kiện JavaScript

preventDefaultstopPropagation: trường hợp sử dụng cụ thể và chức năng chính xác của từng phương thức.

Event.stopPropagation() và Event.preventDefault()

Việc xử lý sự kiện JavaScript thường rất đơn giản. Điều này đặc biệt đúng khi xử lý cấu trúc HTML đơn giản (tương đối phẳng). Tuy nhiên, mọi thứ sẽ phức tạp hơn một chút khi các sự kiện di chuyển (hoặc lan truyền) thông qua một hệ thống phân cấp các phần tử. Đây thường là thời điểm nhà phát triển liên hệ với stopPropagation() và/hoặc preventDefault() để giải quyết các vấn đề mà họ đang gặp phải. Nếu bạn từng nghĩ "Tôi sẽ thử preventDefault() và nếu cách đó không hiệu quả, tôi sẽ thử stopPropagation() và nếu cách đó không hiệu quả, tôi sẽ thử cả hai", thì bài viết này là dành cho bạn! Tôi sẽ giải thích chính xác chức năng của từng phương thức, thời điểm sử dụng từng phương thức và cung cấp cho bạn nhiều ví dụ hoạt động để bạn khám phá. Mục tiêu của tôi là giải quyết dứt điểm sự nhầm lẫn của bạn.

Tuy nhiên, trước khi đi sâu vào vấn đề này, bạn cần nắm sơ bộ về hai loại xử lý sự kiện có thể có trong JavaScript (trong tất cả các trình duyệt hiện đại – Internet Explorer trước phiên bản 9 hoàn toàn không hỗ trợ tính năng ghi lại sự kiện).

Kiểu sự kiện (nắm bắt và truyền tin)

Tất cả trình duyệt hiện đại đều hỗ trợ tính năng ghi lại sự kiện, nhưng các nhà phát triển rất hiếm khi sử dụng tính năng này. Điều thú vị là đây là hình thức tạo sự kiện duy nhất mà Netscape hỗ trợ ban đầu. Đối thủ lớn nhất của Netscape là Microsoft Internet Explorer hoàn toàn không hỗ trợ tính năng ghi lại sự kiện, mà chỉ hỗ trợ một kiểu sự kiện khác gọi là lan truyền sự kiện. Khi W3C được thành lập, họ nhận thấy cả hai kiểu tạo sự kiện đều có giá trị và tuyên bố rằng trình duyệt phải hỗ trợ cả hai kiểu này thông qua tham số thứ ba cho phương thức addEventListener. Ban đầu, tham số đó chỉ là một giá trị boolean, nhưng tất cả các trình duyệt hiện đại đều hỗ trợ một đối tượng options làm tham số thứ ba. Bạn có thể dùng đối tượng này để chỉ định (trong số những thứ khác) xem bạn có muốn sử dụng tính năng ghi lại sự kiện hay không:

someElement.addEventListener('click', myClickHandler, { capture: true | false });

Xin lưu ý rằng đối tượng options là không bắt buộc, cũng như thuộc tính capture của đối tượng này. Nếu bạn bỏ qua một trong hai, giá trị mặc định cho capturefalse, tức là sẽ dùng tính năng truyền sự kiện.

Ghi lại sự kiện

Điều gì sẽ xảy ra nếu trình xử lý sự kiện của bạn "đang lắng nghe trong giai đoạn bắt sự kiện"? Để hiểu rõ điều này, chúng ta cần biết cách các sự kiện bắt nguồn và cách chúng di chuyển. Điều này đúng với tất cả sự kiện, ngay cả khi bạn (nhà phát triển) không tận dụng, quan tâm hoặc nghĩ đến sự kiện đó.

Tất cả các sự kiện đều bắt đầu tại cửa sổ và trước tiên trải qua giai đoạn ghi lại. Điều này có nghĩa là khi một sự kiện được gửi đi, sự kiện đó sẽ bắt đầu cửa sổ và di chuyển "xuống" về phía phần tử đích trước tiên. Điều này xảy ra ngay cả khi bạn chỉ đang nghe ở giai đoạn lan truyền. Hãy xem xét ví dụ sau về mã đánh dấu và JavaScript:

<html>
  <body>
    <div id="A">
      <div id="B">
        <div id="C"></div>
      </div>
    </div>
  </body>
</html>
document.getElementById('C').addEventListener(
  'click',
  function (e) {
    console.log('#C was clicked');
  },
  true,
);

Khi người dùng nhấp vào phần tử #C, một sự kiện bắt nguồn từ window sẽ được gửi đi. Sau đó, sự kiện này sẽ lan truyền qua các thành phần con của nó như sau:

window => document => <html> => <body> => v.v., cho đến khi đạt được mục tiêu.

Không quan trọng nếu không có gì đang theo dõi sự kiện nhấp chuột tại phần tử window hoặc document hoặc <html> hoặc <body> (hoặc bất kỳ phần tử nào khác trên đường đến mục tiêu của phần tử đó). Một sự kiện vẫn bắt nguồn từ window và bắt đầu hành trình như vừa mô tả.

Trong ví dụ của chúng ta, sự kiện nhấp sẽ truyền (đây là một từ quan trọng vì từ này sẽ liên kết trực tiếp với cách hoạt động của phương thức stopPropagation() và sẽ được giải thích sau trong tài liệu này) từ window đến phần tử đích (trong trường hợp này là #C) thông qua mọi phần tử giữa window#C.

Điều này có nghĩa là sự kiện nhấp sẽ bắt đầu lúc window và trình duyệt sẽ đặt ra các câu hỏi sau:

"Có đối tượng nào đang theo dõi sự kiện nhấp chuột trên window trong giai đoạn bắt không?" Nếu có, các trình xử lý sự kiện thích hợp sẽ kích hoạt. Trong ví dụ của chúng ta, không có gì cả, nên không có trình xử lý nào được kích hoạt.

Tiếp theo, sự kiện sẽ truyền đến document và trình duyệt sẽ hỏi: "Có gì đang nghe sự kiện nhấp vào document trong giai đoạn bắt không?" Nếu có, các trình xử lý sự kiện thích hợp sẽ kích hoạt.

Tiếp theo, sự kiện sẽ truyền đến phần tử <html> và trình duyệt sẽ hỏi: "Có gì đang lắng nghe lượt nhấp vào phần tử <html> trong giai đoạn bắt không?" Nếu có, các trình xử lý sự kiện thích hợp sẽ kích hoạt.

Tiếp theo, sự kiện sẽ truyền đến phần tử <body> và trình duyệt sẽ hỏi: "Có gì đang theo dõi sự kiện nhấp vào phần tử <body> trong giai đoạn bắt không?" Nếu có, các trình xử lý sự kiện thích hợp sẽ kích hoạt.

Tiếp theo, sự kiện sẽ truyền đến phần tử #A. Một lần nữa, trình duyệt sẽ hỏi: "Có sự kiện nào đang theo dõi lượt nhấp vào #A trong giai đoạn bắt giữ hay không và nếu có, các trình xử lý sự kiện thích hợp sẽ kích hoạt.

Tiếp theo, sự kiện sẽ truyền đến phần tử #B (và cùng một câu hỏi sẽ được đặt ra).

Cuối cùng, sự kiện sẽ đạt đến mục tiêu và trình duyệt sẽ hỏi: "Có thành phần nào đang lắng nghe sự kiện nhấp vào phần tử #C trong giai đoạn bắt giữ không?" Câu trả lời lần này là "có!" Khoảng thời gian ngắn này khi sự kiện đạt mục tiêu được gọi là "giai đoạn mục tiêu". Tại thời điểm này, trình xử lý sự kiện sẽ kích hoạt, trình duyệt sẽ console.log "#C was clicked" (Đã nhấp vào #C) và sau đó chúng ta sẽ hoàn tất, đúng không? Sai rồi! Chúng tôi vẫn còn nhiều việc phải làm. Quá trình này tiếp tục, nhưng giờ đây, nó chuyển sang giai đoạn lan truyền.

Lan truyền sự kiện

Trình duyệt sẽ hỏi:

"Có sự kiện nào đang theo dõi sự kiện nhấp vào #C trong giai đoạn lan truyền không?" Hãy chú ý kỹ phần này. Bạn hoàn toàn có thể theo dõi các lượt nhấp (hoặc bất kỳ loại sự kiện nào) trong cả giai đoạn bắt giai đoạn truyền tin. Và nếu bạn đã kết nối các trình xử lý sự kiện ở cả hai giai đoạn (ví dụ: bằng cách gọi .addEventListener() hai lần, một lần bằng capture = true và một lần bằng capture = false), thì có, cả hai trình xử lý sự kiện chắc chắn sẽ kích hoạt cho cùng một phần tử. Tuy nhiên, bạn cũng cần lưu ý rằng chúng kích hoạt ở các giai đoạn khác nhau (một ở giai đoạn ghi lại và một ở giai đoạn truyền lên).

Tiếp theo, sự kiện sẽ truyền lan (thường được gọi là "lan truyền" vì có vẻ như sự kiện đang di chuyển "lên" cây DOM) đến phần tử mẹ của nó, #B, và trình duyệt sẽ hỏi: "Có gì đang nghe các sự kiện nhấp vào #B trong giai đoạn lan truyền không?" Trong ví dụ của chúng tôi, không có gì thay đổi, vì vậy không có trình xử lý nào được kích hoạt.

Tiếp theo, sự kiện sẽ lan truyền đến #A và trình duyệt sẽ hỏi: "Có thành phần nào đang lắng nghe các sự kiện nhấp chuột trên #A trong giai đoạn lan truyền không?"

Tiếp theo, sự kiện sẽ lan truyền đến <body>: "Có thành phần nào đang lắng nghe sự kiện nhấp chuột trên phần tử <body> trong giai đoạn lan truyền không?"

Tiếp theo, phần tử <html>: "Có thành phần nào đang lắng nghe các sự kiện nhấp vào phần tử <html> trong giai đoạn truyền nổi không?

Tiếp theo, document: "Có gì đang theo dõi các sự kiện nhấp chuột trên document trong giai đoạn lan truyền không?"

Cuối cùng, window: "Có trình nghe nào đang chờ sự kiện nhấp trên cửa sổ ở giai đoạn lan truyền không?"

Chà! Đó là một hành trình dài và sự kiện của chúng ta có lẽ đã rất mệt mỏi vào lúc này, nhưng tin hay không thì tuỳ, đó là hành trình mà mọi sự kiện đều trải qua! Hầu hết thời gian, điều này không bao giờ được nhận thấy vì các nhà phát triển thường chỉ quan tâm đến một giai đoạn sự kiện hoặc giai đoạn khác (và thường là giai đoạn lan truyền).

Bạn nên dành chút thời gian để thử nghiệm tính năng ghi lại sự kiện, lan truyền sự kiện và ghi một số ghi chú vào bảng điều khiển khi trình xử lý kích hoạt. Việc xem đường dẫn mà một sự kiện thực hiện là rất hữu ích. Sau đây là một ví dụ về việc lắng nghe mọi phần tử trong cả hai giai đoạn.

<html>
  <body>
    <div id="A">
      <div id="B">
        <div id="C"></div>
      </div>
    </div>
  </body>
</html>
document.addEventListener(
  'click',
  function (e) {
    console.log('click on document in capturing phase');
  },
  true,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
  'click',
  function (e) {
    console.log('click on <html> in capturing phase');
  },
  true,
);
document.body.addEventListener(
  'click',
  function (e) {
    console.log('click on <body> in capturing phase');
  },
  true,
);
document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('click on #A in capturing phase');
  },
  true,
);
document.getElementById('B').addEventListener(
  'click',
  function (e) {
    console.log('click on #B in capturing phase');
  },
  true,
);
document.getElementById('C').addEventListener(
  'click',
  function (e) {
    console.log('click on #C in capturing phase');
  },
  true,
);

document.addEventListener(
  'click',
  function (e) {
    console.log('click on document in bubbling phase');
  },
  false,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
  'click',
  function (e) {
    console.log('click on <html> in bubbling phase');
  },
  false,
);
document.body.addEventListener(
  'click',
  function (e) {
    console.log('click on <body> in bubbling phase');
  },
  false,
);
document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('click on #A in bubbling phase');
  },
  false,
);
document.getElementById('B').addEventListener(
  'click',
  function (e) {
    console.log('click on #B in bubbling phase');
  },
  false,
);
document.getElementById('C').addEventListener(
  'click',
  function (e) {
    console.log('click on #C in bubbling phase');
  },
  false,
);

Đầu ra của bảng điều khiển sẽ phụ thuộc vào phần tử mà bạn nhấp vào. Nếu nhấp vào phần tử "sâu nhất" trong cây DOM (phần tử #C), bạn sẽ thấy mọi trình xử lý sự kiện này đều kích hoạt. Với một chút kiểu CSS để giúp bạn dễ dàng nhận biết phần tử nào là phần tử nào, đây là phần tử #C đầu ra của bảng điều khiển (kèm theo ảnh chụp màn hình):

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
"click on <body> in bubbling phase"
"click on <html> in bubbling phase"
"click on document in bubbling phase"

event.stopPropagation()

Khi hiểu rõ nguồn gốc của các sự kiện và cách chúng di chuyển (tức là lan truyền) qua DOM trong cả giai đoạn bắt và giai đoạn truyền tin, giờ đây, chúng ta có thể chuyển sự chú ý sang event.stopPropagation().

Phương thức stopPropagation() có thể được gọi trên (hầu hết) các sự kiện DOM gốc. Tôi nói "hầu hết" vì có một số sự kiện mà việc gọi phương thức này sẽ không có tác dụng gì (vì sự kiện không truyền đi ngay từ đầu). Các sự kiện như focus, blur, load, scroll và một số sự kiện khác thuộc danh mục này. Bạn có thể gọi stopPropagation() nhưng sẽ không có gì thú vị xảy ra vì các sự kiện này không truyền tải.

Nhưng stopPropagation có chức năng gì?

Nói chung, nó hoạt động đúng như tên gọi. Khi bạn gọi sự kiện này, sự kiện sẽ ngừng truyền đến mọi phần tử mà sự kiện sẽ truyền đến. Điều này đúng với cả hai hướng (nắm bắt và truyền lên). Vì vậy, nếu bạn gọi stopPropagation() ở bất kỳ đâu trong giai đoạn bắt, sự kiện sẽ không bao giờ chuyển sang giai đoạn đích hoặc giai đoạn lan truyền. Nếu bạn gọi nó trong giai đoạn truyền tin, nó sẽ đã trải qua giai đoạn bắt, nhưng sẽ ngừng "truyền tin" từ thời điểm bạn gọi nó.

Quay lại cùng một ví dụ về mã đánh dấu, bạn nghĩ điều gì sẽ xảy ra nếu chúng ta gọi stopPropagation() trong giai đoạn bắt tại phần tử #B?

Kết quả sẽ như sau:

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"

Bạn có muốn dừng lan truyền tại #A trong giai đoạn sủi bọt không? Điều này sẽ dẫn đến kết quả sau:

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"

Thêm một cái nữa, chỉ để cho vui. Điều gì sẽ xảy ra nếu chúng ta gọi stopPropagation() trong giai đoạn đích cho #C? Hãy nhớ rằng "giai đoạn mục tiêu" là tên được đặt cho khoảng thời gian mà sự kiện đạt mục tiêu. Kết quả sẽ như sau:

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"

Xin lưu ý rằng trình xử lý sự kiện cho #C mà chúng ta ghi lại "click on #C in the capturing phase" (nhấp vào #C trong giai đoạn bắt) vẫn thực thi, nhưng trình xử lý sự kiện mà chúng ta ghi lại "click on #C in the bubbling phase" (nhấp vào #C trong giai đoạn truyền lên) thì không. Điều này hoàn toàn hợp lý. Chúng ta đã gọi stopPropagation() từ trước đó, vì vậy, đó là thời điểm mà sự lan truyền của sự kiện sẽ dừng lại.

Trong bất kỳ bản minh hoạ trực tiếp nào trong số này, tôi khuyến khích bạn thử nghiệm. Hãy thử nhấp vào phần tử #A hoặc chỉ phần tử body. Hãy thử dự đoán điều gì sẽ xảy ra rồi quan sát xem bạn có dự đoán đúng không. Đến thời điểm này, bạn có thể dự đoán khá chính xác.

event.stopImmediatePropagation()

Phương pháp kỳ lạ và không thường xuyên được sử dụng này là gì? Phương thức này tương tự như stopPropagation, nhưng thay vì ngăn một sự kiện truyền đến các thành phần con (chụp) hoặc thành phần mẹ (truyền bá), phương thức này chỉ áp dụng khi bạn có nhiều trình xử lý sự kiện được kết nối với một phần tử duy nhất. Vì addEventListener() hỗ trợ kiểu truyền sự kiện đa hướng, nên bạn hoàn toàn có thể kết nối một trình xử lý sự kiện với một phần tử duy nhất nhiều lần. Khi điều này xảy ra (trong hầu hết các trình duyệt), các trình xử lý sự kiện sẽ được thực thi theo thứ tự mà chúng được kết nối. Việc gọi stopImmediatePropagation() sẽ ngăn mọi trình xử lý tiếp theo kích hoạt. Hãy xem ví dụ sau đây:

<html>
  <body>
    <div id="A">I am the #A element</div>
  </body>
</html>
document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('When #A is clicked, I shall run first!');
  },
  false,
);

document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('When #A is clicked, I shall run second!');
    e.stopImmediatePropagation();
  },
  false,
);

document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('When #A is clicked, I would have run third, if not for stopImmediatePropagation');
  },
  false,
);

Ví dụ trên sẽ tạo ra đầu ra sau đây trên bảng điều khiển:

"When #A is clicked, I shall run first!"
"When #A is clicked, I shall run second!"

Xin lưu ý rằng trình xử lý sự kiện thứ ba không bao giờ chạy do trình xử lý sự kiện thứ hai gọi e.stopImmediatePropagation(). Nếu thay vào đó, chúng ta gọi e.stopPropagation(), thì trình xử lý thứ ba vẫn sẽ chạy.

event.preventDefault()

Nếu stopPropagation() ngăn một sự kiện di chuyển "xuống" (ghi lại) hoặc "lên" (truyền tin), thì preventDefault() sẽ làm gì? Có vẻ như tính năng này cũng có chức năng tương tự. Có không?

Thực ra là không. Mặc dù thường bị nhầm lẫn, nhưng thực tế thì hai khái niệm này không liên quan nhiều đến nhau. Khi bạn thấy preventDefault(), hãy thêm từ "hành động" vào đầu. Hãy nghĩ đến việc "ngăn chặn hành động mặc định".

Bạn có thể yêu cầu thực hiện thao tác mặc định nào? Rất tiếc, câu trả lời cho câu hỏi đó không rõ ràng vì câu trả lời phụ thuộc rất nhiều vào tổ hợp phần tử và sự kiện được đề cập. Và để mọi chuyện trở nên khó hiểu hơn, đôi khi không có thao tác mặc định nào cả!

Hãy bắt đầu bằng một ví dụ rất đơn giản để hiểu rõ. Bạn mong đợi điều gì xảy ra khi nhấp vào một đường liên kết trên trang web? Rõ ràng là bạn muốn trình duyệt chuyển đến URL do đường liên kết đó chỉ định. Trong trường hợp này, phần tử là một thẻ liên kết và sự kiện là một sự kiện nhấp chuột. Tổ hợp đó (<a> + click) có "hành động mặc định" là chuyển đến href của đường liên kết. Điều gì sẽ xảy ra nếu bạn muốn ngăn trình duyệt thực hiện hành động mặc định đó? Tức là giả sử bạn muốn ngăn trình duyệt chuyển đến URL do thuộc tính href của phần tử <a> chỉ định? Đây là những gì preventDefault() sẽ làm cho bạn. Hãy xem xét ví dụ sau:

<a id="avett" href="https://www.theavettbrothers.com/welcome">The Avett Brothers</a>
document.getElementById('avett').addEventListener(
  'click',
  function (e) {
    e.preventDefault();
    console.log('Maybe we should just play some of their music right here instead?');
  },
  false,
);

Thông thường, khi nhấp vào đường liên kết có nhãn The Avett Brothers, bạn sẽ chuyển đến www.theavettbrothers.com. Tuy nhiên, trong trường hợp này, chúng ta đã kết nối một trình xử lý sự kiện nhấp chuột với phần tử <a> và chỉ định rằng hành động mặc định sẽ bị ngăn chặn. Do đó, khi người dùng nhấp vào đường liên kết này, họ sẽ không được chuyển đến bất kỳ đâu, mà thay vào đó, bảng điều khiển sẽ chỉ ghi lại "Maybe we should just play some of their music right here instead?" (Có lẽ chúng ta chỉ nên phát một số bản nhạc của họ ngay tại đây thì hơn?)

Những tổ hợp phần tử/sự kiện nào khác cho phép bạn ngăn chặn hành động mặc định? Tôi không thể liệt kê hết tất cả các yếu tố đó và đôi khi bạn chỉ cần thử nghiệm để xem. Nhưng tóm lại, sau đây là một số lý do:

  • Phần tử <form> + sự kiện "submit": preventDefault() cho tổ hợp này sẽ ngăn biểu mẫu gửi. Điều này sẽ hữu ích nếu bạn muốn thực hiện quy trình xác thực và nếu có lỗi xảy ra, bạn có thể gọi preventDefault một cách có điều kiện để ngăn biểu mẫu gửi.

  • Phần tử <a> + sự kiện "click": preventDefault() cho tổ hợp này sẽ ngăn trình duyệt chuyển đến URL được chỉ định trong thuộc tính href của phần tử <a>.

  • Sự kiện document + "mousewheel": preventDefault() cho tổ hợp này ngăn thao tác cuộn trang bằng con lăn chuột (mặc dù thao tác cuộn bằng bàn phím vẫn hoạt động).
    ↜ Thao tác này yêu cầu gọi addEventListener() bằng { passive: false }.

  • Sự kiện document + "keydown": preventDefault() cho tổ hợp này là không thể chấp nhận. Điều này khiến trang trở nên vô dụng, ngăn chặn việc cuộn, nhấn phím Tab và làm nổi bật bằng bàn phím.

  • Sự kiện document + "mousedown": preventDefault() cho tổ hợp này sẽ ngăn việc đánh dấu văn bản bằng chuột và mọi hành động "mặc định" khác mà người dùng sẽ thực hiện khi nhấn chuột.

  • Phần tử <input> + sự kiện "keypress": preventDefault() cho tổ hợp này sẽ ngăn các ký tự do người dùng nhập đến phần tử đầu vào (nhưng đừng làm điều này; hiếm khi có lý do hợp lệ cho việc này).

  • Sự kiện document + "contextmenu": preventDefault() cho tổ hợp này ngăn trình đơn theo bối cảnh của trình duyệt gốc xuất hiện khi người dùng nhấp chuột phải hoặc nhấn giữ (hoặc bất kỳ cách nào khác mà trình đơn theo bối cảnh có thể xuất hiện).

Đây không phải là danh sách đầy đủ, nhưng hy vọng sẽ giúp bạn hiểu rõ cách sử dụng preventDefault().

Một trò đùa tinh nghịch vui nhộn?

Điều gì sẽ xảy ra nếu bạn stopPropagation() preventDefault() trong giai đoạn chụp, bắt đầu từ tài liệu? Và thế là một tràng cười sảng khoái! Đoạn mã sau đây sẽ khiến mọi trang web gần như hoàn toàn vô dụng:

function preventEverything(e) {
  e.preventDefault();
  e.stopPropagation();
  e.stopImmediatePropagation();
}

document.addEventListener('click', preventEverything, true);
document.addEventListener('keydown', preventEverything, true);
document.addEventListener('mousedown', preventEverything, true);
document.addEventListener('contextmenu', preventEverything, true);
document.addEventListener('mousewheel', preventEverything, { capture: true, passive: false });

Tôi không thực sự biết lý do bạn muốn làm điều này (ngoại trừ có thể để chơi khăm ai đó), nhưng việc suy nghĩ về những gì đang xảy ra ở đây và nhận ra lý do tại sao nó tạo ra tình huống như vậy là điều hữu ích.

Tất cả các sự kiện đều bắt nguồn từ window, vì vậy trong đoạn mã này, chúng ta sẽ dừng tất cả các sự kiện click, keydown, mousedown, contextmenumousewheel ngay lập tức để chúng không bao giờ đến được bất kỳ phần tử nào có thể đang theo dõi chúng. Chúng ta cũng gọi stopImmediatePropagation để ngăn chặn mọi trình xử lý được kết nối với tài liệu sau trình xử lý này.

Xin lưu ý rằng stopPropagation()stopImmediatePropagation() không phải (ít nhất là không phải phần lớn) những gì khiến trang trở nên vô dụng. Chúng chỉ đơn giản là ngăn các sự kiện diễn ra ở nơi mà chúng sẽ diễn ra.

Nhưng chúng ta cũng gọi preventDefault(), bạn sẽ nhớ rằng phương thức này ngăn chặn hành động mặc định. Vì vậy, mọi thao tác mặc định (chẳng hạn như cuộn bằng bánh xe chuột, cuộn hoặc làm nổi bật hoặc nhấn phím tab, nhấp vào đường liên kết, hiển thị trình đơn theo bối cảnh, v.v.) đều bị ngăn chặn, do đó trang sẽ ở trạng thái khá vô dụng.

Lời cảm ơn

Hình ảnh chính của Tom Wilson trên Unsplash.