Giải phẫu Đối tượng Window và Quản lý Tương tác Giao diện với JavaScript

Tổng quan về Mô hình Đối tượng Trình duyệt (BOM)

Mô hình đối tượng trình duyệt (BOM) cung cấp khả năng tương tác giữa mã JavaScript và cửa sổ trình duyệt một cách độc lập với nội dung trang web. Nhân lõi của BOM chính là đối tượng window.

Đối tượng window đóng vai trò kép trong kiến trúc JavaScript:

  • Là giao diện để truy cập vào khung cửa sổ trình duyệt.
  • Là đối tượng toàn cục, mọi biến và hàm được khai báo ở phạm vi này đều trở thành thuộc tính hoặc phương thức của nó.

Sự kiện hoàn tất tải trang

Việc xác định thời điểm trang web sẵn sàng là rất quan trọng. Thuộc tính window.onload sẽ kích hoạt sau khi toàn bộ tài nguyên bao gồm DOM, hình ảnh, CSS và Flash đã tải xong. Tuy nhiên, cách viết truyền thống này chỉ cho phép gán một sự kiện duy nhất, nếu có nhiều lần gán thì chỉ lần cuối cùng có hiệu lực.

Khuyến nghị sử dụng window.addEventListener('load', handler) để đảm bảo hỗ trợ nhiều hàm xử lý. Đối với trình duyệt IE9 trở lên, có thể dùng document.addEventListener('DOMContentLoaded', handler). Sự kiện này chỉ chờ cấu trúc DOM được xây dựng xong mà không đợi hình ảnh hay các tập tin ngoại lai khác, do đó tốc độ phản hồi nhanh hơn sự kiện load.

Theo dõi thay đổi kích thước cửa sổ

Sự kiện resize được kích hoạt mỗi khi kích thước cửa sổ trình duyệt thay đổi, thường được dùng để thực hiện thiết kế responsive (thích ứng).

// Ví dụ ẩn hiển thị phần tử dựa trên độ rộng màn hình
window.addEventListener('resize', () => {
    const viewportWidth = window.innerWidth;
    const container = document.querySelector('.main-layout');
    
    if (viewportWidth < 768) {
        container.style.display = 'flex';
    } else {
        container.style.display = 'grid';
    }
});

Hệ thống Định thời gian (Timers)

Đối tượng window cung cấp hai phương thức chính để quản lý thời gian thực thi:

  • setTimeout(callback, delay): Thực hiện hàm sau khoảng thời gian trễ nhất định (tính bằng mili-giây), chỉ chạy một lần.
  • setInterval(callback, interval): Lặp lại việc gọi hàm theo chu kỳ cố định cho đến khi bị hủy.

Ví dụ về cơ chế trì hoãn

Tham số đầu vào có thể là tên hàm đã định nghĩa hoặc hàm ẩn danh. Để tránh xung đột khi có nhiều bộ đếm, nên lưu ID của bộ hẹn giờ vào một biến cụ thể.

function handleNotification() {
    console.log('Thông báo mới xuất hiện');
}

const notificationTimer = setTimeout(handleNotification, 3000);

Xử lý và xóa bỏ bộ đếm

Khi người dùng tương tác trước khi hết thời gian chờ (ví dụ nút gửi tin nhắn), cần dừng bộ đếm lại để tránh lỗi logic.

const actionBtn = document.getElementById('sms-btn');
let remainingTime = 60;

actionBtn.addEventListener('click', function() {
    actionBtn.disabled = true;
    
    // Lưu ID vào thuộc tính nút để dễ dàng truy cập sau này
    if (actionBtn.intervalId) clearInterval(actionBtn.intervalId);
    
    actionBtn.intervalId = setInterval(() => {
        remainingTime--;
        actionBtn.textContent = `Gửi lại (${remainingTime}s)`;
        
        if (remainingTime <= 0) {
            clearInterval(actionBtn.intervalId);
            actionBtn.disabled = false;
            actionBtn.textContent = 'Gửi SMS';
            remainingTime = 60;
        }
    }, 1000);
});

