9 CSS Particle Backgrounds — dán 1 dòng là chạy
CSS particle background là kỹ thuật rải hàng chục phần tử nhỏ — chấm, vạch, mảnh nhỏ — bằng CSS thuần rồi cho chúng tự chuyển động liên tục, không cần
Tự chạy · bấm ô để tới mục đó · cả 9 trong một zip
- 01 Tuyết rơi
- 02 Ánh sao lấp lánh
- 03 Sóng SVG
- 04 Mưa rơi
- 05 Confetti rơi
- 06 Bong bóng nổi lên
- 07 Đom đóm
- 08 Cánh hoa anh đào bay
- 09 Sương mù trôi
Chín kiểu dưới đây xếp theo hướng chuyển động của phần tử, không theo mức độ hoành tráng: rơi từ trên xuống (01, 04, 05, 08), đứng yên tại chỗ mà nhấp nháy hoặc trôi ngang (02, 03), nổi từ dưới lên (06), bay dạt bất định tại chỗ (07), rồi cả khung hình cùng dao động qua lại (09). Toàn bộ chỉ dùng transform, opacity, @keyframes, :nth-child() và custom property var() — nhóm thuộc tính này đã ở mức Baseline dùng rộng rãi nên không cần rẽ nhánh cho trình duyệt cũ. Riêng filter: blur() ở mục 09 cần Chrome 53+, Safari 9.1+ và Firefox 35+ theo bảng caniuse css-filters. Mỗi mục dưới đây chỉ trích phần SCSS tính vị trí và tốc độ của các phần tử, bản đầy đủ nằm trong file zip.
01Tuyết rơi
24 vòng tròn nhỏ bắt đầu từ ngoài mép trên màn hình, mỗi vòng nhận vị trí, kích thước và thời lượng rơi riêng qua nth-child rồi trôi xuống chậm bằng translateY và opacity. Hợp với nền hero khuyến mãi mùa đông hoặc đầu trang landing cuối năm.
.p {
position: absolute; top: -8%; left: 0;
width: 6px; height: 6px; border-radius: 50%;
animation: fall 6s linear infinite;
animation-delay: calc(var(--i) * -0.28s);
}
@for $i from 1 through 24 {
.p:nth-child(#{$i}) { left: #{($i * 53) % 97}%; }
}
@keyframes fall {
0% { transform: translateY(0); opacity: 0; }
100% { transform: translateY(340px); opacity: 0; }
}
02Ánh sao lấp lánh
24 chấm rải khắp màn hình, mỗi chấm nhận một độ trễ riêng qua nth-child rồi mờ dần giữa opacity 0.2 và 1 không đồng bộ với nhau. Hợp với hero landing chế độ tối hoặc trang sự kiện chủ đề ban đêm.
.p {
position: absolute; width: 5px; height: 5px; border-radius: 50%;
box-shadow: 0 0 10px rgba(23, 20, 26, .55);
animation: twinkle 1.8s ease-in-out infinite;
animation-delay: calc(var(--i) * -0.31s);
}
@keyframes twinkle {
0%, 100% { transform: scale(.7); opacity: .2; }
50% { transform: scale(1.3); opacity: 1; }
}
03Sóng SVG
Hai path sóng SVG chồng lên nhau lặp translateX theo hai hướng ngược nhau; vì đúng chỗ nối lại là cùng một hình nên dòng chảy không lộ đường ghép. Hợp với đường viền cuối section giới thiệu sản phẩm hoặc nền landing biển và du lịch.
.layer { position: absolute; left: 0; right: 0; bottom: 0; display: flex; width: 200%; }
.wave { width: 50%; flex: none; }
.layer--back { animation: drift 6s linear infinite; }
.layer--front { animation: drift 3.9s linear infinite; }
@keyframes drift {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
04Mưa rơi
22 đường kẻ mảnh rơi chéo nhanh bằng translate ở cùng một độ nghiêng cố định, mỗi đường lệch một chút vị trí và thời điểm rơi qua nth-child nên trông như một chùm mưa. Hợp với nền khuyến mãi mùa mưa hoặc màn hình giới thiệu app thời tiết.
.p {
position: absolute; top: -12%; width: 2px; height: 22px;
transform: rotate(12deg);
animation: rain .6s linear infinite;
animation-delay: calc(var(--i) * -0.07s);
}
@keyframes rain {
0% { transform: translateY(0) translateX(0) rotate(12deg); opacity: .7; }
100% { transform: translateY(380px) translateX(60px) rotate(12deg); opacity: .3; }
}
05Confetti rơi
26 mảnh hình chữ nhật vừa rơi vừa xoay để bay phấp phới, một nửa xoay theo chiều kim đồng hồ và nửa kia ngược lại, còn nth-child đổi màu xen kẽ mỗi 3 phần tử. Hợp với màn hình chúc mừng thanh toán hoặc đăng ký thành công, trang trúng thưởng sự kiện.
.p { position: absolute; top: -10%; width: 7px; height: 11px; }
.p:nth-child(3n) { background: #2f6df6; }
@keyframes confetti {
0% { transform: translateY(0) rotate(0deg); opacity: 0; }
100% { transform: translateY(360px) rotate(var(--spin, 360deg)); opacity: .85; }
}
06Bong bóng nổi lên
20 vòng tròn chỉ có viền nổi từ dưới lên trên qua translateY, vừa nổi vừa phồng to rồi nhỏ lại bằng scale trên đường đi. Hợp với hero thương hiệu đồ uống, rượu, hoặc nền giới thiệu công viên nước và spa.
.p {
position: absolute; bottom: -10%; width: 10px; height: 10px;
border: 1.5px solid currentColor; border-radius: 50%;
animation: rise 4s cubic-bezier(.2, .8, .2, 1) infinite;
}
@keyframes rise {
0% { transform: translateY(0) scale(.6); opacity: 0; }
55% { transform: translateY(-170px) scale(1.15); }
100% { transform: translateY(-360px) scale(.8); opacity: 0; }
}
07Đom đóm
16 chấm mờ vừa nhấp nháy qua opacity vừa đổi hướng liên tục trong quãng ngắn bằng translate, vẽ ra một quỹ đạo ánh sáng bất quy tắc. Hợp với nền thương hiệu thiên nhiên, cắm trại, hoặc hero portfolio thiên về cảm xúc.
.p {
position: absolute; width: 4px; height: 4px; border-radius: 50%;
box-shadow: 0 0 12px rgba(255, 247, 230, .8);
animation: drift 3.5s ease-in-out infinite;
}
@keyframes drift {
0% { transform: translate(0, 0); opacity: 0; }
35% { transform: translate(14px, -10px); opacity: .9; }
55% { transform: translate(-8px, -20px); opacity: .3; }
100% { transform: translate(0, 0); opacity: 0; }
}
08Cánh hoa anh đào bay
18 hình elip rơi vừa xoay vừa lắc nhẹ sang hai bên theo translateX, chuyển động như một cánh hoa đang bay trong gió. Hợp với nền khuyến mãi mùa xuân hoặc hero thương hiệu cưới và làm đẹp.
.p { position: absolute; top: -10%; width: 10px; height: 6px; border-radius: 50%; }
@keyframes petal {
0% { transform: translate(0, 0) rotate(0deg); opacity: 0; }
30% { transform: translate(18px, 110px) rotate(120deg); }
60% { transform: translate(-14px, 240px) rotate(240deg); }
100% { transform: translate(10px, 380px) rotate(360deg); opacity: .3; }
}
09Sương mù trôi
3 dải mờ cùng tốc độ translateX qua lại nhưng animation-delay lệch nhau nên các dải chồng lên nhau, opacity cũng dao động theo để tạo cảm giác lớp sương đang thở. Hợp với hero nội dung bí ẩn, kinh dị, hoặc nền thương hiệu núi và ngoài trời.
.p {
position: absolute; left: -20%; width: 140%; height: 34%;
filter: blur(6px);
animation: driftx 2s linear infinite alternate;
}
.p:nth-child(2) { animation-delay: -0.66s; }
.p:nth-child(3) { animation-delay: -1.32s; }
@keyframes driftx {
from { transform: translateX(-30%); opacity: .18; }
to { transform: translateX(30%); opacity: .42; }
}
Ba lỗi dễ vỡ nhất — sai ở đâu?
Lỗi đầu tiên nằm ở đơn vị phần trăm của transform: translate(). Phần trăm ở đây tính theo kích thước của chính phần tử đang animate chứ không phải theo khung stage hay phần tử cha. Năm mục rơi — tuyết, mưa, confetti, bong bóng, cánh hoa — lúc mới viết dùng translateY(370%) như một con số trực quan, nhưng span chỉ cao vài pixel nên toàn bộ chuyển động chỉ nhích 6 đến 22px, gần như đứng yên trên màn hình. Đổi sang giá trị px cố định, khớp với chiều cao 300px của khung, mới khiến các hạt thật sự băng ngang màn hình.
Lỗi thứ hai liên quan đến filter: blur(). Làm mờ càng mạnh thì vùng chuyển tiếp ở rìa càng rộng, và khi đó chênh lệch giữa hai khung hình liên tiếp có thể tụt xuống dưới ngưỡng 12 pixel dùng để đếm chuyển động. Mục 09 lúc đầu chỉ dùng blur(16px) cùng opacity, đo được 0 khung hình chuyển động dù diện tích thay đổi tích lũy lên tới 94% — blur quá mạnh khiến mỗi bước đổi khung hình vỡ vụn dưới ngưỡng đo, nên phải giảm blur và tăng biên độ dịch chuyển mới đo đúng.
Lỗi thứ ba là ghép sai độ dài animation với easing chậm. ease-in-out cộng với một duration dài hơn hẳn khoảng thời gian ghi hình khiến khung ghi chỉ rơi đúng vào đoạn mở đầu chậm rãi của đường cong, nên trông gần như đứng yên dù animation vẫn đang chạy phía sau. Mục 09 thử với 10 giây cho ra kết quả đo bằng 0, còn sau khi rút ngắn duration về đúng khớp hoặc là ước số của khoảng ghi hình thì chuyển động mới hiện rõ trở lại. Chín file đã sửa cả ba lỗi này được gói trong file zip, mở bằng mật khẩu fjv3dy5x.
Khả năng tiếp cận
Khi bạn bật "giảm chuyển động" trên hệ điều hành, prefers-reduced-motion: reduce áp cho toàn bộ chín mục nhưng không tắt sạch — mỗi mục giữ lại một trạng thái tĩnh còn mang ý nghĩa thị giác. Tuyết, mưa, confetti và cánh hoa (01, 04, 05, 08) dừng rơi và chỉ còn một chấm mờ đứng yên ở khoảng nửa trên khung hình. Sao và đom đóm (02, 07) dừng nhấp nháy nhưng vẫn giữ một opacity cố định, còn sóng SVG và sương (03, 09) dừng hẳn animation để hiện một hình tĩnh. Bong bóng (06) dừng bay lên và chỉ còn vài vòng tròn đứng yên gần đáy khung.
@media (prefers-reduced-motion: reduce) {
.p { animation: none; opacity: .35; }
}
Bảng hỗ trợ trình duyệt đầy đủ của media query này nằm trong tài liệu prefers-reduced-motion của MDN.
Nếu bạn tìm hiệu ứng CSS ngoài chủ đề nền và hạt, chuyên mục CSS tập hợp toàn bộ bài cùng dạng của site, còn trang giới thiệu giải thích vì sao mỗi demo ở đây đều đi kèm một file nén có mật khẩu.
FAQ
Có cần thêm JavaScript để chạy chín hiệu ứng này không?
Không cần. Cả chín mục chỉ dùng @keyframes CSS để tự phát và lặp vô hạn, còn animation-delay được tính sẵn qua nth-child hoặc custom property --i ngay lúc biên dịch SCSS. Không mục nào gắn :hover hay lắng nghe sự kiện JS nào cả.
Vì sao cánh hoa anh đào ở mục 08 lại có màu xanh dương?
Vì bảng màu demo của site này không có sắc hồng, nên mục 08 dùng $subject-blue thay cho màu cánh hoa thật. Mở file zip ra thì chỉ cần đổi biến $color ở đầu file .scss là ra đúng màu bạn muốn, không phải sửa lại từng dòng transform hay keyframes.
9 nền hạt này khác gì với bài css background animation trước đó?
Bài trước mỗi hiệu ứng chỉ có một phần tử nền tự đổi màu hay hình dạng, còn ở bài này mỗi hiệu ứng rải từ 3 đến 26 phần tử span riêng biệt qua nth-child và --i, mỗi phần tử tự mang vị trí, kích thước và tốc độ khác nhau. ID của từng mục cũng được đặt mới hoàn toàn nên không trùng với bài kia.