Biến CSS - Tại sao bạn nên quan tâm?

Biến CSS, chính xác hơn là thuộc tính tuỳ chỉnh CSS, sẽ ra mắt trong Chrome 49. Các API này có thể hữu ích cho việc giảm tình trạng lặp lại trong CSS, cũng như giúp tạo ra các hiệu ứng mạnh mẽ trong thời gian chạy như chuyển đổi giao diện và có thể mở rộng/chèn các tính năng CSS trong tương lai.

CSS lộn xộn

Khi thiết kế ứng dụng, bạn thường phải đặt ra một bộ màu sắc thương hiệu sẽ được sử dụng lại để giữ cho giao diện của ứng dụng nhất quán. Rất tiếc, việc lặp lại các giá trị màu này nhiều lần trong CSS không chỉ là một công việc tẻ nhạt mà còn dễ xảy ra lỗi. Nếu vào một lúc nào đó, nếu cần thay đổi một trong các màu, bạn có thể cẩn trọng với gió và "tìm và thay thế" tất cả mọi thứ, nhưng trong một dự án đủ lớn, điều này có thể dễ dàng trở nên nguy hiểm.

Gần đây, nhiều nhà phát triển đã chuyển đến các bộ tiền xử lý CSS như SASS hoặc LESS để giải quyết vấn đề này thông qua việc sử dụng các biến bộ tiền xử lý. Mặc dù các công cụ này đã giúp tăng đáng kể năng suất của nhà phát triển, nhưng các biến mà họ sử dụng lại có một hạn chế lớn, đó là các biến này mang tính tĩnh và không thể thay đổi trong thời gian chạy. Việc thêm khả năng thay đổi biến trong thời gian chạy không chỉ mở ra cơ hội cho những việc như tuỳ chỉnh giao diện ứng dụng động, mà còn tạo ra hệ quả lớn cho thiết kế thích ứng và khả năng chèn nhiều tính năng CSS trong tương lai. Với bản phát hành Chrome 49, các tính năng này hiện có sẵn dưới dạng thuộc tính tuỳ chỉnh CSS.

Tóm tắt về thuộc tính tuỳ chỉnh

Thuộc tính tuỳ chỉnh thêm hai tính năng mới vào hộp công cụ CSS:

  • Khả năng tác giả chỉ định giá trị tuỳ ý cho một thuộc tính có tên do tác giả chọn.
  • Hàm var() cho phép tác giả sử dụng các giá trị này trong các thuộc tính khác.

Sau đây là ví dụ nhanh để minh hoạ

:root {
    --main-color: #06c;
}

#foo h1 {
    color: var(--main-color);
}

--main-color là thuộc tính tuỳ chỉnh do tác giả xác định có giá trị là #06c. Xin lưu ý rằng tất cả các thuộc tính tuỳ chỉnh đều bắt đầu bằng 2 dấu gạch ngang.

Hàm var() truy xuất và tự thay thế bằng giá trị thuộc tính tuỳ chỉnh, dẫn đến color: #06c;. Miễn là thuộc tính tuỳ chỉnh được xác định ở đâu đó trong tệp định kiểu, thì thuộc tính đó phải có sẵn cho hàm var.

Cú pháp có thể trông hơi lạ khi mới nhìn. Nhiều nhà phát triển đặt câu hỏi: "Tại sao không chỉ sử dụng $foo cho tên biến?" Phương pháp này được chọn đặc biệt để linh hoạt nhất có thể và có thể cho phép các macro $foo trong tương lai. Để tìm hiểu cốt truyện, bạn có thể đọc bài đăng này của một trong những tác giả thông số kỹ thuật, Tab Atkins.

Cú pháp thuộc tính tuỳ chỉnh

Cú pháp cho một thuộc tính tuỳ chỉnh rất đơn giản.

--header-color: #06c;

Xin lưu ý rằng các thuộc tính tuỳ chỉnh có phân biệt chữ hoa chữ thường, vì vậy, --header-color--Header-Color là hai thuộc tính tuỳ chỉnh khác nhau. Mặc dù có vẻ đơn giản, nhưng cú pháp được phép cho các thuộc tính tuỳ chỉnh thực sự khá cởi mở. Ví dụ: sau đây là một thuộc tính tuỳ chỉnh hợp lệ:

--foo: if(x > 5) this.width = 10;

