Xây dựng bảng phối màu

Tổng quan cơ bản về cách thiết lập bảng phối màu động và có thể định cấu hình

Trong bài đăng này, tôi muốn chia sẻ suy nghĩ về cách quản lý nhiều bảng phối màu trong CSS. Dùng thử bản minh hoạ.

Bản minh hoạ

Nếu bạn thích xem video, hãy xem phiên bản video của bài đăng này trên YouTube:

Tổng quan

Chúng ta sẽ xây dựng một hệ thống màu sắc dễ tiếp cận bằng các thuộc tính tuỳ chỉnh và calc() để tạo một trang web thích ứng với lựa chọn ưu tiên của người dùng trong khi vẫn giữ cho trải nghiệm tạo nội dung ở mức tối thiểu. Chúng ta bắt đầu bằng một màu sắc cơ bản của thương hiệu và xây dựng một hệ thống biến thể từ màu sắc đó: 2 màu văn bản, 4 màu nền và một bóng phù hợp.

Hướng dẫn này bắt đầu bằng cách xác định tất cả màu sắc cho từng bảng phối màu. Chỉ đến cuối cùng, các giá trị này mới được dùng để thay đổi trang.

Thương hiệu

Thông thường, màu sắc thương hiệu đã được thiết lập và được phân phối dưới dạng thập lục phân hoặc rgb. Thử thách về GUI này có màu cơ bản của thương hiệu là #0af. Trước tiên, đối với hệ thống màu này, giá trị hex cần được chuyển đổi thành hsl.

* {
  --brand: #0af;
  --brand: hsl(200 100% 50%);
}

Để bật khái niệm làm tối hoặc làm sáng màu sắc của thương hiệu, chẳng hạn như 20%, bạn cần trích xuất 3 kênh của giá trị màu hsl vào các thuộc tính tuỳ chỉnh riêng, như sau:

* {
  --brand-hue: 200;
  --brand-saturation: 100%;
  --brand-lightness: 50%;
}

CSS có thể thực hiện phép tính trên các thuộc tính màu đó, ví dụ: calc(var(--brand-lightness) - 20%) để giảm 20% giá trị độ sáng. Đây là nền tảng để xây dựng bảng phối màu vì CSS có thể giữ tất cả màu trong cùng một nhóm màu bằng cách điều chỉnh độ bão hoà và độ sáng hsl.

Giao diện sáng

Mỗi biến thể màu sẽ được đánh dấu bằng giao diện phù hợp, trong trường hợp này, mỗi biến thể sẽ được thêm vào -light.

bản xem trước kết quả cuối cùng của giao diện sáng

Thương hiệu

Bắt đầu với màu thương hiệu, màu này được tạo lại bằng cách gói các thuộc tính tuỳ chỉnh --brand-hue, --brand-saturation--brand-lightness bên trong dấu ngoặc đơn của hàm hsl () mà không cần tính toán:

* {
  --brand-light: hsl(var(--brand-hue) var(--brand-saturation) var(--brand-lightness));
}

Màu văn bản

Tiếp theo, bảng phối màu cần có màu văn bản. Trong giao diện sáng, văn bản phải rất tối. Hãy lưu ý độ sáng của các màu sau đây rất thấp, dưới 50%.

* {
  --text1-light: hsl(var(--brand-hue) var(--brand-saturation) 10%);
  --text2-light: hsl(var(--brand-hue) 30% 30%);
}

--text1-light, vì màu này rất tối ở độ sáng 10%, nên giữ độ bão hoà cao 100% để màu sắc thương hiệu vẫn có thể lấp ló trong màu xanh dương đậm.

--text2-light, màu này không tối như màu thứ nhất, điều này rất tốt vì đây là màu phụ và cũng ít bão hoà hơn nhiều.

Màu cho vùng hiển thị

Màu sắc của bề mặt là nền, đường viền và các bề mặt trang trí khác mà văn bản nằm trên hoặc bên trong. Trong giao diện sáng, đây là màu sáng, trái ngược với màu văn bản tối. Để tạo màu sáng bằng hsl, chúng ta sẽ sử dụng các giá trị phần trăm cao hơn trong giá trị độ sáng thứ ba. Chúng ta cũng sẽ giảm độ bão hoà để màu xám nhạt không bị quá đậm.

