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 lớp này có thể hữu ích để giảm sự lặp lại trong CSS, cũng như cho các hiệu ứng thời gian chạy mạnh mẽ như chuyển đổi giao diện và có thể 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 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 tại một thời điểm nào đó, bạn cần thay đổi một trong các màu, bạn có thể bỏ qua sự thận trọng và "tìm và thay thế" tất cả các màu, nhưng trên một dự án đủ lớn, việc này có thể dễ dàng gây nguy hiểm.

Gần đây, nhiều nhà phát triển đã chuyển sang sử dụng trình xử lý trước 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 trình xử lý trước. Mặc dù các công cụ này đã giúp nhà phát triển tăng năng suất đáng kể, 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 những điều như giao diện ứng dụng động, mà còn có tác động lớn đến thiết kế thích ứng và tiềm năng polyfill các tính năng CSS trong tương lai. Với bản phát hành Chrome 49, các khả 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à một 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 hai 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 vào. Nhiều nhà phát triển thắc mắc: "Tại sao không chỉ sử dụng $foo cho tên biến?" Phương pháp này được chọn để linh hoạt nhất có thể và có thể cho phép sử dụng các macro $foo trong tương lai. Để biết thông tin cơ bản, bạn có thể đọc bài đăng này của một trong những tác giả của thông số kỹ thuật, Tab Atkins.

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 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 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ĩ "ngáp Tôi có SASS nên ai quan tâm…", hãy xem lại! Đâ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. Khả năng này sẽ giúp bạn khai phá 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 tạo 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() 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à một danh sách được phân tách bằng dấu phẩy, 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ả 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ị "móc" giao diện dưới 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ể (một cách đơn giản) tạo một giá trị mà một phần của giá trị đó do một 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 làm việc với hàm này, thì hàm calc() là một công cụ nhỏ tiện dụng cho phép bạn thực hiện các phép tính để xác định giá trị CSS. Phương thức này được hỗ trợ trên tất cả trình duyệt hiện đại và có thể được kết hợp với các thuộc tính tuỳ chỉnh để tạo giá trị mới. Ví dụ:

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

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

Để lấy 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ì 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 mọi 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 muốn tìm hiểu thêm về tài sản tuỳ chỉnh, Philip Walton thuộc nhóm Google Analytics đã viết một bài viết cơ bản về lý do anh hào hứng với tài sản tuỳ chỉnh. Bạn có thể theo dõi tiến trình của tài sản tuỳ chỉnh trong các trình duyệt khác trên chromestatus.com.