GODRICH

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

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.

0.4배translateY단일 레이어
.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.

0.2·0.5·0.8배세 겹translateY
.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.

1.6배mix-blend-modetranslateY
.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.

scale 1→.6opacity 동반축소+페이드
.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.

translateXscroll(root)가로 흐름
.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á.

scale 단계별z-index 3겹translateY
.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.

position sticky배경만 이동고정+흐름
.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.

pointermove레이어별 배율translate
.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.

scale 1.4→1scroll(root)줌아웃
.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.

Nhập mật khẩu file nén

Mật khẩu nằm ngay trong bài viết này. Đọc tới đó bạn sẽ thấy.