* {
  --surface1-light: hsl(var(--brand-hue) 25% 90%);
  --surface2-light: hsl(var(--brand-hue) 20% 99%);
  --surface3-light: hsl(var(--brand-hue) 20% 92%);
  --surface4-light: hsl(var(--brand-hue) 20% 85%);
}

4 màu cho bề mặt được tạo vì màu trang trí có xu hướng cần nhiều biến thể hơn, cho các khoảnh khắc tương tác như :focus hoặc :hover hoặc để tạo giao diện của các lớp giấy. Trong những trường hợp này, bạn nên chuyển đổi --surface2-light khi di chuột sang --surface3-light, vì vậy, khi di chuột, độ tương phản sẽ tăng lên (độ sáng 99% thành độ sáng 92%; làm cho màu tối hơn).

Bóng

Bóng trong bảng phối màu là một yếu tố vượt trội, nhưng cũng mang lại hiệu ứng chân thực và giúp hiệu ứng này nổi bật so với bóng dựa trên màu đen không thực tế. Để làm điều này, màu của bóng sẽ sử dụng thuộc tính tuỳ chỉnh sắc độ, hơi bão hoà với sắc độ nhưng vẫn rất tối. Về cơ bản, hãy tạo một bóng rất tối, hơi xanh.

* {
  --surface-shadow-light: var(--brand-hue) 10% 20%;
  --shadow-strength-light: .02;
}

--surface-shadow-light không được gói trong một hàm hsl. Lý do là giá trị --shadow-strength sẽ được kết hợp để tạo độ mờ và CSS cần các phần này để thực hiện phép tính. Chuyển đến phần bóng đổ hình tròn để tìm hiểu thêm.

Tất cả màu sáng

Bạn không cần phải tìm kiếm để biết cách tạo màu sáng, tất cả màu sáng đều ở cùng một nơi trong CSS.

* {
  --brand-light: hsl(var(--brand-hue) var(--brand-saturation) var(--brand-lightness));
  --text1-light: hsl(var(--brand-hue) var(--brand-saturation) 10%);
  --text2-light: hsl(var(--brand-hue) 30% 30%);
  --surface1-light: hsl(var(--brand-hue) 25% 90%);
  --surface2-light: hsl(var(--brand-hue) 20% 99%);
  --surface3-light: hsl(var(--brand-hue) 20% 92%);
  --surface4-light: hsl(var(--brand-hue) 20% 85%);
  --surface-shadow-light: var(--brand-hue) 10% calc(var(--brand-lightness) / 5);
  --shadow-strength-light: .02;
}
ảnh chụp màn hình tất cả các màu sáng
Hộp cát trên CodePen

Giao diện tối

Hầu hết các thương hiệu không bắt đầu bằng giao diện tối, mà là một biến thể của giao diện chính, thường là sáng hơn. Mặt khác, người dùng thường chọn giao diện tối cho nhiều bối cảnh khác nhau, chẳng hạn như ban đêm. Những yếu tố này đã khiến tôi lưu ý đến 2 điều đối với giao diện tối:

  1. Người dùng thường ở trong bóng tối khi sử dụng giao diện này, vì vậy, hãy kiểm thử trong bóng tối.
  2. Màu sắc nên giảm độ bão hoà để không bị rung trên màn hình do quá đậm.

bản xem trước kết quả cuối cùng của giao diện tối

Thương hiệu

Giao diện sáng sử dụng 3 giá trị kênh màu hsl của thương hiệu mà không thay đổi, còn giao diện tối thì không. Độ bão hoà giảm một nửa và độ sáng giảm tương đối 50%.

* {
  --brand-dark: hsl(
    var(--brand-hue)
    calc(var(--brand-saturation) / 2)
    calc(var(--brand-lightness) / 1.5)
  );
}

Màu văn bản

Trong giao diện tối, màu văn bản phải sáng. Các màu sau đây có giá trị độ sáng cao, gần với màu trắng hơn.

* {
  --text1-dark: hsl(var(--brand-hue) 15% 85%);
  --text2-dark: hsl(var(--brand-hue) 5% 65%);
}

Màu cho vùng hiển thị

