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

Biến CSS (hay còn gọi chính xác hơn là thuộc tính tuỳ chỉnh CSS) sẽ xuất hiện trong Chrome 49. Các thành phần này có thể giúp giảm tình trạng lặp lại trong CSS, cũng như mang lại các hiệu ứng mạnh mẽ khi bắt đầu chạy như chuyển đổi giao diện và khả năng mở rộng/polyfill 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 nên dành một bộ màu thương hiệu để dùng lại nhằm 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à việc nhàm chán mà còn dễ xảy ra lỗi. Nếu vào một thời điểm nào đó, nếu một trong các màu cần thay đổi, bạn có thể cảnh báo về điều đó cũng như "tìm và thay thế" tất cả mọi thứ, nhưng trên một dự án đủ lớn, điều này có thể dễ dàng trở nên nguy hiểm.

Trong thời gian gần đây, nhiều nhà phát triển đã chuyển sang sử dụng 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 nhà phát triển tăng năng suất làm việc đáng kể, nhưng các biến mà họ sử dụng có một hạn chế lớn, đó là chúng ở dạng tĩnh và không thể thay đổi trong thời gian chạy. Việc thêm khả năng thay đổi các 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 mang lại nhiều lợi ích lớn cho thiết kế thích ứng và tiềm năng thay thế các 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 cung cấp dưới dạng các thuộc tính tuỳ chỉnh CSS.

Thông tin tóm tắt về thuộc tính tuỳ chỉnh

Tài sản tuỳ chỉnh bổ sung hai tính năng mới vào bộ công cụ CSS của chúng tôi:

  • Tác giả có thể gán các 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à một ví dụ ngắn gọn để 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ị #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à thay thế chính nó bằng giá trị thuộc tính tuỳ chỉnh, dẫn đến color: #06c;. Chỉ cần thuộc tính tuỳ chỉnh được xác định ở vị trí nào đó trong biểu định kiểu của bạn, thuộc tính này sẽ có sẵn cho hàm var.

Ban đầu, cú pháp có thể hơi lạ. Nhiều nhà phát triển 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. Đối với cốt truyện, bạn có thể đọc bài đăng này của Tab Atkins, một trong những tác giả thông số kỹ thuật.

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

Cú pháp cho 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à các thuộc tính tuỳ chỉnh khác nhau. Mặc dù có vẻ đơn giản nhưng thực tế thì cú pháp được cho phép đối với các thuộc tính tuỳ chỉnh thực ra khá phù hợp. 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ù biến này không hữu ích trong vai trò một biến vì sẽ không hợp lệ trong mọi thuộc tính thông thường, nhưng bạn có thể đọc và thực thi 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ó thể mở ra mọi loại kỹ thuật thú vị mà các bộ tiền xử lý CSS hiện nay không có được. Vì vậy, nếu bạn đang nghĩ rằng "ngáp tôi có SASS, vậy ai quan tâm..." thì hãy xem lại! Đây không phải là các biến mà bạn thường dùng.

Thác nước

Các thuộc tính tuỳ chỉnh tuân theo quy tắc phân tầng chuẩn, vì vậy, bạn có thể xác định cùng một thuộc tính ở nhiều mức độ 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 nghe nhì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 thành phần phân mục chính khi kích thước màn hình tăng:

: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 cách sử dụng các bộ tiền xử lý CSS hiện nay, chúng không thể xác định các biến bên trong các truy vấn phương tiện. Khả năng này sẽ giúp bạn mở ra rất nhiều tiềm năng!

Bạn cũng có thể sở hữu 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. Việc 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 một 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 một 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 sử dụng khi thuộc tính tuỳ chỉnh được tham chiếu không hợp lệ. Giá trị dự phòng có thể là 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 bản 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, một 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);
}

Khi 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 vệ 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 khi thiết kế giao diện cho các Thành phần web sử dụng DOM bóng, vì các thuộc tính tuỳ chỉnh có thể đi 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ị "hook" của giao diện ở 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 phải chú ý một số điều. Biến không thể 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 ra dưới dạng lỗi.

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

.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 có một hàm khác: hàm calc().

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

Nếu trước đây bạn chưa từng làm việc với hàm này, thì hàm calc() là một công cụ nhỏ cho phép bạn thực hiện các phép tính để xác định các giá trị CSS. Thuộc tính này được hỗ trợ trên tất cả các trình duyệt hiện đại và có thể kết hợp với các 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 một 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ì 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 biểu định kiểu, nên bạn có thể hình dung ra cách đ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 để biết thông tin sơ lược về tất cả các kỹ thuật thú vị mà bạn hiệ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ề các thuộc tính tuỳ chỉnh, Philip Walton thuộc nhóm Google Analytics đã viết một bài viết sơ lược về lý do anh quan tâm đến thuộc tính tuỳ chỉnh. Ngoài ra, bạn có thể theo dõi tiến trình của các thuộc tính đó trong các trình duyệt khác trên chromestatus.com.