The CSS4 Spec
Đối với những người chưa bắt đầu (tôi cách đây một tuần), các biến CSS mới sẽ trông như thế này:
.class {
--color-background: #FFBB00;
background-color: var(--color-background);
}
The variable --color-background
is bound to the scope of the .class
selector. Global variables can be defined outside of an element in a :root
pseudo-class:
/* global variables in the :root class */
:root {
--color-background: #FFBB00;
}
/* calling our global variable outside the :root class */
.class {
background-color: var(--color-background);
}
Bạn có thể đọc thêm về các biến trong W3C Spec hoặc trên Mozilla Developer Network.
Sass
Bất kể bạn có thích cú pháp biến mới hay không, rất có thể bạn đã sử dụng bộ xử lý trước để quản lý các biến của mình và muốn tiếp tục làm như vậy. Một số cá nhân cực kỳ có tổ chức thậm chí có thể sử dụng các bản đồ biến đổi trong Sass.
// a variable map in Sass
$colors: (
primary: #FFBB00,
secondary: #0969A2
);
// using `map-get` in Sass
body {
color: map-get($colors, primary);
}
body {
color: #FFBB00;
}
Working together
Chỉ vì bạn có thể thích cú pháp biến Sass hơn thông số CSS4 không có nghĩa là bạn chỉ nên bỏ thông số mới. Nếu bạn có thể tham chiếu đến một biến trong CSS vani, bạn nên làm như vậy. Thời đại của các màu sắc dư thừa đã qua!
Trình kiểm tra Chrome chắc chắn sẽ cho phép bạn thay đổi định nghĩa biến CSS4 trong trình duyệt và bạn sẽ thấy kết quả cập nhật trực tiếp trên trang. Trong trường hợp màu sắc, điều đó có nghĩa là chúng tôi có thể thay đổi một biến và yêu cầu mọi thứ sử dụng biến đó thay đổi màu ngay lập tức. Nếu chúng tôi sử dụng các phương pháp cũ của mình, tính năng đó sẽ vô dụng. Nếu các bảng định kiểu của chúng tôi có thể mang tính mô tả nhiều hơn bao giờ hết, thì chúng nên như vậy.
Điều gì sẽ xảy ra nếu chúng ta có thể sử dụng Sass để tạo và sử dụng các biến tuân thủ CSS4 của mình? Chúng ta có thể. Trước tiên, hãy tạo tất cả các khai báo biến màu CSS4 toàn cầu của chúng tôi bằng cách sử dụng bản đồ biến Sass và lặp lại nó bằng vòng lặp @each:
// sass variable map
$colors: (
primary: #FFBB00,
secondary: #0969A2
);
// ripped CSS4 vars out of color map
:root {
// each item in color map
@each $name, $color in $colors {
--color-#{$name}: $color;
}
}
Điều này sẽ biên dịch thành các khai báo biến CSS4 hợp lệ.
:root {
--color-primary: #FFBB00;
--color-secondary: #0969A2;
}
Những gì chúng tôi vừa làm là khá đơn giản. Chúng tôi lặp lại từng mục trong bản đồ $ Colors của chúng tôi. Trong khi lặp lại, chúng tôi có thể truy cập cả tên và màu sắc của mục trong bản đồ. Sau đó, sử dụng cú pháp nội suy (# {}), chúng ta có thể lấy ra tên của biến dưới dạng một chuỗi và kết hợp nó với một tiền tố biến --color- như định nghĩa biến CSS4.
Vì vậy, nó bao gồm việc khai báo các biến, bây giờ chúng ta cần có thể tham chiếu chúng. Chúng ta có thể làm điều đó với @function
:
@function color($color-name) {
@return var(--color-#{$color-name});
}
This color
function will take a color name, and return our CSS4 variable reference. We can then use this function and have our color references be syntactically-awesome:
// calling our sass function
body {
color: color(primary);
}
/* compiles to */
body {
color: var(--color-primary);
}
Further Sophistication
Let’s say we have a complex variable map of sizes that includes both nested maps and top-level values:
$sizes: (
gutter: 30px,
spacer: 15px,
container: (sm: 750px, md: 970px, lg: 1170px),
viewport: (sm: 768px, md: 992px, lg: 1200px)
);
To access our nested maps container
and viewport
, while maintaining the ability to reference our top-level variables gutter
and spacer
, we’ll need to slightly modify our prior approach:
// ripping CSS4 vars out of size map
:root {
// each item in size map
@each $name, $size in $sizes {
// maps require a second loop
@if type-of($size) == "map" {
// each item in sub map
@each $subname, $subsize in $size {
// --size-viewport-md
--size-#{$name}-#{$subname}: $subsize;
}
// top-level sizes
} @elseif type-of($size) == "number" {
// --size-background
--size-#{$name}: $size;
}
}
}
As you can see, we have the ability to detect the type-of
value for each item in our $sizes
map. If it is not a "number"
and it is a "map"
, we do another loop on that item’s map value. The output is a tidy collection of dash-spaced variable references:
:root {
--size-gutter: 30px;
--size-spacer: 15px;
--size-container-sm: 750px;
--size-container-md: 970px;
--size-container-lg: 1170px;
--size-viewport-sm: 768px;
--size-viewport-md: 992px;
--size-viewport-lg: 1200px;
}
In order to reference our more sophisticated variables, we need a more sophisticated @function
:
// retrieve size from map with Sass ie. `size(viewport, sm)`
@function size($size-name, $size-variant:null) {
// size variant is optional
@if ($size-variant != null) {
// map inception, need two names
@return var(--sizes-#{$size-name}-#{$size-variant});
} @else {
// single-level size, one name
@return var(--sizes-#{$size-name});
}
}
As you can see, we will optionally pass in a $size-variant
value. In this case, those would be sm
, md
, or lg
for container
or viewport
. If the variant is there, we know that it is a nested map value and can write the appropriate CSS4 variable reference. If it is null
, we can spit out our standard top-level reference.
UPDATE 7/21/2016: I'm about to show CSS4 variables being used in @media
queries. @keithjgrant was kind enough to point out that these wont actually work since @media
queries are outside the :root
scope. I am leaving it in as an example of iteration. Towards the end of this article there is a section on adding an override to spit out the true value and not the var
notation. You could use that here inside the @media
query.
Here’s how we use it:
// referencing a top-level size variable with CSS via Sass
.container {
margin: size(spacer) auto;
}
// referencing our nested size variables with CSS via Sass
@media (min-width: size(viewport, sm)) {
.container { width: size(container, sm); }
}
Here’s the valid CSS4 output:
.container {
margin: var(--size-spacer) auto;
}
@media (min-width: var(--size-viewport-sm)) {
.container {
width: var(--size-container-sm);
}
}
By managing our CSS4 variables in Sass, we are free to take advantage of all the awesome things about Sass, like looping over our viewport
sizes and outputting container
sizes for each.
// referencing our size variables with CSS via Sass @each loop
@each $name, $size in map-get($sizes, viewport) {
@media (min-width: size(viewport, $name)) {
.container { width: size(container, $name); }
}
}
The above @each
loop will output container sizes for each of our viewport widths.
@media (min-width: var(--size-viewport-sm)) {
.container {
width: var(--size-container-sm);
}
}
@media (min-width: var(--size-viewport-md)) {
.container {
width: var(--size-container-md);
}
}
@media (min-width: var(--size-viewport-lg)) {
.container {
width: var(--size-container-lg);
}
}
Over the top
Let’s crank up the funk and groove out to the tune of complexity. Say that there was a case where we wanted to get the true Sass value of the variable instead of the CSS4 selector. To illustrate, I am going to move back to colors.
// color variable map
$colors: (
text: #FFF,
background: #333,
// nested map inception
primary: (
base: #FFBB00,
light: lighten(#FFBB00, 15%),
dark: darken(#FFBB00, 15%),
trans: transparentize(#FFBB00, 0.5)
),
// and another. is the totem still spinning?
secondary: (
base: #0969A2,
light: lighten(#0969A2, 15%),
dark: darken(#0969A2, 15%),
trans: transparentize(#0969A2, 0.5)
)
);
To to get the actual value of the color, we need another optional parameter in our function. Let’s call it $true-val
.
@function color($color-name, $color-variant:null, $true-val:false) {
// if we are returning the true color value
@if $true-val == true {
// color variant is optional
@if ($color-variant != null) {
// map inception, need two deep
@return map-get(map-get($colors,$color-name),$color-variant);
} @else {
// single-level color, one deep
@return map-get($colors,$color-name);
}
// if we’re only returning the CSS4 variable
} @else {
// color variant is optional
@if ($color-variant != null) {
// map inception, need two names
@return var(--color-#{$color-name}-#{$color-variant});
} @else {
// single-level color, one name
@return var(--color-#{$color-name});
}
}
}
We are defaulting the $true-val
value to false
, which means the second block of code will run unless we pass in a true value.
body {
color: color(primary,base);
color: color(primary,base,false);
color: color(primary,base,true);
color: color(background,null,true);
}
body {
color: var(--colors-primary-base);
color: var(--colors-primary-base);
color: #FFBB00;
color: #333;
}
You might be wondering when this would ever be useful, and I will answer that inadvertently through another important example. Remember at the beginning of this post where we saw that CSS4 variable could be defined inside of a selector’s scope? After briefly mentioning it I jumped straight into global variables using the :root
pseudo class. I didn’t forget about scoped variables.
The fact is, the vanilla syntax for defining a variable is fairly concise. We can create a @mixin
to create variables, but it would take more code to write the inclusion of the mixin than to just write the standard CSS4 variable. Just to take this to the nth-degree and to appease the Sass gods, we will make both a @mixin
to create a variable and a custom @function
to reference any variable—global or scoped.
// define local variable just because
@mixin var($name,$value) {
#{--$name}: $value;
}
// access any variable
@function v($name) {
@return var(--#{$name});
}
We then define a local variable by including our @mixin
, and refer to the variable using our @function
. Note that I am also defining the variable in vanilla syntax so you can see how unnecessary the @mixin
is.
h1 {
// getting a darker form of our true value
$darker-primary: darken(color(primary,base,true), 5%);
// defining the new variable without the extra -- fluff
@include var(darker-primary, $darker-primary);
// CSS4 syntax is much more terse
--darker-primary: $darker-primary;
// accessing our locally-scoped CSS4 variable
color: v(darker-primary);
border-color: v(darker-primary);
}
This will output the following valid CSS4 locally-scoped variable and references.
h1 {
--darker-primary: #e6a800; /* mixin */
--darker-primary: #e6a800; /* vanilla */
color: var(--darker-primary);
border-color: var(--darker-primary);
}
This level of complexity may not suit everyone, but it is certainly doable if you enjoy writing less code.
Bonanza!
Techniques like this allow us to utilize the power of Sass without having to ignore important advances in CSS. Any Sass we write should always output well-written modern CSS. Just because the W3C implements seemingly competing features into CSS does not mean we need to choose one over the other. Embrace the webs!
Props for making it to the end of this post. Here is everything we have learned, fully-functioning in a pen. Click “View Compiled” to see the output. Obviously the CSS4 variables are invalid CSS right now, but you can see that the Sass compiler can already handle it.