Cơ chế hàng đợi sự kiện và Vòng lặp Event Loop

JavaScript chạy đơn luồng (single-threaded). Mọi tác vụ đồng bộ (Synchronous) được thực thi ngay lập tức trên gọi ngăn xếp (Call Stack). Tác vụ bất đồng bộ (Asynchronous) như sự kiện click, tải tài nguyên hoặc timer sẽ được đưa vào hàng đợi nhiệm vụ (Task Queue).

Quá trình diễn ra như sau: Hệ thống thực thi hết mã đồng bộ, sau đó kiểm tra hàng đợi bất đồng bộ và đẩy các hàm callback vào ngăn xếp để chạy tiếp. Cơ chế này gọi là Event Loop.

API Điều hướng và Trạng thái

Đối tượng Location

window.location chứa thông tin URL hiện tại của trang. Nó hỗ trợ phân tích chuỗi truy vấn, hash và chuyển hướng.

const currentHost = location.hostname;
const searchParams = location.search; // Lấy tham số GET

// Chuyển hướng trang (lưu lịch sử duyệt web)
// location.assign('https://example.com');

// Thay thế trang hiện tại (không lưu lịch sử, không thể bấm back)
// location.replace('https://example.com');

Đối tượng History

Cho phép điều khiển lịch sử tab trình duyệt:

  • history.back(): Quay lại trang trước.
  • history.forward(): Đi tới trang sau.
  • history.go(n): Di chuyển n bước trong lịch sử (dương là đi tới, âm là quay lại).

Đối tượng Navigator

Cung cấp thông tin về trình duyệt và hệ thống máy chủ, thông dụng nhất là navigator.userAgent dùng để kiểm tra phiên bản trình duyệt hoặc thiết bị di động.

Đo lường Kích thước và Vị trí Phần tử

Để xử lý bố cục và hiệu ứng chuyển động, chúng ta cần hiểu rõ ba nhóm thuộc tính đo đạc chiều cao/rộng của phần tử:

Chuỗi Offset (Vị trí + Viền)

offsetTopoffsetLeft trả về khoảng cách từ mép trên/trái của phần tử đến vị trí chứa nó gần nhất có định dạng tương đối (position: relative). Nếu không tìm thấy phần tử cha được定位, giá trị sẽ dựa trên body. Đặc điểm nổi bật là kết quả kèm theo chiều rộng và đường viền.

Chuỗi Client (Kích thước hiển thị)

clientWidthclientHeight trả về kích thước vùng nội dung cộng với padding nhưng loại trừ border và scrollbar. Thường dùng để lấy kích thước thực tế của phần tử nhìn thấy.

Chuỗi Scroll (Thanh cuộn)

scrollTopscrollLeft xác định số pixel đã bị cuộn đi. Khi cuộn trang web, thanh cuộn ngang/thẳng đứng tăng lên.

  • Để lấy khoảng cách cuộn của toàn bộ trang web, dùng window.pageYOffset hoặc document.documentElement.scrollTop.
  • Thuộc tính onscroll kích hoạt khi thanh cuộn thay đổi.

So sánh Mouse Events

Giữa mouseovermouseenter:

  • mouseover: Có hiện tượng lan truyền (bubbling), sẽ trigger khi chuột đi vào phần tử con.
  • mouseenter: Không lan truyền, chỉ trigger khi chuột vào đúng phần tử cha được chọn, hiệu suất tốt hơn.

Xây dựng Hàm Hoạt họa (Animation)

Nguyên lý di chuyển tuyến tính

Hoạt họa cơ bản sử dụng setInterval để cập nhật vị trí CSS liên tục. Cần nhớ rằng phần tử phải có thuộc tính định vị (position: absolute hoặc relative) để thuộc tính style.left có tác dụng.

