
Slider JavaScript làm chậm website khủng khiếp như thế nào
Sơ đồ trang
Câu chuyện về slider – kẻ “ăn cắp” performance
Slider (hay carousel) xuất hiện ở khắp mọi nơi trên web ngày nay. Dù bạn có thích hay không, thì slider vẫn là cách hay ho để trình bày nội dung, nhất là khi màn hình nhỏ.
Với các web bán hàng, slider được dùng để show sản phẩm hot, banner quảng cáo, khuyến mãi, gallery ảnh… đủ thứ trên đời! Mặc dù có nhiều tranh cãi về hiệu quả, nhưng người dùng đã quen mắt với nó rồi, nên trải nghiệm cũng khá mượt mà.
Tại sao các thư viện slider lại “nặng” đến thế?
Là developer frontend, chúng ta có cả trăm thư viện slider JavaScript để chọn. Nhưng buồn thay, hầu hết đều mắc chung một bệnh: làm website chậm như rùa. Tin tôi đi, tôi đã thử qua hết rồi!
Những “ông lớn” trong làng slider
Top 5 thư viện được dùng nhiều nhất:
- Slick Slider – Ông vua cũ
- Owl Carousel – Đối thủ nặng ký
- Flickity – Cái tên quen thuộc
- Keen-slider – Tân binh đầy hứa hẹn
- Glider.js – Nhỏ gọn nhưng mạnh mẽ
Điểm chung của tất cả: chúng chỉ cho bạn CSS cơ bản để chạy được, còn muốn đẹp thì phải tự làm thêm. Điều này thực ra khá ổn vì mình hay custom slider nhiều, nên càng ít CSS phải ghi đè càng tốt.
Thực nghiệm so sánh hiệu suất – Ai thắng ai thua?
1. Slick Slider – “Ông lão” nặng nề
Cách dùng đơn giản:
$(function () {
$('.js-slider').slick({
centerMode: true,
centerPadding: '15px',
arrows: false,
infinite: false,
});
})
Cái giá phải trả:
Bài viết liên quan
- JavaScript: 126 KB (kèm theo jQuery – nặng như chì!)
- CSS: 4.9 KB
Hiệu suất thực tế (test trên mạng 3G, CPU chậm 4 lần):
- Thời gian chặn trang (TBT): 0ms
- Hiện nội dung đầu tiên (FCP): 1989ms – gần 2 giây mới thấy gì!
- Hiện nội dung chính (LCP): 2789ms – chờ gần 3 giây
- Layout nhảy (CLS): 0.1344 – trang bị giật
- Tương tác được (TTI): 2939ms – 3 giây mới bấm được
Vấn đề lớn: Website “đơ” gần 2 giây rồi mới hiện, 3 giây mới tương tác được. Còn có hiện tượng layout nhảy nữa.
Khắc phục layout nhảy bằng CSS:
.gallery > .slide:not(:first-child) {
display: none;
}
.gallery > .slide:first-child {
padding: 0 20px;
}
Kết quả sau khi fix:
- FCP: 1356ms (cải thiện 31%)
- LCP: 2568ms (cải thiện 8%)
- CLS: 0 (hết nhảy layout!)
- TTI: 2746ms (cải thiện 6%)
2. Owl Carousel – Đối thủ cạnh tranh
Cách setup:
$(function () {
$('.js-slider').owlCarousel({
center: true,
stagePadding: 15
});
});
So sánh tài nguyên:
- JavaScript: 117 KB (nhẹ hơn Slick 7%)
- CSS: 6.8 KB (nặng hơn 38%)
Hiệu suất có khá hơn: TTI: 2357ms (nhanh hơn Slick 14%)
3. Keen Slider – Ngôi sao sáng giá
Tài nguyên siêu nhẹ:
- JavaScript: 10.3 KB (nhẹ hơn Owl tới 91%!)
- CSS: 3.7 KB (nhẹ hơn 24%)
Hiệu suất ấn tượng:
- FCP: 1275ms
- LCP: 1667ms (nhanh hơn 34%)
- TTI: 1740ms (nhanh hơn 26%)
Tại sao Keen Slider lại nhanh đến thế?
- Keen Slider chỉ cần 2.73ms để render slider
- Slick phải mất tới 125.23ms để làm cùng việc đó
- Ít tính toán phức tạp, ít thao tác DOM hơn
Bí kíp tối thượng: Slider chỉ bằng CSS
Thay vì dùng JavaScript, ta có thể tạo slider hoàn toàn bằng CSS với:
- CSS Grid – xử lý bố cục
- CSS Scroll Snap – làm hiệu ứng cuộn mượt mà
Code CSS ma thuật:
.gallery {
display: grid;
grid-gap: 10px;
grid-auto-flow: column;
grid-auto-columns: calc(100% - 20px);
padding-left: 20px;
scroll-snap-type: x mandatory;
overflow: auto;
}
.slide {
scroll-snap-align: center;
}
.slide:last-child {
position: relative;
}
.slide:last-child::after {
content: '';
position: absolute;
top: 0;
width: 20px;
height: 100%;
}
Kết quả đáng kinh ngạc:
Tài nguyên:
- JavaScript: 0 KB (không cần JS!)
- CSS: 3.5 KB
Hiệu suất thần thánh:
- FCP: 1284ms
- LCP: 1380ms (nhanh hơn 17%)
- CLS: 0 (không bị giật)
- TTI: 1358ms (nhanh hơn 22%)
Google Lighthouse Score: 100/100 🎉
Bonus: Còn có thêm tính năng accessibility tự động như điều khiển bằng phím!
Bổ sung: Các kỹ thuật tối ưu nâng cao
1. Lazy Loading cho Images
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" alt="Product image">
// Intersection Observer API
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});
2. Preload Critical Images
<link rel="preload" as="image" href="hero-image.jpg">
3. Responsive Images với srcset
<img
srcset="image-320w.jpg 320w,
image-640w.jpg 640w,
image-1024w.jpg 1024w"
sizes="(max-width: 320px) 280px,
(max-width: 640px) 600px,
1024px"
src="image-640w.jpg"
alt="Responsive image">
4. CSS Animation Performance
/* Sử dụng transform thay vì thay đổi width/height */
.slide {
transition: transform 0.3s ease;
will-change: transform;
}
.slide:hover {
transform: scale(1.05);
}
/* Tránh animation trên thuộc tính layout */
/* Tệ */
.bad-animation {
transition: width 0.3s ease;
}
/* Tốt */
.good-animation {
transition: transform 0.3s ease;
}
5. Critical CSS cho Slider
/* Inline critical CSS */
.gallery {
display: grid;
grid-auto-flow: column;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.slide {
scroll-snap-align: center;
min-width: 100%;
}
WordPress Integration
Custom Post Type cho Slider
// functions.php
function create_slider_post_type() {
register_post_type('slider',
array(
'labels' => array(
'name' => 'Sliders',
'singular_name' => 'Slider'
),
'public' => true,
'supports' => array('title', 'editor', 'thumbnail'),
'show_in_rest' => true
)
);
}
add_action('init', 'create_slider_post_type');
PHP Template cho CSS Slider
<?php
// slider-template.php
$slides = get_posts(array(
'post_type' => 'slider',
'numberposts' => -1,
'post_status' => 'publish'
));
?>
<div class="css-slider">
<div class="gallery">
<?php foreach($slides as $slide): ?>
<div class="slide">
<?php echo get_the_post_thumbnail($slide->ID, 'large'); ?>
<div class="slide-content">
<h3><?php echo $slide->post_title; ?></h3>
<p><?php echo $slide->post_content; ?></p>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
Best Practices cho Production
1. Performance Budget
// webpack.config.js
module.exports = {
performance: {
maxAssetSize: 250000, // 250kb
maxEntrypointSize: 250000,
hints: 'warning'
}
};
2. Bundle Analysis
# Phân tích bundle size
npm install --save-dev webpack-bundle-analyzer
npx webpack-bundle-analyzer dist/static/js/*.js
3. Core Web Vitals Monitoring
// Google Analytics 4 Web Vitals
import {getCLS, getFID, getFCP, getLCP, getTTFB} from 'web-vitals';
function sendToGoogleAnalytics({name, delta, value, id}) {
gtag('event', name, {
event_category: 'Web Vitals',
event_label: id,
value: Math.round(name === 'CLS' ? delta * 1000 : delta),
non_interaction: true,
});
}
getCLS(sendToGoogleAnalytics);
getFID(sendToGoogleAnalytics);
getFCP(sendToGoogleAnalytics);
getLCP(sendToGoogleAnalytics);
getTTFB(sendToGoogleAnalytics);
Checklist ra quyết định thông minh:
- Thực sự cần slider không? Nếu không, đừng tự hành hạ bản thân. Dùng grid hay layout khác đi.
- Có thể xài CSS-only không? Bạn có thật sự cần slider tự chạy mỗi 5 giây không? (Mà nói thật, cái đó phiền lắm!)
- Nếu bắt buộc phải dùng JS, chọn Keen Slider hoặc Glider.js – hai cái này nhẹ mà vẫn đủ tính năng.
Ngân sách Performance cho Slider:
- Tối ưu nhất: CSS-only (0kb JS)
- Tốt: Keen Slider (~10kb)
- Chấp nhận được: Glider.js (~20kb)
- Nên tránh: Slick + jQuery (~126kb)
Dùng cái nào khi nào:
Tình huống | Nên chọn | Điểm Performance |
---|---|---|
Gallery ảnh đơn giản | CSS-only | 100/100 |
Cần hỗ trợ touch/swipe | Keen Slider | 95-98/100 |
Nhiều tính năng phức tạp | Swiper.js (hiện đại) | 85-90/100 |
Phải hỗ trợ trình duyệt cũ | Owl Carousel | 70-80/100 |
Lời khuyên cuối: Slider có thể đẹp và tiện, nhưng đừng vì nó mà làm chậm website. Performance budget nên dành cho những thứ quan trọng hơn như framework hay state management.
Công cụ kiểm tra hiệu suất hữu ích:
- Google PageSpeed Insights – Kiểm tra Core Web Vitals
- GTmetrix – Phân tích chi tiết hiệu suất
- WebPageTest – Test trên nhiều điều kiện mạng
- Chrome DevTools – Debug và profile
- Lighthouse CI – Tự động kiểm tra trong CI/CD
Bài viết được viết lại dựa trên kinh nghiệm đau thương với những slider nặng nề 😅và tham khảo từ itnext.io