Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> JavaScript

Sự kiện cuộn (Scroll Events) trong JavaScript

Khái niệm

Sự kiện scroll kích hoạt khi người dùng cuộn nội dung (window hoặc element có overflow). Có thể dùng để: lazy load, infinite scroll, parallax, sticky header, progress bar.

Ví dụ cơ bản

<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Scroll Events</title>
    <style>
        body { font-family: 'Segoe UI', sans-serif; padding: 20px; height: 300vh; } /* Tạo scrollbar */
        .result { 
            position: fixed; top: 20px; right: 20px; 
            font-size: 16px; font-weight: 500; color: #6a1b9a; 
            padding: 15px; background: #f3e5f5; border-radius: 4px; 
            box-shadow: 0 2px 10px rgba(0,0,0,0.1); z-index: 1000;
            min-width: 250px;
        }
        h3 { position: fixed; top: 100px; right: 20px; color: #555; z-index: 1000; }
        .content { margin-top: 150px; }
        .section { height: 80vh; display: flex; align-items: center; justify-content: center; font-size: 32px; font-weight: bold; color: white; }
        .s1 { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
        .s2 { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); }
        .s3 { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); }
    </style>
</head>
<body>
    <h1>Scroll Events trong JavaScript</h1>
    <div class="result" id="result">Cuộn trang để xem thông tin...</div>
    <h3>Cuộn thanh cuộn để thấy thông tin cập nhật</h3>
    
    <div class="content">
        <div class="section s1">Section 1</div>
        <div class="section s2">Section 2</div>
        <div class="section s3">Section 3</div>
    </div>

    <script>
        const resEle = document.getElementById('result');
        
        // Throttle để tối ưu performance
        let ticking = false;
        window.addEventListener('scroll', () => {
            if (!ticking) {
                window.requestAnimationFrame(() => {
                    updateScrollInfo();
                    ticking = false;
                });
                ticking = true;
            }
        }, { passive: true }); // Passive listener cho performance

        function updateScrollInfo() {
            const scrollY = window.scrollY || window.pageYOffset;
            const docHeight = document.documentElement.scrollHeight - window.innerHeight;
            const scrollPercent = ((scrollY / docHeight) * 100).toFixed(1);
            
            resEle.innerHTML = `
                📍 Scroll Y: ${scrollY}px
📏 Trang cao: ${docHeight}px
📊 Tiến độ: ${scrollPercent}%
🎯 document.scrollingElement: ${document.scrollingElement?.tagName} `; } </script> </body> </html>

Kết quả

Khi cuộn: Hiển thị vị trí scrollY, chiều cao trang, % tiến độ cập nhật realtime.

Tối ưu Performance (Quan trọng)

1. Passive Event Listener

// Cho phép browser scroll ngay mà không chờ preventDefault
window.addEventListener('scroll', handler, { passive: true });

2. Throttle / Debounce

// Throttle với requestAnimationFrame (mượt nhất)
let ticking = false;
window.addEventListener('scroll', () => {
    if (!ticking) {
        requestAnimationFrame(() => {
            handleScroll();
            ticking = false;
        });
        ticking = true;
    }
});

// Hoặc dùng lodash throttle
// import { throttle } from 'lodash';
// window.addEventListener('scroll', throttle(handleScroll, 16)); // ~60fps

3. Intersection Observer (Thay thế scroll cho nhiều trường hợp)

// Lazy load images
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            observer.unobserve(img);
        }
    });
});

document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));

Ứng dụng thực tế

1. Sticky Header

const header = document.querySelector('header');
const headerHeight = header.offsetHeight;

window.addEventListener('scroll', () => {
    header.classList.toggle('sticky', window.scrollY > headerHeight);
});

2. Scroll Progress Bar

const progressBar = document.querySelector('.progress-bar');
window.addEventListener('scroll', () => {
    const scrolled = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
    progressBar.style.width = `${scrolled}%`;
});

3. Back to Top Button

const btn = document.getElementById('backToTop');
window.addEventListener('scroll', () => {
    btn.classList.toggle('show', window.scrollY > 300);
});
btn.addEventListener('click', () => {
    window.scrollTo({ top: 0, behavior: 'smooth' });
});

4. Infinite Scroll

let loading = false;
window.addEventListener('scroll', async () => {
    if (loading) return;
    const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
    
    if (scrollTop + clientHeight >= scrollHeight - 500) {
        loading = true;
        await loadMoreData();
        loading = false;
    }
});