function startLinearMove(element, targetPosition) {
    // Xóa bộ đếm cũ nếu người dùng kích hoạt nhiều lần liên tiếp
    if (element.timer) clearInterval(element.timer);
    
    element.timer = setInterval(() => {
        const currentPosition = element.offsetLeft;
        
        if (currentPosition >= targetPosition) {
            clearInterval(element.timer);
            return;
        }
        
        // Tăng bước nhảy cố định
        element.style.left = (currentPosition + 1) + 'px';
    }, 20);
}

Hiệu ứng Chậm dần (Easing)

Để chuyển động mượt mà tự nhiên hơn, tốc độ không được cố định. Công thức tính bước nhảy (step) giảm dần theo thời gian dựa trên khoảng cách còn lại.

function startEasingMove(box, destination, onComplete) {
    if (box.timer) clearInterval(box.timer);
    
    box.timer = setInterval(() => {
        let currentPos = box.offsetLeft;
        let speed = (destination - currentPos) / 10;
        
        // Làm tròn tốc độ để tránh giá trị thập phân gây jitter
        speed = speed > 0 ? Math.ceil(speed) : Math.floor(speed);
        
        if (currentPos === destination) {
            clearInterval(box.timer);
            if (typeof onComplete === 'function') {
                onComplete();
            }
        } else {
            box.style.left = (currentPos + speed) + 'px';
        }
    }, 15);
}

Xuất bản trên Thiết bị Di động

Sự kiện Cảm ứng (Touch Events)

Khác với máy tính bảng, điện thoại sử dụng các sự kiện cảm biến:

  • touchstart: Khi ngón tay chạm vào bề mặt màn hình.
  • touchmove: Khi ngón tay di chuyển trong khi vẫn đang chạm.
  • touchend: Khi ngón tay rời khỏi màn hình.

Xử lý kéo thả (Drag & Drop)

Để triển khai chức năng kéo thả phần tử trên mobile:

  1. Gán sự kiện touchstart để lưu tọa độ ban đầu của ngón tay và vị trí gốc của phần tử.
  2. Gán sự kiện touchmove, tính toán vector lệch (pageX hiện tại - pageX ban đầu) và áp dụng vào style left/top của phần tử.
  3. Dùng e.preventDefault() để ngăn trình duyệt cuộn trang khi đang thao tác kéo.

Vấn đề trễ Click và ClassList

Trên iOS/Android, sự kiện click thường bị trễ 300ms để chờ thao tác double-tap zoom. Cách giải quyết bao gồm tắt zoom trên meta tag hoặc sử dụng thư viện như FastClick, hoặc tự xử lý qua sự kiện Touch.

Việc quản lý class nên dùng element.classList thay vì thao tác trực tiếp string class name:

const item = document.querySelector('.item');
// Thêm class
item.classList.add('active-state');
// Xóa class
item.classList.remove('hidden');
// Chuyển đổi trạng thái (toggle)
item.classList.toggle('selected');

Trình bày Lưu trữ Dữ liệu Cục bộ

Trình duyệt cung cấp hai cơ chế lưu trữ dữ liệu phía khách hàng (Client-side):

Session Storage

Dữ liệu tồn tại trong phiên làm việc của cửa sổ trình duyệt đang mở. Khi người dùng đóng tab/cửa sổ, dữ liệu sẽ bị mất. Phù hợp cho dữ liệu tạm thời.

// Thêm dữ liệu
sessionStorage.setItem('token', 'abc-123');
// Đọc dữ liệu
const savedToken = sessionStorage.getItem('token');
// Xóa dữ liệu
sessionStorage.removeItem('token');

Local Storage

Dữ liệu được lưu vĩnh viễn cho đến khi người dùng chủ động xóa hoặc clear cache. Dữ liệu này chia sẻ chung giữa các tab của cùng một nguồn (domain).

// Lưu tên đăng nhập
localStorage.setItem('username', 'UserJohn');
// Kiểm tra xem có lưu sẵn chưa
if (localStorage.getItem('username')) {
    document.getElementById('input-name').value = localStorage.getItem('username');
}

Thẻ: JavaScript browser-api dom-manipulation event-handling web-animation

Đăng vào ngày 7 tháng 6 lúc 05:27