Trong giao diện tối, màu bề mặt phải là màu tối. Các màu sau đây có độ sáng và độ bão hoà thấp, trong đó bề mặt 1 là tối nhất ở mức 10%.

* {
  --surface1-dark: hsl(var(--brand-hue) 10% 10%);
  --surface2-dark: hsl(var(--brand-hue) 10% 15%);
  --surface3-dark: hsl(var(--brand-hue) 5%  20%);
  --surface4-dark: hsl(var(--brand-hue) 5% 25%);
}

Bóng

Trong giao diện tối, rất khó để nhìn thấy bóng đổ. Điều này cũng dễ hiểu vì khó làm tối một đối tượng đã khá tối. Đây là trường hợp --shadow-strength-dark rất hữu ích vì nó cho phép chúng ta làm tối bóng bằng cách thay đổi một biến.

* {
  --surface-shadow-dark: var(--brand-hue) 50% 3%;
  --shadow-strength-dark: .8;
}

Ngoài ra, hãy xem độ bão hoà trong bóng đó. Bạn có nhận thấy màu sắc khi nhìn vào giao diện không? Hãy thử xoá độ bão hoà khỏi devtools, bạn thích cái nào hơn?!

Tất cả màu tối

* {
  --brand-dark: hsl(var(--brand-hue) calc(var(--brand-saturation) / 2) calc(var(--brand-lightness) / 1.5));
  --text1-dark: hsl(var(--brand-hue) 15% 85%);
  --text2-dark: hsl(var(--brand-hue) 5% 65%);
  --surface1-dark: hsl(var(--brand-hue) 10% 10%);
  --surface2-dark: hsl(var(--brand-hue) 10% 15%);
  --surface3-dark: hsl(var(--brand-hue) 5%  20%);
  --surface4-dark: hsl(var(--brand-hue) 5% 25%);
  --surface-shadow-dark: var(--brand-hue) 50% 3%;
  --shadow-strength-dark: .8;
}
ảnh chụp màn hình tất cả các màu tối
Sandbox trên CodePen

Giao diện tối

Bảng phối màu này chủ yếu là về việc điều phối độ sáng và độ bão hoà. Màu sắc phải có đủ độ bão hoà để vẫn có thể nhìn thấy sắc độ, nhưng cũng chỉ vừa đủ để vượt qua điểm tương phản vì dù sao thì màu sắc này cũng được thiết kế để có độ tương phản thấp và mờ.

bản xem trước kết quả cuối cùng của giao diện mờ

Thương hiệu

* {
  --brand-dim: hsl(
    var(--brand-hue)
    calc(var(--brand-saturation) / 1.25)
    calc(var(--brand-lightness) / 1.25)
  );
}

Màu văn bản

* {
  --text1-dim: hsl(var(--brand-hue) 15% 75%);
  --text2-dim: hsl(var(--brand-hue) 10% 61%);
}

Màu cho vùng hiển thị

* {
  --surface1-dim: hsl(var(--brand-hue) 10% 20%);
  --surface2-dim: hsl(var(--brand-hue) 10% 25%);
  --surface3-dim: hsl(var(--brand-hue) 5%  30%);
  --surface4-dim: hsl(var(--brand-hue) 5% 35%);
}

Bóng

* {
  --surface-shadow-dim: var(--brand-hue) 30% 13%;
  --shadow-strength-dim: .2;
}

Làm mờ tất cả màu

* {
  --brand-dim: hsl(var(--brand-hue) calc(var(--brand-saturation) / 1.25) calc(var(--brand-lightness) / 1.25));
  --text1-dim: hsl(var(--brand-hue) 15% 75%);
  --text2-dim: hsl(var(--brand-hue) 10% 61%);
  --surface1-dim: hsl(var(--brand-hue) 10% 20%);
  --surface2-dim: hsl(var(--brand-hue) 10% 25%);
  --surface3-dim: hsl(var(--brand-hue) 5%  30%);
  --surface4-dim: hsl(var(--brand-hue) 5% 35%);
  --surface-shadow-dim: var(--brand-hue) 30% 13%;
  --shadow-strength-dim: .2;
}
ảnh chụp màn hình các màu tối
Hộp cát trên CodePen

Màu sắc hỗ trợ tiếp cận

