Tối qua ngồi refactor lại một đống event handlers cho dự án, mình chợt nhận ra là có mấy cái event trong JavaScript mà trước giờ mình ít dùng nhưng lại cực kì hữu ích. Hóa ra sau bao năm làm frontend, mình vẫn còn lắm điều để học.
Hôm nay mình sẽ chia sẻ một số event listener thú vị mà có thể bạn chưa biết, hoặc biết rồi nhưng chưa từng nghĩ đến việc sử dụng chúng trong dự án của mình.
1. hashchange - Theo dõi thay đổi phần hash của URL
Đây là event mình mới khám phá gần đây. Nó cho phép bạn theo dõi khi phần hash (dấu # và ký tự phía sau) của URL thay đổi:
// Lấy hash ban đầu từ URL
const id = window.location.hash.slice(1);
// Lắng nghe khi hash thay đổi
window.addEventListener("hashchange", () => {
const newId = window.location.hash.slice(1);
console.log(`Hash đã thay đổi từ ${id} thành ${newId}`);
// Thực hiện các hành động dựa trên hash mới
});
Event này cực kỳ hữu ích khi bạn muốn tạo Single Page Application (SPA) đơn giản mà không cần dùng thư viện routing phức tạp. Ví dụ:
function renderPage() {
const page = window.location.hash.slice(1) || "home";
// Ẩn tất cả các section
document.querySelectorAll("section").forEach((section) => {
section.style.display = "none";
});
// Hiển thị section tương ứng với hash
const currentSection = document.getElementById(page);
if (currentSection) {
currentSection.style.display = "block";
}
}
// Render lần đầu khi trang tải
renderPage();
// Render lại khi hash thay đổi
window.addEventListener("hashchange", renderPage);
Với đoạn code đơn giản này, bạn đã có một hệ thống routing cơ bản dựa vào hash. Người dùng có thể điều hướng giữa các trang bằng các liên kết như <a href="#about">About</a>
, <a href="#contact">Contact</a>
,… mà không cần tải lại trang.
2. beforeunload - Xác nhận khi người dùng rời trang
Event này kích hoạt trước khi người dùng rời khỏi trang, cho phép bạn hiển thị một thông báo xác nhận:
window.addEventListener("beforeunload", (event) => {
// Nếu có dữ liệu chưa lưu
if (hasUnsavedChanges) {
// Hiển thị thông báo xác nhận
event.preventDefault();
// Chrome yêu cầu đặt returnValue để hiển thị dialog
event.returnValue = "";
// Thông báo (trình duyệt hiện đại sẽ hiển thị thông báo mặc định)
return "Bạn có dữ liệu chưa lưu. Bạn có chắc muốn rời trang?";
}
});
Mình đã từng dùng event này để ngăn người dùng vô tình đóng tab khi đang nhập form hoặc đang trong quá trình chỉnh sửa nội dung. Đặc biệt hữu ích cho các ứng dụng như trình soạn thảo văn bản, form đăng ký phức tạp, hoặc bất kỳ tình huống nào mà việc mất dữ liệu có thể gây khó chịu cho người dùng.
Lưu ý rằng các trình duyệt hiện đại đã hạn chế khả năng tùy chỉnh thông báo này để tránh lạm dụng, vì vậy bạn không thể kiểm soát hoàn toàn nội dung thông báo. Thay vào đó, trình duyệt sẽ hiển thị một thông báo tiêu chuẩn.
3. visibilitychange - Phát hiện khi tab bị ẩn hoặc hiển thị
Event này kích hoạt khi trạng thái hiển thị của trang thay đổi - ví dụ khi người dùng chuyển sang tab khác và quay lại:
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
console.log("Tab đang được hiển thị");
// Tiếp tục phát video, cập nhật dữ liệu, v.v.
videoElement.play();
} else {
console.log("Tab đã bị ẩn");
// Tạm dừng video, dừng animation để tiết kiệm tài nguyên
videoElement.pause();
}
});
Mình thường dùng event này để:
- Tạm dừng video hoặc animation khi người dùng không xem để tiết kiệm tài nguyên
- Tạm dừng các tác vụ không cần thiết khi tab không được hiển thị
- Cập nhật dữ liệu khi người dùng quay lại tab sau một thời gian
- Hiển thị thông báo “Bạn đã quay lại!” khi người dùng trở lại sau một thời gian dài
4. online/offline - Phát hiện thay đổi kết nối internet
Hai event này cho phép bạn phát hiện khi người dùng mất kết nối internet hoặc kết nối trở lại:
// Khi người dùng mất kết nối
window.addEventListener("offline", () => {
console.log("Mất kết nối internet!");
// Hiển thị thông báo cho người dùng
showNotification("Bạn đã offline. Một số tính năng có thể không hoạt động.");
// Lưu dữ liệu vào localStorage để tránh mất dữ liệu
saveDataLocally();
});
// Khi người dùng kết nối trở lại
window.addEventListener("online", () => {
console.log("Đã kết nối internet trở lại!");
// Thông báo cho người dùng
showNotification("Bạn đã online trở lại!");
// Đồng bộ dữ liệu đã lưu trong localStorage lên server
syncDataWithServer();
});
// Kiểm tra trạng thái kết nối hiện tại
if (navigator.onLine) {
console.log("Hiện tại đang online");
} else {
console.log("Hiện tại đang offline");
}
Event này đặc biệt hữu ích cho các ứng dụng Progressive Web App (PWA) hoặc các ứng dụng cần hoạt động offline. Bạn có thể sử dụng nó để:
- Thông báo cho người dùng về trạng thái kết nối
- Chuyển đổi giữa chế độ online và offline
- Lưu dữ liệu tạm thời khi offline và đồng bộ khi online
- Tắt các tính năng yêu cầu kết nối internet khi offline
5. deviceorientation - Phát hiện thay đổi hướng thiết bị
Event này kích hoạt khi người dùng xoay thiết bị di động, cung cấp thông tin về góc xoay theo ba trục:
window.addEventListener("deviceorientation", (event) => {
// Alpha: Xoay quanh trục z (0-360)
const alpha = event.alpha;
// Beta: Xoay quanh trục x (-180 đến 180)
const beta = event.beta;
// Gamma: Xoay quanh trục y (-90 đến 90)
const gamma = event.gamma;
console.log(`Alpha: ${alpha}, Beta: ${beta}, Gamma: ${gamma}`);
// Sử dụng thông tin này để điều khiển game, thay đổi UI, v.v.
updateGameControls(alpha, beta, gamma);
});
Lưu ý rằng trên các trình duyệt hiện đại, bạn cần xin quyền truy cập vào cảm biến:
if (typeof DeviceOrientationEvent.requestPermission === "function") {
// iOS 13+ yêu cầu xin quyền
DeviceOrientationEvent.requestPermission()
.then((permissionState) => {
if (permissionState === "granted") {
window.addEventListener("deviceorientation", handleOrientation);
}
})
.catch(console.error);
} else {
// Xử lý cho các thiết bị khác
window.addEventListener("deviceorientation", handleOrientation);
}
Mình đã từng sử dụng event này để tạo các trò chơi điều khiển bằng cách nghiêng thiết bị, tạo hiệu ứng parallax dựa trên hướng thiết bị, hoặc thậm chí là tạo một ứng dụng thước đo độ nghiêng đơn giản.
6. resize - Theo dõi thay đổi kích thước cửa sổ
Đây có thể là event quen thuộc với nhiều người, nhưng có một số kỹ thuật tối ưu khi sử dụng nó mà không phải ai cũng biết:
// Sử dụng debounce để tránh gọi hàm quá nhiều lần
let resizeTimeout;
window.addEventListener("resize", () => {
// Hủy timeout cũ nếu có
clearTimeout(resizeTimeout);
// Đặt timeout mới
resizeTimeout = setTimeout(() => {
console.log("Cửa sổ đã thay đổi kích thước!");
// Cập nhật layout, tính toán lại kích thước, v.v.
updateLayout();
}, 250); // Chỉ thực hiện sau khi người dùng ngừng resize trong 250ms
});
Một cách tiếp cận hiện đại hơn là sử dụng ResizeObserver API:
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
const { width, height } = entry.contentRect;
console.log(`Element size: ${width}x${height}`);
// Cập nhật UI dựa trên kích thước mới
}
});
// Theo dõi một phần tử cụ thể
resizeObserver.observe(document.querySelector(".container"));
ResizeObserver hiệu quả hơn vì nó chỉ kích hoạt khi phần tử được theo dõi thay đổi kích thước, không phải toàn bộ cửa sổ, và không cần phải debounce.
7. IntersectionObserver - Phát hiện khi phần tử xuất hiện trong viewport
Đây không phải là một event listener thông thường, mà là một API mạnh mẽ cho phép bạn phát hiện khi một phần tử xuất hiện trong viewport:
const observer = new IntersectionObserver(
(entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log("Phần tử đã xuất hiện trong viewport!");
// Thực hiện hành động khi phần tử xuất hiện
entry.target.classList.add("visible");
// Nếu chỉ cần quan sát một lần, có thể ngừng theo dõi
observer.unobserve(entry.target);
}
});
},
{
root: null, // viewport
rootMargin: "0px", // margin xung quanh root
threshold: 0.1, // kích hoạt khi ít nhất 10% phần tử hiển thị
}
);
// Bắt đầu theo dõi các phần tử
document.querySelectorAll(".lazy-load").forEach((el) => {
observer.observe(el);
});
Mình thường dùng IntersectionObserver để:
- Lazy load hình ảnh và video khi chúng xuất hiện trong viewport
- Kích hoạt animation khi phần tử xuất hiện (scroll animations)
- Thực hiện infinite scrolling - tải thêm nội dung khi người dùng cuộn đến cuối trang
- Theo dõi hiệu suất quảng cáo - kiểm tra xem quảng cáo có được nhìn thấy không
Kết luận
Các event listener này đã giúp mình cải thiện đáng kể trải nghiệm người dùng trong các dự án web. Chúng cho phép tạo ra các tương tác phức tạp mà không cần dùng đến các thư viện nặng nề.
Hy vọng bài viết này giúp bạn khám phá thêm những công cụ mới để làm phong phú ứng dụng web của mình. Nếu bạn biết thêm event listener hữu ích nào khác, hãy chia sẻ trong phần bình luận nhé!