Mặc dù không hữu ích dưới dạng biến vì sẽ không hợp lệ trong bất kỳ thuộc tính thông thường nào, nhưng bạn có thể đọc và xử lý thuộc tính này bằng JavaScript trong thời gian chạy. Điều này có nghĩa là các thuộc tính tuỳ chỉnh có khả năng mở ra mọi loại kỹ thuật thú vị hiện không thể thực hiện được với các trình xử lý trước CSS hiện nay. Vì vậy, nếu bạn đang nghĩ "yawn ta có SASS, vậy ai quan tâm..." thì hãy xem lại nhé! Đây không phải là những biến mà bạn thường sử dụng.

Loạt ảnh

Các thuộc tính tuỳ chỉnh tuân theo các quy tắc xếp chồng tiêu chuẩn, vì vậy, bạn có thể xác định cùng một thuộc tính ở nhiều cấp độ cụ thể

:root { --color: blue; }
div { --color: green; }
#alert { --color: red; }
* { color: var(--color); }
<p>I inherited blue from the root element!</p>
<div>I got green set directly on me!</div>
<div id="alert">
    While I got red set directly on me!
    <p>I’m red too, because of inheritance!</p>
</div>

Điều này có nghĩa là bạn có thể tận dụng các thuộc tính tuỳ chỉnh bên trong truy vấn nội dung đa phương tiện để hỗ trợ thiết kế thích ứng. Một trường hợp sử dụng có thể là mở rộng lề xung quanh các phần tử phân đoạn chính khi kích thước màn hình tăng lên:

:root {
    --gutter: 4px;
}

section {
    margin: var(--gutter);
}

@media (min-width: 600px) {
    :root {
    --gutter: 16px;
    }
}

Điều quan trọng cần lưu ý là bạn không thể sử dụng đoạn mã ở trên bằng trình xử lý trước CSS hiện nay vì trình xử lý này không thể xác định các biến bên trong truy vấn nội dung nghe nhìn. Việc có khả năng này sẽ mở ra rất nhiều tiềm năng!

Bạn cũng có thể có các thuộc tính tuỳ chỉnh lấy giá trị từ các thuộc tính tuỳ chỉnh khác. Điều này có thể cực kỳ hữu ích cho việc tuỳ chỉnh giao diện:

:root {
    --primary-color: red;
    --logo-text: var(--primary-color);
}

Hàm var()

Để truy xuất và sử dụng giá trị của thuộc tính tuỳ chỉnh, bạn cần sử dụng hàm var(). Cú pháp cho hàm var() sẽ có dạng như sau:

var(<custom-property-name> [, <declaration-value> ]? )

Trong đó <custom-property-name> là tên của thuộc tính tuỳ chỉnh do tác giả xác định, chẳng hạn như --foo<declaration-value> là giá trị dự phòng được dùng khi thuộc tính tuỳ chỉnh được tham chiếu không hợp lệ. Các giá trị dự phòng có thể là một danh sách được phân tách bằng dấu phẩy và sẽ được kết hợp thành một giá trị duy nhất. Ví dụ: var(--font-stack, "Roboto", "Helvetica"); xác định một phương thức dự phòng của "Roboto", "Helvetica". Xin lưu ý rằng các giá trị viết tắt, chẳng hạn như các giá trị dùng cho lề và khoảng đệm, không được phân tách bằng dấu phẩy. Vì vậy, phương án dự phòng thích hợp cho khoảng đệm sẽ có dạng như sau.

p {
    padding: var(--pad, 10px 15px 20px);
}

Bằng cách sử dụng các giá trị dự phòng này, tác giả thành phần có thể viết các kiểu phòng thủ cho phần tử của họ:

/* In the component’s style: */
.component .header {
    color: var(--header-color, blue);
}
.component .text {
    color: var(--text-color, black);
}

/* In the larger application’s style: */
.component {
    --text-color: #080;
    /* header-color isn’t set,
        and so remains blue,
        the fallback value */
}

Kỹ thuật này đặc biệt hữu ích cho việc tạo giao diện cho các Thành phần web sử dụng Shadow DOM, vì các thuộc tính tuỳ chỉnh có thể vượt qua ranh giới bóng. Tác giả của Thành phần web có thể tạo thiết kế ban đầu bằng cách sử dụng các giá trị dự phòng và hiển thị giao diện "hook" ở dạng thuộc tính tuỳ chỉnh.

