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

Cách tạo Section Counter (Bộ đếm thống kê) responsive với CSS

Giới thiệu

Section Counter (thường gọi là "Stats Counter" hoặc "Number Counter") hiển thị các chỉ số quan trọng: số lượng khách hàng, dự án hoàn thành, năm kinh nghiệm... Thường đặt ở homepage hoặc landing page để xây dựng uy tín.

Ví dụ hoàn chỉnh: Responsive Counter Grid với Font Awesome

<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Responsive Section Counter</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        * { box-sizing: border-box; }
        body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f8f9fa; padding: 2rem; }
        
        .counterSection {
            max-width: 1200px; margin: 0 auto;
            text-align: center;
        }
        .counterSection h1 { margin-bottom: 2rem; color: #2c3e50; font-size: 2.5rem; }
        
        .sectionContainer {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 20px;
            margin: 0 -10px;
        }
        
        .sectionCard {
            background: linear-gradient(135deg, #7f10ac 0%, #a810ac 100%);
            color: white;
            padding: 2.5rem 1.5rem;
            border-radius: 16px;
            box-shadow: 0 8px 24px rgba(127, 16, 172, 0.3);
            transition: transform 0.3s, box-shadow 0.3s;
        }
        .sectionCard:hover {
            transform: translateY(-4px);
            box-shadow: 0 16px 32px rgba(127, 16, 172, 0.4);
        }
        
        .sectionCard i { font-size: 3rem; margin-bottom: 1rem; display: block; opacity: 0.9; }
        .sectionCard h3 { font-size: 2.5rem; font-weight: 700; margin: 0.5rem 0; line-height: 1.2; }
        .sectionCard p { font-size: 1.1rem; opacity: 0.9; margin: 0; }
        
        /* Responsive */
        @media (max-width: 992px) {
            .sectionContainer { grid-template-columns: repeat(2, 1fr); }
        }
        @media (max-width: 576px) {
            .sectionContainer { grid-template-columns: 1fr; }
            .sectionCard h3 { font-size: 2rem; }
        }
    </style>
</head>
<body>
    <div class="counterSection">
        <h1>Thống Kê Nổi Bật</h1>
        <div class="sectionContainer">
            <div class="sectionCard">
                <i class="fas fa-mobile-alt" aria-hidden="true"></i>
                <h3 data-count="100000">0</h3>
                <p>Lượt tải ứng dụng</p>
            </div>
            <div class="sectionCard">
                <i class="fas fa-globe" aria-hidden="true"></i>
                <h3 data-count="5000">0</h3>
                <p>Đại lý trên toàn cầu</p>
            </div>
            <div class="sectionCard">
                <i class="fas fa-chart-line" aria-hidden="true"></i>
                <h3 data-count="1">0</h3>
                <p>Top Performer 5 năm liên tiếp</p>
            </div>
            <div class="sectionCard">
                <i class="fas fa-handshake" aria-hidden="true"></i>
                <h3 data-count="3000">0</h3>
                <p>Đối tác chiến lược</p>
            </div>
        </div>
    </div>

    <script>
        // Simple count-up animation
        const counters = document.querySelectorAll('[data-count]');
        const speed = 200; // lower = faster
        
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const counter = entry.target;
                    const target = +counter.dataset.count;
                    const count = +counter.innerText;
                    const increment = target / speed;
                    
                    if (count < target) {
                        counter.innerText = Math.ceil(count + increment);
                        setTimeout(() => updateCounter(counter), 1);
                    } else {
                        counter.innerText = target.toLocaleString() + (target >= 1000 ? '+' : '');
                    }
                    observer.unobserve(counter);
                }
            });
        }, { threshold: 0.5 });
        
        function updateCounter(counter) {
            const target = +counter.dataset.count;
            const count = +counter.innerText.replace(/,/g, '').replace('+', '');
            const increment = target / speed;
            if (count < target) {
                counter.innerText = Math.ceil(count + increment);
                setTimeout(() => updateCounter(counter), 1);
            } else {
                counter.innerText = target.toLocaleString() + (target >= 1000 ? '+' : '');
            }
        }
        
        counters.forEach(counter => observer.observe(counter));
    </script>
</body>
</html>

Điểm nổi bật của giải pháp này

  • CSS Grid: Layout 4 cột → 2 cột → 1 cột tự động, không cần float/clearfix.
  • Gradient background + Shadow: Card hiện đại, có chiều sâu.
  • Hover effect: Nâng card lên, tăng shadow - tương tác trực quan.
  • Font Awesome 6: Icon vector nhẹ, scale tốt, accessibility với aria-hidden.
  • Count-up Animation (JS): Số đếm từ 0 lên giá trị target khi scroll vào view (IntersectionObserver - hiệu năng cao).
  • Number formatting: toLocaleString() thêm dấu phân cách hàng nghìn (100,000).

Phiên bản CSS-only (Không đếm số, chỉ hiển thị static)

Nếu không cần animation đếm, chỉ cần HTML/CSS như ví dụ trên, bỏ phần data-count và script, viết trực tiếp số vào <h3>.

Mẹo thiết kế Counter Section

  • Chọn 3-5 chỉ số quan trọng nhất - đừng nhồi nhét quá nhiều.
  • Icon phải gợi ý đúng ý nghĩa số liệu (users, projects, awards, years...).
  • Màu sắc nhất quán với brand identity.
  • Đặt ở vị trí "above the fold" hoặc ngay sau hero section để tối đa tác động.