9 hiệu ứng css parallax scrolling, không cần JS
CSS parallax scrolling là cách tạo cảm giác chiều sâu khi cuộn trang, bằng cách cho các lớp — nền, chữ, thẻ — di chuyển với tốc độ khác nhau, chỉ bằng
Tự chạy · bấm ô để tới mục đó · cả 9 trong một zip
- 01 Nền trôi chậm
- 02 Phối cảnh 3 lớp
- 03 Chữ trôi qua ảnh
- 04 Hero co lại khi cuộn
- 05 Timeline cuộn ngang
- 06 Thẻ xếp lớp
- 07 Sticky kết hợp parallax
- 08 Parallax theo chuột
- 09 Zoom out khi cuộn
Chín hiệu ứng dưới đây xếp theo độ phức tạp của lớp: từ một lớp nền trôi chậm (01) đến ba lớp phối cảnh thật (02), rồi tới những biến thể dùng sticky, timeline ngang hoặc chuột thay cho cuộn dọc thuần túy. Trang demo không cuộn được, nên lưới 3×3 phía trên lặp lại chuyển động mỗi 2 giây trong một iframe cố định — bạn chỉ cần xem; các số đo dưới mỗi mục lấy từ chuyển động cuộn thật, không phải từ vòng lặp trình diễn này. animation-timeline hiện chạy trên Chrome 115+ và Safari 26+, còn Firefox bản ổn định vẫn cần bật cờ tính đến 9/2026.
01Nền trôi chậm
Chỉ một lớp nền di chuyển chậm hơn tốc độ cuộn, tạo khoảng cách với chữ phía trước và sinh ra cảm giác chiều sâu. Dùng cho nền hero trang landing hoặc banner đầu section.
.bgslow__bg {
position: absolute;
inset: -20% -10%;
background: radial-gradient($color 0 46px, transparent 47px) 24% 30%;
@supports (animation-timeline: scroll()) {
animation: drift linear both;
animation-timeline: scroll(root);
}
}
@keyframes drift {
from { transform: translateY(0); }
to { transform: translateY(-24px); }
}
02Phối cảnh 3 lớp
Ba lớp sau, giữa, trước di chuyển với tốc độ khác nhau, tạo phối cảnh thật cho sân khấu. Hợp với trang giới thiệu game hoặc section kể chuyện thương hiệu.
.layer3__back { animation: drift-back linear both; animation-timeline: scroll(root); }
.layer3__mid { animation: drift-mid linear both; animation-timeline: scroll(root); }
.layer3__front { animation: drift-front linear both; animation-timeline: scroll(root); }
@keyframes drift-back { from { transform: translateY(0); } to { transform: translateY(-10px); } }
@keyframes drift-mid { from { transform: translateY(0); } to { transform: translateY(-26px); } }
@keyframes drift-front { from { transform: translateY(0); } to { transform: translateY(-46px); } }
03Chữ trôi qua ảnh
Ảnh gần như đứng yên, chỉ chữ di chuyển nhanh hơn tốc độ cuộn rồi trượt qua bên trên ảnh. Hợp với hero kiểu tạp chí hoặc tiêu đề photo essay.
.textimg__image {
animation: image-drift linear both;
animation-timeline: scroll(root);
}
.textimg__text {
mix-blend-mode: difference;
animation: text-drift linear both;
animation-timeline: scroll(root);
}
@keyframes image-drift { from { transform: translateY(0); } to { transform: translateY(-14px); } }
@keyframes text-drift { from { transform: translateY(60px); } to { transform: translateY(-70px); } }
04Hero co lại khi cuộn
Tiêu đề hero nhỏ dần và mờ dần trong lúc bạn cuộn, nhường chỗ cho section tiếp theo. Hợp với màn hình đầu trang landing hoặc phần mở đầu giới thiệu app.
.heroshrink__title {
animation: shrink linear both;
animation-timeline: scroll(root);
}
@keyframes shrink {
from { transform: scale(1); opacity: 1; }
to { transform: scale(.55); opacity: .25; }
}
05Timeline cuộn ngang
Cuộn dọc như bình thường nhưng dãy thẻ lại trôi ngang, đọc như một timeline. Hợp với trang lịch sử công ty hoặc roadmap sản phẩm.
.hline__track {
display: flex;
gap: 14px;
animation: travel linear both;
animation-timeline: scroll(root);
}
@keyframes travel {
from { transform: translateX(0); }
to { transform: translateX(-120px); }
}
06Thẻ xếp lớp
Ba thẻ xếp chồng lên nhau bong ra với tốc độ khác nhau mỗi khi bạn cuộn, để lộ chiều sâu bên dưới. Hợp với thẻ giới thiệu tính năng hoặc so sánh gói giá.
.depthcards__card--1 { animation: peel-1 linear both; animation-timeline: scroll(root); }
.depthcards__card--2 { animation: peel-2 linear both; animation-timeline: scroll(root); }
.depthcards__card--3 { animation: peel-3 linear both; animation-timeline: scroll(root); }
@keyframes peel-1 { from { transform: translateY(0) scale(1); } to { transform: translateY(-34px) scale(1); } }
@keyframes peel-2 { from { transform: translateY(9px) scale(.94); } to { transform: translateY(-25px) scale(1); } }
@keyframes peel-3 { from { transform: translateY(18px) scale(.88); } to { transform: translateY(-30px) scale(1); } }
07Sticky kết hợp parallax
Chữ phía trước dính cố định trên màn hình bằng position: sticky, chỉ nền phía sau tiếp tục trôi qua. Hợp với section kể chuyện hoặc giải thích thông số sản phẩm.
.stickypar__caption {
position: sticky;
top: 40%;
}
.stickypar__bg {
animation: bg-drift linear both;
animation-timeline: scroll(root);
}
@keyframes bg-drift {
from { transform: translateY(0); }
to { transform: translateY(-40px); }
}
08Parallax theo chuột
Các lớp bám theo hướng con trỏ di chuyển với biên độ khác nhau, tạo cảm giác nổi khối mà không cần cuộn trang. Hợp với showcase sản phẩm hoặc hero portfolio.
.mousepar__layer {
transition: transform 120ms ease-out;
// JS đặt --mx, --my từ pointermove; --depth khác nhau theo từng lớp
transform: translate(
calc(var(--mx, 0) * var(--depth) * 22px),
calc(var(--my, 0) * var(--depth) * 22px)
);
}
09Zoom out khi cuộn
Ảnh phóng to sẵn thu nhỏ dần khi bạn cuộn, để lộ layout xung quanh nó. Hợp với màn hình đầu trang chi tiết sản phẩm hoặc phần mở đầu gallery.
.zoomout__media {
transform: scale(1.45);
animation: zoom-out linear both;
animation-timeline: scroll(root);
}
@keyframes zoom-out {
from { transform: scale(1.45); }
to { transform: scale(1); }
}
Chỗ dễ hỏng — cái bẫy nằm ở đâu?
Cả 9 hiệu ứng đều đặt code thật trong @supports (animation-timeline: scroll()), và bẫy dễ mắc nhất là quên nhánh dự phòng: trình duyệt chưa hỗ trợ không báo lỗi gì cả, lớp đó chỉ đứng yên, còn phần còn lại của trang vẫn chạy bình thường nên rất dễ bỏ sót khi test nhanh trên một trình duyệt mới. Bẫy thứ hai nằm ở mục 03 — mix-blend-mode: difference đọc màu nền ngay bên dưới nó, nên nếu bạn đổi ảnh nền mà quên kiểm tra lại độ tương phản, chữ có thể biến mất hoàn toàn trên một vài đoạn ảnh. Bẫy thứ ba là hiệu năng: cả 9 hiệu ứng chỉ animate transform và opacity, không đụng vào width hay top, vì hai thuộc tính đó buộc trình duyệt tính lại layout ở từng frame còn transform thì không.
Toàn bộ 9 file, kèm nhánh dự phòng và biến thể đã chỉnh sẵn, nằm trong file nén mà mật khẩu là x6462unj viết liền không dấu cách.
Khả năng tiếp cận
Cả 9 hiệu ứng đều tắt được qua prefers-reduced-motion: reduce, nhưng mỗi hiệu ứng giữ lại một phần khác nhau thay vì đứng hình hoàn toàn. Mục 01, 02, 05, 06, 07 và 09 dừng animation và giữ nguyên vị trí cuối; mục 03 giữ ảnh và chữ tĩnh nhưng vẫn giữ mix-blend-mode để không mất độ tương phản; mục 04 giữ tiêu đề ở kích thước gốc thay vì đứng ở giữa quá trình co lại; mục 08 tắt transition theo chuột hoàn toàn. MDN có tài liệu đầy đủ về prefers-reduced-motion và cách hệ điều hành bật nó.
@media (prefers-reduced-motion: reduce) {
.bgslow__bg, .layer3__back, .layer3__mid, .layer3__front,
.textimg__image, .textimg__text, .heroshrink__title,
.hline__track, .depthcards__card--1, .depthcards__card--2,
.depthcards__card--3, .stickypar__bg, .zoomout__media {
animation: none !important;
transform: none;
}
}
Các hiệu ứng cuộn khác của trang này gom trong chuyên mục CSS, còn cách trang chọn hiệu ứng để viết nằm ở trang giới thiệu.
FAQ
animation-timeline: scroll() có thay được thư viện parallax dùng JS không?
Được với phần lớn trường hợp. Chín hiệu ứng ở trên đều chạy hoàn toàn bằng CSS, không cần thư viện nào tính toán vị trí cuộn mỗi frame, nên nhẹ hơn và không có độ trễ giữa lúc cuộn và lúc lớp di chuyển. Chỉ mục 08 (theo chuột) cần một đoạn JS ngắn, vì đó là chuyển động theo con trỏ chứ không phải theo cuộn.
Vì sao lưới demo phía trên không cuộn được như trang thật?
Vì mỗi ô demo nằm trong một iframe cố định 480×300, không có nội dung để cuộn thật sự, nên mỗi ô lặp lại đúng chuyển động đó bằng một animation 2 giây thay vì đọc animation-timeline: scroll(). Bản trong file zip vẫn dùng scroll() thật; bạn cần xem trên trang có nội dung dài mới thấy đúng cảm giác.
Dùng cả 9 hiệu ứng trên cùng một trang có ổn không?
Không nên. Mỗi hiệu ứng gắn với một vị trí cụ thể trên trang — nền hero, một section kể chuyện, một dãy thẻ — nên dùng hai đến ba hiệu ứng khác khu vực trong cùng một trang là hợp lý, còn dùng cả 9 liên tiếp sẽ khiến người đọc chóng mặt vì tốc độ cuộn cảm nhận được liên tục thay đổi.