Một cái bóng tốt là khác biệt giữa tấm thẻ trông như lơ lửng và tấm thẻ trông như bị dán lên. Phần lớn bóng trông sai mắc một trong ba lỗi: quá đậm, quá đều, hoặc bị phần tử cha cắt mất. Bài này giải thích từng phần của giá trị box-shadow bằng số liệu, cho thấy vì sao chồng nhiều lớp nhạt trông tự nhiên hơn, và nói về hiệu năng, điều trở nên quan trọng ngay khi cái bóng chuyển động.
Mọi đoạn CSS dưới đây đều lấy từ các mẫu có sẵn của công cụ tạo box shadow, và các con số về độ lan được tính từ chúng.
Năm giá trị
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);Theo thứ tự: độ lệch x (0, dương là sang phải), độ lệch y (10px, dương là xuống dưới), bán kính blur (30px), spread tuỳ chọn (ở đây không có, tức 0) và màu. Từ khoá inset tuỳ chọn sẽ vẽ bóng vào bên trong hộp.
Spread làm hình dạng của bóng to ra hoặc nhỏ lại trước khi làm mờ: 4px khiến bóng lớn hơn 4 px ở mọi phía, -5px khiến nó nhỏ hơn 5 px. Sau đó blur làm mềm mép. Đặc tả CSS mô tả nó là phép làm mờ Gauss với độ lệch chuẩn bằng nửa bán kính blur, nên mép mềm trải từ khoảng một bán kính blur bên trong hình tới một bán kính blur bên ngoài.
Bóng lan xa bao nhiêu
Từ đó có quy tắc đơn giản cho độ lan nhìn thấy được ở mỗi phía: blur + spread, cộng độ lệch ở phía bóng dịch về và trừ đi ở phía kia. Với cái bóng ở trên:
| Phía | Cách tính | Độ lan |
|---|---|---|
| Trên | 30 + 0 − 10 | 20 px |
| Dưới | 30 + 0 + 10 | 40 px |
| Trái và phải | 30 + 0 | 30 px |
Vì thế tấm thẻ có bóng này cần khoảng 40 px trống bên dưới trước phần tử tiếp theo, và bị cắt khi nằm trong khung có overflow: hidden mà ít padding. Bóng không bao giờ chiếm chỗ trong bố cục, nên không có gì tự dịch ra nhường chỗ cho nó.
Spread âm là cách để bóng không lộ ra hai bên. Mẫu "lifted",
box-shadow:
0 20px 25px -5px rgba(0, 0, 0, 0.1),
0 8px 10px -6px rgba(0, 0, 0, 0.1);lan 0 px phía trên thẻ (25 − 5 − 20), 20 px mỗi bên và 40 px phía dưới. Ánh sáng như chiếu từ trên xuống, và tấm thẻ trông như được nâng lên khỏi trang.
Vì sao nên chồng nhiều lớp
Bóng của vật thật thì đậm và sắc khi ở gần, nhạt và mềm khi ở xa. Một box-shadow duy nhất chỉ làm được một trong hai. Nhiều lớp, mỗi lớp có độ lệch và blur lớn hơn, mô phỏng được cả hai:
box-shadow:
0 1px 1px rgba(0, 0, 0, 0.08),
0 2px 2px rgba(0, 0, 0, 0.08),
0 4px 4px rgba(0, 0, 0, 0.08),
0 8px 8px rgba(0, 0, 0, 0.08),
0 16px 16px rgba(0, 0, 0, 0.08);Mỗi lớp chỉ đục 8%, nhưng ở chỗ cả năm lớp chồng lên nhau, ngay dưới mép thẻ, chúng cộng lại thành 1 − 0,92⁵ ≈ 34%. Ra xa hơn chỉ còn các lớp lớn, nên bóng nhạt dần mượt mà trong 32 px bên dưới và 16 px hai bên, phía trên thì không có gì. Kết quả có đường tiếp xúc sắc nét và phần nhạt dần dài, mềm, điều mà một lớp 34% không làm được: nó sẽ đậm đều và trông nặng nề.
Các lớp được vẽ theo thứ tự, lớp đầu tiên nằm trên cùng. Hãy đặt lớp nhỏ, sắc lên đầu.
Những cái bóng không phải là bóng
box-shadow còn là cách rẻ để vẽ những thứ chẳng liên quan gì tới ánh sáng:
- Bóng lệch cứng,
6px 6px 0 rgb(31, 41, 55), không có blur, tạo vẻ phẳng, đồ hoạ kiểu "neo-brutalism". - Vòng viền,
0 0 0 4px rgba(59, 130, 246, 0.35), là spread không blur và không lệch: một đường viền không làm thay đổi bố cục, khác vớiborder. Ghép thêm một lớp blur 24 px cùng màu là thành hiệu ứng phát sáng, hay dùng cho trạng thái focus. Nếu dùng cho focus, hãy chắc chắn nó đủ tương phản với nền, hoặc thêmoutlinethật. - Bóng inset,
inset 0 2px 6px rgba(0, 0, 0, 0.25), khiến ô nhập trông như bị ấn lõm xuống.
box-shadow hay drop-shadow
box-shadow bám theo hộp của phần tử, kể cả góc bo tròn. Ảnh PNG nền trong suốt hay biểu tượng SVG vẫn nhận bóng hình chữ nhật. filter: drop-shadow(...) thì bám theo các điểm ảnh nhìn thấy được, nên logo có bóng đúng hình logo.
drop-shadow không có spread và không có inset. Công cụ chuyển đổi khi có thể: các mẫu soft, layered và hard có dạng drop-shadow tương đương (mẫu layered thành năm hàm drop-shadow() nối nhau), còn mẫu lifted và glow dùng spread, mẫu inset là bóng trong, nên công cụ báo không có dạng tương đương thay vì tạo ra một thứ khác đi.
Hiệu năng: cái gì rẻ, cái gì không
Bóng được vẽ ở bước paint. Bóng tĩnh trên vài tấm thẻ gần như không tốn gì. Chi phí xuất hiện trong ba trường hợp:
- Tạo hiệu ứng động cho chính cái bóng. Chuyển
box-shadowtừ nhỏ sang lớn khi rê chuột nghĩa là vẽ lại toàn bộ vùng bóng ở mỗi khung hình. Với bán kính blur lớn và nhiều phần tử, điện thoại cấu hình vừa phải có thể bị giật. - Blur rất lớn trên phần tử lớn. Khối lượng tính toán blur tăng theo diện tích được phủ.
- Bóng trên hàng trăm phần tử trong một danh sách cuộn.
Cách sửa thường dùng cho hiệu ứng rê chuột là vẽ cái bóng lớn một lần trên một pseudo-element, rồi chỉ thay đổi opacity của nó, thứ mà trình duyệt thường xử lý được mà không cần vẽ lại:
.card { position: relative; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); }
.card::after { content: ""; position: absolute; inset: 0; border-radius: inherit;
box-shadow: 0 16px 32px rgba(0, 0, 0, 0.12); opacity: 0; transition: opacity 0.2s; }
.card:hover::after { opacity: 1; }Những lỗi thường gặp
- Màu đen tuyền với độ đục cao. Một lớp đơn đục quá khoảng 25% thường trông nặng và bẩn. Hãy bắt đầu ở 0,08 đến 0,12 mỗi lớp và để các lớp cộng dồn.
- Bóng trên nền tối. Bóng đen trên trang gần như đen thì không thấy được; hãy dùng màu bề mặt sáng hơn hoặc một đường viền nhẹ.
- Bị cắt. Phần tử cha có
overflow: hiddencắt bóng ở mép của nó. Hãy thêm padding ít nhất bằng độ lan. - Hướng sáng không thống nhất. Thẻ này bóng đổ xuống, thẻ kia bóng đổ sang phải, trang trông sai mà không ai biết vì sao. Giữ độ lệch x bằng 0 hoặc thống nhất.
- Dùng bóng làm dấu hiệu duy nhất cho một trạng thái. Vòng viền là chỉ báo focus duy nhất thì phải đạt yêu cầu tương phản, và hiệu ứng nâng lên khi rê chuột không nên tự mang ý nghĩa.
Hãy thử các mẫu trong công cụ tạo box shadow, dán một giá trị box-shadow có sẵn để chỉnh sửa, và ghép với nền từ công cụ tạo gradient CSS. Với các hiệu ứng kính mờ và giao diện nổi dựa trên bóng, xem bài glassmorphism, neumorphism và khả năng tiếp cận.