[Sass] Get Your Groove On
Introduction
Sass cleans up your CSS by adding nested rules, variables and functions, mixins and selector inheritance. Now, what the heck does any of that mean? As with a great many things, working it out for yourself is often the best way to learn.
If you have not already installed Sass, you should do so now. Instructions can be found on Sass' site (sass-lang.com) or in the first task in this course ([haml] Jumping In). In this task, we'll be looking at Sass's newer syntax, commonly referred to as SCSS (or Sassy CSS). This syntax loses significant whitespace (which was inspired by Haml), but adds full compatibility with CSS, meaning you could copy and paste a big ol' CSS file into a SCSS file and it would automatically work. Since this is the newer and more preferred syntax, it just makes sense to use it over the old one.
Code Examples
Let's start by looking at a (very) basic HTML page. We'll combine Haml and Sass in the next task.
<html> <head> <title>My Awesome Demo Page</title> <link rel="stylesheet" href="master.css" type="text/css" media="screen" title="no title" charset="utf-8"> </head> <body> <h2>Groovy!</h2> <table> <thead> <tr> <th>Start</th> <th>33%</th> <th>66%</th> <th>End</th> </tr> </thead> <tbody> <tr class="white"> <td class="zero">White</td> <td class="thirty-three">Light Gray</td> <td class="sixty-six">Dark Gray</td> <td class="one-hundred">Black</td> </tr> <tr class="red"> <td class="zero">Dark Red</td> <td class="thirty-three">Red</td> <td class="sixty-six">Pink</td> <td class="one-hundred">White</td> </tr> </tbody> </table> </body> </html>
Like I said, basic. I do not actually encourage the use of tables in most situations (so many better options out there), but it serves well to demonstrate a few things here.
If you have not done so yet, type the above into your preferred text editor (okay, you can copy-paste the html if you want. This is a Sass course, not a HTML course, so I'll let you slide on that one) and save it as "index.html". Then, create and open a new file called "master.scss" and type the following (yes, I recommend you type it: best way to learn something is to experience it, and typing it in by hand is how you experience code).
/* master.scss */ $bkg: yellow; $wht: white; $red: #8b0000; $brdr: 1px solid black; body { background: #eee; } table { background: $bkg; border-collapse:collapse; border: $brdr; thead { border-bottom: $brdr; } th, td { border-right: $brdr; } td { padding: 5px; } } tr { &.white { td { &.zero { background-color: darken($wht, 0%); } &.thirty-three { background-color: darken($wht, 33%); } &.sixty-six { background-color: darken($wht, 66%); color: $wht; } &.one-hundred { background-color: darken($wht, 100%); color: $wht; } } } &.red { td { &.zero { background-color: lighten($red, 0%); color: $wht; } &.thirty-three { background-color: lighten($red, 33%); color: $wht; } &.sixty-six { background-color: lighten($red, 66%); } &.one-hundred { background-color: lighten($red, 100%); } } } }
First, a disclaimer: I am not a Sass Master. There are probably more elegant ways of doing what I've done here. That's besides the point though, because I didn't create this to be a masterpiece, I created it to be an example. ;)
Having saved the html and scss files, go to your Terminal or Command Prompt and type this in:
sass --watch master.scss:master.css
This starts the Sass watcher, which will detect anytime a change occurs in the .scss file and then compiles it to the master.css file. Once the master.css file has been written to, open up the index.html in your browser. The result should look something like this:
If it doesn't look like that, check for typos. If there are no typos, then don't look at me. You're the programmer, you go figure it out. ;)
Code Review
Let's look at the highlights of our Sass code. First off, I defined a few variables at the top of the script using the $ sign. You can define variables using strings (with or without quotes, Sass can usually figure out what you mean), numbers (including hex values and percentages), or just about any combination thereof you need, as long as it conforms in the end to a CSS-like value. I love to use variables to cut down on repeating myself.
Next, in the table style definition, you'll get your first look at nesting. Nesting allows us to quickly define styles for child elements under a specific parent. These nested/child elements inherit from their parent(s) just like in regular CSS, but don't require their own special blocks to define. See, cleaner code already!
After that comes the big long section where we define the background color for each of the td elements. First, we state that we want to define something for the tr elements, and then a separate section for each class. We could have defined them as tr.white and tr.red, but I like organizing this way a little better. Plus, it shows off another fine Sass shortcut: '&', the parent-selector reference.
This code also illustrates an important concept to keep in mind when nesting your code while using Sass. If I had written ".white" without the "&", it would compile to tr .white in the CSS file, which wouldn't match the tr.white element correctly. I know this, because I tried it that way first, and it didn't work. This is because in rendering CSS, tr .white selects any elements with the white class under a tr parent element.
I then went on under each class to define the background-color for each class of td using the same parent-selector reference. For the background-color, we're making use of one of Sass' many functions called 'darken'. Later, under the red class, we use its opposite 'lighten'. A full reference to the many functions and how to use them can be found here.
Advanced Techniques: Mixins
While this code is perfectly acceptable as is, I felt like there had to be a better way to accomplish the lighten and darken techniques without having to write 90% of the same code all over again. Turns out, there is a better way, using what Sass calls "Mixins".
Rather than bore you with what a mixin is and what you can do with it, let me show you. Type in the following somewhere in your master.scss, I prefer sticking mixins at the top but you do what works for you.
@mixin the_colors($l_or_d, $color: $wht) { &.zero { background-color: if(($l_or_d == "darken"), darken($color, 0%), lighten($color, 0%)); color: if(($l_or_d == "darken"), black, $wht); } &.thirty-three { background-color: if(($l_or_d == "darken"), darken($color, 33%), lighten($color, 33%)); color: if(($l_or_d == "darken"), black, $wht); } &.sixty-six { background-color: if(($l_or_d == "darken"), darken($color, 66%), lighten($color, 66%)); color: if(($l_or_d == "darken"), $wht, black); } &.one-hundred { background-color: if(($l_or_d == "darken"), darken($color, 100%), lighten($color, 100%)); color: if(($l_or_d == "darken"), $wht, black); } }
First off, we say to the compiler "hey, this is a mixin" by using the @mixin directive. Then I named the mixin "the_colors", followed by a couple of argument definitions. "$l_or_d" will be where we say if we want to lighten or darken, and "$color" is where we define what color we want to perform the lightening or darkening to. Here, I gave the $color variable a default value, in this case $wht.
In this mixin, we're also making use of another function, the "if" function from Sass. "if" lets us do a boolean operation (comparing one or more things to determine if they evaluate to true or false), and then return one of two values depending on the value of the comparison. In english, this reads like this: "if $l_or_d is equal to 'darken', then darken $color by X%, otherwise lighten $color by X%, and return that value as the background-color".
Having defined our mixin, let's put it to good use. Go back to your .scss file and change the bottom tr et al definitions to look like this:
tr { &.white td { @include the_colors("darken"); } &.red td { @include the_colors("lighten", $red); } }
Here I use another directive, "@include", to tell the Sass compiler to include the mixin I defined earlier. You may notice that for the white mixin include, I did not specify a color to darken. This is because I defined a default value of $wht in the mixin definition.
Save your master.scss file, confirm that the watcher overwrites the master.css, and then reload your index.html. If all goes well, then it should look exactly the same. Yes, all that work to get the same result, but don't you feel good about having cleaner, DRY-er code? (DRY == "Don't Repeat Yourself", a good principle to live by when coding.)
So that does it for this task. Think you could do the same thing better? Have some additional tips and tricks you want to share? Feel free to comment, or share this task with others. Or, if you're itching for more, move on to the next task, where we'll combine Haml and Sass into a new project together.