Lưu ý độ sáng thấp nhất trong nhóm màu văn bản tối là 65% và độ sáng cao nhất trong các bề mặt tối là 25%. Đó là 40% độ sáng giữa các thành phần. Trong giao diện sáng, có 55% không gian thở trong giao diện sáng. Việc duy trì sự khác biệt về độ sáng giữa màu văn bản và màu nền ở mức khoảng 40-50% có thể giúp duy trì độ tương phản màu cao, đồng thời là một đòn bẩy tinh tế để điều chỉnh trong trường hợp điểm số thấp.

Tôi gọi đó là "bump bump til ya pass" (nhấn cho đến khi bạn vượt qua), đó là hoạt động tương tác của việc nhấn vào giá trị độ sáng cho đến khi một công cụ cho thấy tôi đang vượt qua.

nhấn shift + mũi tên xuống để giảm độ sáng và tăng độ tương phản cho đến khi vượt qua

Mỗi giao diện được tạo trong thử thách này đều đạt điểm tương phản. Bảng phối màu tối có độ tương phản thấp nhất trong số các bảng phối màu, nhưng vẫn đáp ứng các yêu cầu tối thiểu. Để giúp những người khác trong nhóm sử dụng màu tương phản tốt, bạn nên tạo một tên lớp ghép nối màu bề mặt với màu văn bản dễ tiếp cận.

.surface1 {
  background-color: var(--surface1);
  color: var(--text2);
}

.surface2 {
  background-color: var(--surface2);
  color: var(--text2);
}

.surface3 {
  background-color: var(--surface3);
  color: var(--text1);
}

.surface4 {
  background-color: var(--surface4);
  color: var(--text1);
}
Ảnh chụp màn hình ghép nối văn bản và bề mặt làm mờ
Ảnh chụp màn hình về bề mặt mờ và các cặp văn bản với VisBug

Rad Shadow

Các giao diện này sử dụng một lớp tiện ích có tên là .rad-shadow. Bóng này được tạo bằng công cụ Smooth Shadow (Bóng mượt) mà tôi rất cảm ơn. Tôi đã lấy đoạn mã được tạo và tuỳ chỉnh đoạn mã đó bằng màu sắc và tính toán độ mờ của riêng mình. Lý do là để tạo bóng mà tôi có thể điều chỉnh trong mỗi bảng phối màu.

mỗi bóng nằm cạnh nhau

Để thực hiện việc này, tôi đã tạo 2 biến cho mỗi bảng phối màu để điều chỉnh, một màu bóng và độ đậm của bóng. Màu sắc dùng để điều chỉnh độ bão hoà và độ tối, còn độ đậm là cách dễ dàng để tăng cường độ của bóng khi đây là bảng phối màu tối. Kết quả cuối cùng sẽ có dạng như sau.

:root {
  --surface-shadow-light: var(--brand-hue) 10% 20%;
  --shadow-strength-light: .02;
}

.rad-shadow {
  box-shadow:
    0 2.8px 2.2px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .03)),
    0 6.7px 5.3px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .01)),
    0 12.5px 10px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .02)),
    0 22.3px 17.9px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .02)),
    0 41.8px 33.4px hsl(var(--surface-shadow) / calc(var(--shadow-strength) + .03)),
    0 100px 80px hsl(var(--surface-shadow) / var(--shadow-strength))
  ;
}

Nếu muốn đi xa hơn với bóng trong bảng phối màu, tôi cũng sẽ đặt các góc bóng thành một mã thông báo thiết kế không đổi, vì hướng ánh sáng phải giống nhau giữa tất cả các bóng của thiết kế.

Sử dụng bảng phối màu

Sau khi việc xác định trước màu sắc hoàn tất, đã đến lúc chuyển các màu đó thành các thuộc tính không thể biết lược đồ. Ý tôi là, với tư cách là tác giả CSS trong dự án bảng phối màu này, hiếm khi bạn cần truy cập vào giá trị của một bảng phối màu cụ thể. Tôi muốn giúp bạn dễ dàng tuân thủ chủ đề.

