Box-shadow nhiều lớp trong CSS: blur, spread và hiệu năng

Từng giá trị của box-shadow làm gì, bóng thực sự lan xa bao nhiêu, vì sao năm lớp nhạt đẹp hơn một lớp đậm, và cách làm bóng chuyển động mà không tốn.

Trong bài này

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íaCách tínhĐộ lan
Trên30 + 0 − 1020 px
Dưới30 + 0 + 1040 px
Trái và phải30 + 030 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ới border. 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êm outline thậ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:

  1. Tạo hiệu ứng động cho chính cái bóng. Chuyển box-shadow từ 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.
  2. 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ủ.
  3. 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

  1. 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.
  2. 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ẹ.
  3. Bị cắt. Phần tử cha có overflow: hidden cắt bóng ở mép của nó. Hãy thêm padding ít nhất bằng độ lan.
  4. 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.
  5. 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.

Công cụ được nhắc trong bài

Về trang blog