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 video, đây là phiên bản YouTube của bài đăng này:

Tổng quan

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

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

Thương hiệu

Thông thường, màu thương hiệu đã được thiết lập và được phân phối dưới dạng hệ lục phân hoặc rgb. Thử thách GUI này có màu thương hiệu cơ bản 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%);
}

Để có khái niệm làm tối hoặc làm sáng màu thương hiệu, khoảng 20%, 3 kênh của giá trị màu hsl cần được trích xuất 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ể tính toán trên các thuộc tính màu đó, chẳng hạn như 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ả các màu trong cùng một bộ 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 lược đồ phù hợp, trong trường hợp này, mỗi biến thể sẽ được thêm -light.

bản xem trước kết quả kết thúc của giao diện sáng

Thương hiệu

Bắt đầu từ màu thương hiệu, thuộc tính 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 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, các thành phần thiết yếu của 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 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ì trời rất tối với độ sáng 10%, nên vẫn giữ được độ bão hoà cao 100% để màu sắc thương hiệu vẫn có thể xuyên qua màu xanh hải quân đậm nhạt.

--text2-light, màu đầu tiên không quá tối như màu đầu tiên, rất đẹp vì đây là màu thứ cấp và cũng có độ bão hoà thấp hơn nhiều.

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

Màu sắc 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à các màu sáng, ngược lại với màu văn bản là màu tối. Để tạo màu sáng bằng hsl, chúng ta sẽ sử dụng giá trị phần trăm cao hơn trong giá trị độ sáng thứ ba. Chúng tôi cũng sẽ giảm độ rực màu để màu xám nhạt trông không bị phủ màu quá.

* {
  --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 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 ra giao diện của các lớp giấy. Trong các 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, thao tác di chuột sẽ làm tăng độ tương phản (độ sáng 99% xuống 92% độ sáng; làm cho độ sáng tối hơn).

Bóng

Các bóng trong bảng phối màu vượt trội hơn, nhưng thêm tính chất giống như thật vào hiệu ứng và giúp nó nổi bật so với các bóng có 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 về màu sắc, có màu hơi bão hoà với tông màu đó nhưng vẫn rất tối. Về cơ bản là tạo một bóng rất tối và hơi xanh.

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

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

Tất cả các màu sáng

Bạn không cần phải tìm kiếm khắp nơi để tìm cách tạo ra màu sáng bất kỳ, tất cả đề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 các màu sáng cùng nhau
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 các bối cảnh khác nhau, chẳng hạn như ban đêm. Các yếu tố này đã giúp tôi lưu ý đến 2 điều liên quan đến giao diện tối:

  1. Người dùng thường sẽ ở 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 phải giảm độ bão hoà sao cho không rung trên màn hình do quá mức.

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 là màu sáng. Các màu sau đây có giá trị cao về độ sáng, đưa chúng 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 của vùng hiển thị cũng 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 thứ nhất có độ 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, bạn có thể rất khó nhìn thấy bóng. Hợp lý vì khó làm tối một thứ gì đó đã khá tối. Đây là lúc --shadow-strength-dark cực kỳ thuận tiện 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 độ rực màu 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 công cụ cho nhà phát triển. Bạn muốn dùng công cụ nào hơn?!

Tất cả cá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 các màu tối
Hộp cát trên CodePen

Giao diện tối

Bảng phối màu này chú trọng đến việc điều chỉnh độ sáng và độ rực màu. Độ bão hoà vẫn đủ để vẫn hiển thị sắc độ, nhưng cũng chỉ nên gần như không vượt qua điểm tương phản vì dù sao thì màu đó cũng sẽ bị mờ và tương phản thấp.

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

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;
}

Giảm độ sáng tất cả cá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 cho hỗ trợ tiếp cận

Lưu ý rằng độ sáng thấp nhất trong bộ màu văn bản tối là 65% và độ sáng cao nhất trong các giao diện tối là 25%. Tức là 40% không gian thở vào giữa chúng. Trong giao diện sáng, có 55% không gian trong giao diện sáng. Việc giữ mức chênh lệch độ sáng giữa màu văn bản và màu bề mặt ở khoảng 40–50% có thể giúp giữ tỷ lệ tương phản màu ở mức cao, đồng thời cũng là một yếu tố tinh tế để điều chỉnh trong trường hợp điểm số kém.

Tôi gọi hành động này là " mong muốn chạm vào cho đến khi truyền", tức là tương tác với việc tăng giá trị độ sáng cho đến khi công cụ cho thấy tôi đang vượt qua.

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

Mỗi chủ đề được tạo trong thử thách này đều đạt điểm số tương phản. Bảng phối màu mờ có độ tương phản thấp nhất, nhưng vẫn đáp ứng được các yêu cầu tối thiểu. Để giúp những thành viên khác trong nhóm sử dụng màu có độ tương phản phù hợp, bạn nên tạo tên lớp (classname) kết hợp giữa màu của vùng hiển thị và màu văn bản có thể đọc được.

.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 giao diện làm mờ và ghép nối văn bản
Ảnh chụp màn hình giao diện làm mờ và ghép nối văn bản với VisBug

Bóng to

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 tại công cụ Bóng mượt mà tôi rất đánh giá cao. Tôi lấy đoạn mã đã tạo và tuỳ chỉnh bằng màu sắc cũng như các phép tính về độ 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 bên cạnh nhau

Để làm được điều này, tôi đã tạo 2 biến cho mỗi bảng phối màu cần điều chỉnh, một màu đổ bóng và một độ mạnh của bóng đổ. Màu sắc dùng để điều chỉnh độ rực màu và độ tối, còn độ mạnh dùng để dễ dàng tăng cường độ bóng khi đó là bảng phối màu tối. Kết quả cuối cùng giống như thế này.

: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 tìm hiểu kỹ hơn về bóng trong bảng phối màu, tôi sẽ tạo góc của bóng đổ thành một hằng số mã thông báo thiết kế, vì hướng ánh sáng giữa tất cả các bóng của thiết kế phải giống nhau.

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

Khi đã hoàn tất việc xác định trước màu, đã đến lúc biến các màu này thành các thuộc tính không phân biệt lược đồ. Ý tôi là, với tư cách là một tác giả CSS trong dự án bảng phối màu này, bạn hiếm khi 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 làm cho chủ đề dễ dàng hơn.

Để thực hiện điều 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, người dùng khi sử dụng các biến thiết kế sẽ không bao giờ phải lo về bảng phối màu nào đang được đặt, họ chỉ cần sử dụng màu của vùng hiển thị và màu văn bản. Thay vì color: var(--text1-light), hãy sử dụng color: var(--text1). Tất cả việc điều chỉnh và chuyển hướng màu sắc đều được thực hiện ở cấp độ cao hơn nhiều trong CSS.

Tìm hiểu thêm 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ả sử dụng var(--brand) đều 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 hiện đang sử dụng giao diện sáng. Đây là một khoảnh khắc thành công rất vui vẻ! Hãy xem thêm một vài khoảnh khắc đó khi chúng ta sử dụng các 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ể tuỳ ý sử dụng bảng phối màu tổng quát đượ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ờ bạn đã biết tôi làm việc đó như thế nào, bạn sẽ làm thế nào?! 🙂

Hãy đa dạng hoá phương pháp tiếp cận của chúng ta và tìm hiểu tất cả các cách xây dựng trên web. Tạo Codepen hoặc tổ chức bản minh hoạ của riêng bạn, đăng bài cho tôi trên Twitter và tôi sẽ thêm 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, màu trạng thái và chế độ tương phản cho no-preference, moreless: bản minh hoạ.