Để thực hiện việc này, bạn chỉ nên sử dụng bảng phối màu thông qua các thuộc tính tuỳ chỉnh chung mà chúng ta sẽ xác định trong giây lát. Bằng cách này, những người sử dụng biến thiết kế không bao giờ phải lo lắng về bảng phối màu hiện đang được đặt, họ chỉ cần sử dụng màu nền và màu văn bản. Hãy sử dụng color: var(--text1) thay vì color: var(--text1-light). Tất cả các hoạt động điều chỉnh và xoay màu đều được thực hiện ở cấp cao hơn nhiều trong CSS.

Hãy tìm hiểu sâu hơn về các kiểu kết nối của giao diện sáng trong khối mã sau đây, kết nối một thuộc tính tuỳ chỉnh chung với màu cụ thể của giao diện sáng. Giờ đây, tất cả các trường hợp sử dụng var(--brand) sẽ sử dụng màu thương hiệu sáng.

Giao diện sáng (tự động)

:root {
  color-scheme: light;
  --brand: var(--brand-light);
  --text1: var(--text1-light);
  --text2: var(--text2-light);
  --surface1: var(--surface1-light);
  --surface2: var(--surface2-light);
  --surface3: var(--surface3-light);
  --surface4: var(--surface4-light);
  --surface-shadow: var(--surface-shadow-light);
  --shadow-strength: var(--shadow-strength-light);
}

Trang web này đang dùng giao diện sáng. Đây là một khoảnh khắc thành công rất thú vị! Hãy cùng xem thêm một vài ví dụ khác về việc sử dụng màu được xác định trước trong các ngữ cảnh bảng phối màu khác.

Giao diện tối (tự động)

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;

    --brand: var(--brand-dark);
    --text1: var(--text1-dark);
    --text2: var(--text2-dark);
    --surface1: var(--surface1-dark);
    --surface2: var(--surface2-dark);
    --surface3: var(--surface3-dark);
    --surface4: var(--surface4-dark);
    --surface-shadow: var(--surface-shadow-dark);
    --shadow-strength: var(--shadow-strength-dark);
  }
}

Giao diện sáng

[color-scheme="light"] {
  color-scheme: light;

  --brand: var(--brand-light);
  --text1: var(--text1-light);
  --text2: var(--text2-light);
  --surface1: var(--surface1-light);
  --surface2: var(--surface2-light);
  --surface3: var(--surface3-light);
  --surface4: var(--surface4-light);
  --surface-shadow: var(--surface-shadow-light);
  --shadow-strength: var(--shadow-strength-light);
}

Giao diện tối

[color-scheme="dark"] {
  color-scheme: dark;

  --brand: var(--brand-dark);
  --text1: var(--text1-dark);
  --text2: var(--text2-dark);
  --surface1: var(--surface1-dark);
  --surface2: var(--surface2-dark);
  --surface3: var(--surface3-dark);
  --surface4: var(--surface4-dark);
  --surface-shadow: var(--surface-shadow-dark);
  --shadow-strength: var(--shadow-strength-dark);
}

Giao diện tối

[color-scheme="dim"] {
  color-scheme: dark;

  --brand: var(--brand-dim);
  --text1: var(--text1-dim);
  --text2: var(--text2-dim);
  --surface1: var(--surface1-dim);
  --surface2: var(--surface2-dim);
  --surface3: var(--surface3-dim);
  --surface4: var(--surface4-dim);
  --surface-shadow: var(--surface-shadow-dim);
  --shadow-strength: var(--shadow-strength-dim);
}

Tại thời điểm này, tác giả có thể tự do sử dụng các giao diện màu chung được cung cấp nếu cần và không bao giờ phải lo lắng về giao diện nữa.

Kết luận

Giờ thì bạn đã biết cách tôi làm, còn bạn thì sao?! 🙂

Hãy đa dạng hoá phương pháp tiếp cận và tìm hiểu tất cả các cách xây dựng ứng dụng trên web. Tạo một Codepen hoặc lưu trữ bản minh hoạ của riêng bạn, tweet cho tôi về bản minh hoạ đó và tôi sẽ thêm bản minh hoạ đó vào phần Bản phối lại của cộng đồng ở bên dưới.

Nguồn

Bản phối lại của cộng đồng – @chris-kruining đã thêm thanh trượt màu sắc, các màu trạng thái và chế độ tương phản cho no-preference, moreless: bản minh hoạ.