Đến nội dung chính
Lập trình

light-dark() giờ nhận cả hình ảnh, không chỉ màu

Hàm light-dark() nhận hai giá trị và trả về giá trị đầu khi phần tử đang ở chế độ sáng, giá trị sau khi ở chế độ tối. Lúc mới ra, nó chỉ nhận màu. Giờ nó nhận cả hình ảnh, nên việc đổi ảnh minh hoạ theo giao diện gọn đi rất nhiều.

Toàn bộ mẹo

:root {
  color-scheme: light dark;
}

.hero {
  background-image: url(day.svg);
  background-image: light-dark(url(day.svg), url(night.svg));
}

Có ba điểm đáng chú ý:

  • color-scheme: light dark là bắt buộc. light-dark() không tự đọc hệ điều hành. Nó dựa vào color-scheme đang áp dụng cho phần tử, và dòng này cho phép trang theo chế độ của hệ thống.
  • Dòng background-image đầu tiên là phương án dự phòng. Trình duyệt chưa hiểu light-dark() với ảnh sẽ bỏ qua dòng thứ hai và giữ ảnh ngày.
  • Không còn cặp @media (prefers-color-scheme: dark) lặp lại bộ chọn. Giá trị sáng và tối nằm cạnh nhau trên cùng một dòng.

Ví dụ 1: Ảnh nền ngày và đêm

Khung bên dưới dùng đúng hai dòng background-image ở trên, với hai ảnh SVG nhúng sẵn. Bấm nút để đổi color-scheme của riêng khung, hoặc chọn “Theo trang” để khung theo chế độ sáng/tối của máy bạn. Khi thử trên Chrome, transition: background-image còn cho một cú chuyển mờ giữa hai ảnh.

Ví dụ 1. Một khung, hai ảnh, light-dark() chọn theo color-scheme.

Phần CSS chính:

:root {
  color-scheme: light dark;
}

.scene {
  background-size: cover;
  background-position: center;
  background-image: url(day.svg);                            /* trình duyệt cũ dừng ở đây */
  background-image: light-dark(url(day.svg), url(night.svg)); /* trình duyệt mới chọn theo color-scheme */
  color: light-dark(#3b2416, #f3e6d3);
  transition: background-image 600ms ease, color 600ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .scene { transition: none; }
}

Nút bấm chỉ đặt element.style.colorScheme trên khung. Việc chọn ảnh vẫn là của light-dark().

Ví dụ 2: Không chỉ ảnh nền

light-dark() làm việc theo color-scheme của từng phần tử, nên có thể ép một khu vực luôn sáng hay luôn tối bằng color-scheme ngay trên phần tử đó. Hàm cũng dùng được ở những thuộc tính nhận ảnh khác: list-style-image cho dấu đầu dòng và content cho biểu tượng trước tiêu đề.

Ví dụ 2. Ba khung với ba color-scheme khác nhau.
.panel {
  background: light-dark(#fffaf3, #1d1b22);
  color: light-dark(#3b2416, #ece2d6);
  border: 1px solid light-dark(#ecdcc8, #34303d);
}

/* Ảnh minh hoạ: background-image */
.panel .art {
  background: center / cover no-repeat;
  background-image: light-dark(url(day.svg), url(night.svg));
}

/* Dấu đầu dòng: list-style-image */
.panel ul {
  list-style-image: light-dark(url(sun.svg), url(moon.svg));
}

/* Biểu tượng trước tiêu đề: content */
.panel h3::before {
  content: light-dark(url(sun.svg), url(moon.svg));
  margin-right: .4rem;
}

Ba khung dùng chung đoạn CSS trên nhưng cho ba kết quả khác nhau:

<div class="panel" style="color-scheme: light">…</div> <!-- luôn sáng: ảnh ngày, dấu mặt trời -->
<div class="panel" style="color-scheme: dark">…</div>  <!-- luôn tối: ảnh đêm, dấu mặt trăng -->
<div class="panel">…</div>                             <!-- theo trang, kế thừa từ :root -->

Ví dụ 3: Trình duyệt của bạn có hỗ trợ không?

Dùng CSS.supports() để kiểm tra trực tiếp. Khung bên dưới chạy phép kiểm tra ngay trong trình duyệt của bạn.

Ví dụ 3. Kết quả CSS.supports() ngay trong trình duyệt của bạn.

Muốn tự chạy, dán đoạn này vào Console:

const img = 'light-dark(url(a.svg), url(b.svg))';
for (const prop of ['background-image', 'list-style-image', 'mask-image', 'content']) {
  console.log(prop, CSS.supports(prop, img) ? 'hỗ trợ' : 'chưa hỗ trợ');
}
console.log('color (chỉ màu, có từ trước)', CSS.supports('color', 'light-dark(white, black)'));

Trong CSS, có thể làm tương tự với @supports (background-image: light-dark(url(a.svg), url(b.svg))).

Lưu ý khi áp dụng

  • Nếu bạn dùng hệ thống giao diện có thuộc tính kiểu data-theme, hãy chắc chắn nó cũng đặt color-scheme. Các demo ở trên dùng daisyUI, vốn đã đặt sẵn, nên chúng đổi theo nút sáng/tối mà không cần thêm dòng nào.
  • Cả hai ảnh nên có cùng kích thước và bố cục để lúc chuyển không bị giật.
  • Ảnh SVG có thể tự mang animation và tự tôn trọng prefers-reduced-motion ngay bên trong file, như mặt trời và các vì sao ở ví dụ 1.

Hỗ trợ trình duyệt. Việc light-dark() nhận hình ảnh là tính năng mới. Ví dụ 3 cho biết chính xác trình duyệt bạn đang dùng hỗ trợ tới đâu. Với những nơi chưa có, giữ khai báo dự phòng phía trước như trong đoạn mã ở trên.