<!-- In the web component's definition: -->
<x-foo>
    #shadow
    <style>
        p {
        background-color: var(--text-background, blue);
        }
    </style>
    <p>
        This text has a yellow background because the document styled me! Otherwise it
        would be blue.
    </p>
</x-foo>
/* In the larger application's style: */
x-foo {
    --text-background: yellow;
}

Khi sử dụng var(), bạn cần lưu ý một số điểm sau. Biến không được là tên thuộc tính. Ví dụ:

.foo {
    --side: margin-top;
    var(--side): 20px;
}

Tuy nhiên, điều này không tương đương với việc thiết lập margin-top: 20px;. Thay vào đó, nội dung khai báo thứ hai không hợp lệ và bị loại bỏ dưới dạng lỗi.

Tương tự, bạn không thể (tự nhiên) tạo một giá trị mà một phần của giá trị đó được biến cung cấp:

.foo {
    --gap: 20;
    margin-top: var(--gap)px;
}

Xin nhắc lại rằng điều này không tương đương với việc thiết lập margin-top: 20px;. Để tạo một giá trị, bạn cần một hàm khác: hàm calc().

Tạo giá trị bằng hàm calc()

Nếu bạn chưa từng sử dụng hàm này trước đây, thì hàm calc() là một công cụ nhỏ gọn cho phép bạn thực hiện các phép tính để xác định giá trị CSS. Thuộc tính này được hỗ trợ trên tất cả trình duyệt hiện đại và có thể kết hợp với thuộc tính tuỳ chỉnh để tạo ra các giá trị mới. Ví dụ:

.foo {
    --gap: 20;
    margin-top: calc(var(--gap) * 1px); /* niiiiice */
}

Làm việc với thuộc tính tuỳ chỉnh trong JavaScript

Để nhận giá trị của thuộc tính tuỳ chỉnh trong thời gian chạy, hãy sử dụng phương thức getPropertyValue() của đối tượng CSSStyleDeclaration đã tính toán.

/* CSS */
:root {
    --primary-color: red;
}

p {
    color: var(--primary-color);
}
<!-- HTML -->
<p>I’m a red paragraph!</p>
/* JS */
var styles = getComputedStyle(document.documentElement);
var value = String(styles.getPropertyValue('--primary-color')).trim();
// value = 'red'

Tương tự, để đặt giá trị của thuộc tính tuỳ chỉnh trong thời gian chạy, hãy sử dụng phương thức setProperty() của đối tượng CSSStyleDeclaration.

/* CSS */
:root {
    --primary-color: red;
}

p {
    color: var(--primary-color);
}
<!-- HTML -->
<p>Now I’m a green paragraph!</p>
/* JS */
document.documentElement.style.setProperty('--primary-color', 'green');

Bạn cũng có thể đặt giá trị của thuộc tính tuỳ chỉnh để tham chiếu đến một thuộc tính tuỳ chỉnh khác trong thời gian chạy bằng cách sử dụng hàm var() trong lệnh gọi đến setProperty().

/* CSS */
:root {
    --primary-color: red;
    --secondary-color: blue;
}
<!-- HTML -->
<p>Sweet! I’m a blue paragraph!</p>
/* JS */
document.documentElement.style.setProperty('--primary-color', 'var(--secondary-color)');

Vì các thuộc tính tuỳ chỉnh có thể tham chiếu đến các thuộc tính tuỳ chỉnh khác trong bảng kiểu, nên bạn có thể tưởng tượng điều này có thể dẫn đến tất cả các loại hiệu ứng thời gian chạy thú vị.

Hỗ trợ trình duyệt

Hiện tại, Chrome 49, Firefox 42, Safari 9.1 và iOS Safari 9.3 hỗ trợ các thuộc tính tuỳ chỉnh.

Bản minh hoạ

Hãy dùng thử mẫu này để xem nhanh tất cả các kỹ thuật thú vị mà giờ đây bạn có thể tận dụng nhờ các thuộc tính tuỳ chỉnh.

Tài liệu đọc thêm

Nếu bạn muốn tìm hiểu thêm về thuộc tính tuỳ chỉnh, thì Philip Walton từ nhóm Google Analytics đã viết sơ lược về lý do khiến ông quan tâm đến thuộc tính tuỳ chỉnh và bạn có thể theo dõi tiến trình của chúng trong các trình duyệt khác trên chromestatus.com.