Advertisement

How to Squeeze the Most out of LESS

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

During a sick day a few weeks ago, I got around to something I've been meaning to look at for about a year: LESS. If anything web technology is worth a look, I promise LESS is. In this article, we'll look at the amazing power of LESS and its ability to streamline and improve your development process. We'll cover rapid prototyping, building a lightweight grid system, and using CSS3 with LESS.


Getting Started: What We'll Be Using

For this tutorial, I will be using the PHP LESS implementation by Leaf Corcoran. This implementation is only one of many and the principles discussed in this tutorial should apply to any LESS implementation. Other versions of LESS include the original Ruby version and a JavaScript implementation.

Start by downloading the latest version of LESS. After you extract the file, you should have a 'lessphp' folder. Inside you will find a file called 'less.inc.php': that's the one we want. Take this file and place it in your site's CSS folder.


Setting Up Your Master PHP Stylesheet

The way LESS PHP works is by parsing .less files and converting them to CSS, that the browsers can understand. LESS PHP can accomplish this task in several ways, but we are going to be using a simple PHP class method to parse the CSS; other methods are discussed in the LESS PHP documentation. All right, let's write some code.

Create the file styles.php in your site's CSS folder.

        <?php
        if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) ob_start("ob_gzhandler"); 
        else ob_start();

The first line is merely a means to gzip the final "CSS" file, reducing its file size; this is always a good practice. You could also do this via an .htaccess file.

        $files = array(
            'styles.less'
        );

In this code we are setting up the paths to our .less files (we'll add more later) and storing these paths in an array. The one thing about LESS PHP is that the parsing can take a while; it can take up to a few hundred miliseconds if you have 20K+ of .less file. It also uses server resources. If you are going to be using the PHP version, caching is a must. Let's see one way that we could accomplish this.

        $time = mktime(0,0,0,21,5,1980);
        $cache = 'cache.css';

        foreach($files as $file) {
            $fileTime = filemtime($file);

            if($fileTime > $time) {
                $time = $fileTime;
            }
        }

        if(file_exists($cache)) {
            $cacheTime = filemtime($cache);
            if($cacheTime < $time) {
                $time = $cacheTime;
                $recache = true;
            } else {
                $recache = false;
            }
        } else {
            $recache = true;
        }

This code checks each of our .less files and gets the time each of them was last modified. This time is then compared to the modified time of the cache file, if it exists. If the cache file doesn't exist yet—or needs to be updated—we set $recache to true. Continuing on...

        if(!$recache && isset($_SERVER['If-Modified-Since']) && strtotime($_SERVER['If-Modified-Since']) >= $time){
            header("HTTP/1.0 304 Not Modified");
        } else {
            header('Content-type: text/css');
            header('Last-Modified: ' . gmdate("D, d M Y H:i:s",$time) . " GMT");

            if($recache) {
                require 'lessc.inc.php';
                $lc = new lessc();

                $css = '';

                foreach($files as $file){
                    $css .= file_get_contents($file);
                }

                $css = $lc->parse($css);
                file_put_contents($cache, $css);
                echo $css;
            } else {
                readfile($cache);
            }
        }
        ?>

If we don't need to recache and the client's browser has a copy of the file already, we compare the timestamp of the client's copy against the latest modified file's timestamp. If the client has the most up-to-date version, we send a 304 response and the browser doesn't need to download anything. If the client doesn't have a copy yet or has an outdated version, we set the header on our PHP file to be a CSS document and set the last-modified time to the time stamp of the most recently updated .less file.

If we need to create the cache file or update the cache file, we bring in the LESS PHP file. We require it as opposed to including it, because this portion of styles.php will not work without it. Next we loop through the files, assigning their contents to a single variable $css which is parsed by LESS PHP, saved to disc for future caching purposes (only one visitor to the site is needed to cache the file for everyone), and the parsed LESS is output as standard CSS. If the client needs a version of the file (an update or for the first time) and the cached CSS file is up to date, we simply serve that file up, resulting in a significant speed savings.


Using LESS

Before we go too far, we need to create the styles.less file we referenced in styles.php.

        @h1BG: #1D5A50;
        h1 {
            background: @h1BG;
            border-bottom: 1px solid @h1BG + #333;
            border-top: 1px solid @h1BG - #333;
            color: white;
            font-family: Helvetica, sans-serif;
            padding: 3px;
        }

For those unfamiliar with LESS, in this code we set a variable @h1BG and assigned it a dark green color. In the h1 rule, we made use of it three times: once we used the standard property, and the other two times we told LESS that we wanted the borders slightly lighter/darker than the background. LESS PHP performed the math on the hex values for us.

Now let's create a basic HTML document to make use of this new stylesheet.

        <!doctype html>
        <html lang="en">
            <head>
                <meta charset="utf-8">
                <title>My Less Demo</title>
                <link type="text/css" rel="stylesheet" href="css/styles.php">
            </head>
            <body>
                <h1>Sample Page Header</h1>
            </body>
        </html>

If everything went right, you should see something similar to the following image. This is a good time to mention that for LESS, PHP syntax and formatting is very important. If you don't space your variables quite right, your entire stylesheet can vanish due a parse error. In other words, make sure you test the new sheet before pushing changes to a live site.

example of the h1 tag with green background, lighter bottom border, darker top border

If you take a peek at the source of styles.php in your browser, you should see the following:

        h1 {
          background:#1d5a50;
          border-bottom:1px solid #508d83;
          border-top:1px solid #00271d;
          color:white;
          font-family:Helvetica, sans-serif;
          padding:3px;
        }

Note that, not only is the variable appropriately applied, but the variable declaration doesn't appear in the final code. As a side note, LESS PHP condenses your CSS, but does not minify it.

Let's take things a little further now and see what else LESS can do. Make the following additions to your styles.less and HTML file (and while we're at it change @h1BG to @color4):

        @pageWidth: 1000px;
        @color1: #F0F0F0;
        @color2: #201A16;
        @color3: #3B3128;
        @color4: #1D5A50;

        #wrap {
            background: @color1;
            border: 2px solid @color2;
            margin: 0 auto;
            overflow: hidden;
            width: @pageWidth;
        }

        h1 {
            background: @color4;
            border-bottom: 1px solid @color4 + #333;
            border-top: 1px solid @color4 - #333;
            color: white;
            font-family: Helvetica, sans-serif;
            padding: 3px;
        }

        h2 {
            color: #A1915F;
        }

        #content, #skyscraper {
            float: left;
        }

        #content {
            border-right: 1px solid @color1 - #111;
            width: @pageWidth * .75;

            h2 {
                background: @color2;
            }

            p {
                background: #FFF;
            }
        }

        #skyscraper {
            width: @pageWidth * .25;

            h2 {
                background: @color3;
            }
        }
        <body>
            <div id="wrap">
                <h1>Sample Page Header</h1>
                <div id="content">
                    <h2>My Content</h2>
                    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
                </div>
                <div id="skyscraper">
                    <h2>Be the first to advertise!</h2>
                </div>
            </div>
        </body>

Firstly, those of you who are not familiar with LESS may be confused by the rules within rules concept, a.k.a. nested rules. What it means is you don't have to type #content h2 {...}, #content a {...} anymore. It also lets you structure your CSS better so you can clearly see the CSS inheritance chain.

For those of you coding along, you've noticed a few problems. The 1px border on #content is causing our ad section to drop below. This is the same problem you would get if you had defined the widths as 75% and 25%. As many of you know, margin, borders, and padding can wreak havoc with floated, percent-based layouts. So let's fix some of that; it would be nice if the content wasn't so bunched up. Make the following change to styles.less:

        @contentRightBorder: 1px;
        @contentLRPadding: 15px;

        #content {
            border-right: @contentRightBorder solid @color1 - #111;
            padding: 0 @contentLRPadding 5px @contentLRPadding;
            width: (@pageWidth * .75) - @contentRightBorder - (2 * @contentLRPadding);
float problem before and after

Mixins

As you can already tell, I'm not a designer (though the color scheme isn't a bad one *wink*). However, if I was, I would want the headers on the site to have similar characteristics. Let's do it the LESS way. Add the following to styles.less:

        .header(@headingBackground: @color4; @headingFontColor: #FFF;) {
            background: @headingBackground;
            border-bottom: 1px solid @color4 + #333;
            border-top: 1px solid @color4 - #333;
            color: @headingFontColor;
            font-family: Helvetica, sans-serif;
            padding: 3px;
        }

That is called a mixin; think of it like a CSS function. We can apply it to our headers. Make the following changes:

        h1 {
            .header();
        }

        #content {
            h2 {
                .header(@color2; #A1915F;);
            }
        }

        #skyscraper {
            h2 {
                .header(@color3; #A1915F);
            }
        }

Let's take a look at just the header's CSS output to see if we can learn anything about LESS.

        .header {
          background:#1d5a50;
          border-bottom:1px solid #508d83;
          border-top:1px solid #00271d;
          color:#ffffff;
          font-family:Helvetica, sans-serif;
          padding:3px;
        }
        h1 {
          background:#1d5a50;
          border-bottom:1px solid #508d83;
          border-top:1px solid #00271d;
          color:#ffffff;
          font-family:Helvetica, sans-serif;
          padding:3px;
        }
        #content h2 {
          background:#201a16;
          border-bottom:1px solid #508d83;
          border-top:1px solid #00271d;
          color:#a1915f;
          font-family:Helvetica, sans-serif;
          padding:3px;
        }
        #skyscraper h2 {
          background:#3b3128;
          border-bottom:1px solid #508d83;
          border-top:1px solid #00271d;
          color:#a1915f;
          font-family:Helvetica, sans-serif;
          padding:3px;
        }

You can end up with a lot of duplicate code if you are not careful.

This is the main downside with LESS from what I can see: you can end up with a lot of duplicate code, if you are not careful. Be sure to consider what code you insert -- especially in mixins. In the code output we have a class .header that is not used, and multiple properties that are unnecessarily duplicated. Remember, when working with LESS, you are working with both it and CSS, so CSS best practices still apply. A better way to write this would be:

        h1, h2, h3 {
            font-family: Helvetica, sans-serif;
            padding: 3px;
        }

        @header(@headingBackground: @color4; @headingFontColor: #FFF;) {
            background: @headingBackground;
            border-bottom: 1px solid @color4 + #333;
            border-top: 1px solid @color4 - #333;
            color: @headingFontColor;
        }

        h1 {
            @header();
        }

        #content {
            h2 {
                @header(@color2; #A1915F;);
            }
        }

        #skyscraper {
            h2 {
                @header(@color3; #A1915F);
            }
        }

If we look at the code output now, we should see that the properties that should be grouped, are. The unique properties only show up in their respective rules and we don't have an unnecessary rule, .header, appearing in our css.

        h1, h2, h3 {
          font-family:Helvetica, sans-serif;
          padding:3px;
        }
        h1 {
          background:#1d5a50;
          border-bottom:1px solid #508d83;
          border-top:1px solid #00271d;
          color:#ffffff;
        }
        #content h2 {
          background:#201a16;
          border-bottom:1px solid #508d83;
          border-top:1px solid #00271d;
          color:#a1915f;
        }
        #skyscraper h2 {
          background:#3b3128;
          border-bottom:1px solid #508d83;
          border-top:1px solid #00271d;
          color:#a1915f;
        }

Unnecessarily Nesting Selectors

Another thing to be careful about is unnecessarily nesting selectors. If your CSS doesn't need to be that specific to work, don't nest it; it can be its own separate rule. LESS is here to help you write CSS, not to bloat your stylesheet. Don't use it to bring the DOM to your stylesheet; it's bad enough where it is. You don't want to end up with rules like #main .menu_items #center .menuTable table tr td.itemMoveDown a:hover {...}. For a reference point, take a look at the following:

        #wrapper {
            background: blue;

            #content {
                color: pink;

                .about {
                    float: left;

                    p {
                        span {
                            font-weight: bold;

                            a {
                                font-family: Aurabesh, sans-serif;
                            }
                        }

                        a {
                            font-family: Verdana, sans-serif;
                        }
                    }
                }
            }
        }

        /* (among other rules) results in: */

        #wrapper #content .about p span a {
            font-family: Aurabesh, sans-serif;
        }

        /* (depending on the DOM), better could be: */

        #wrapper {
            background: blue;
        }

        #content {
            color: pink;
        }

        .about {
            float: left;

            span {
                font-weight: bold;

                a {
                    font-family: Aurabesh, sans-serif;
                }

            a {
                font-family: Verdana, sans-serif;
            }
        }

Don't use less to bring the DOM to your stylesheet, it's bad enough where it is.

Above, if in your site #content is always inside #wrapper, you don't need to specify #contents within #wrapper, and so on. From before, if your document has tds that aren't within rows or tables, you have other problems. Again, before you nest, make sure it is saving you time and is necessary. As a general rule of thumb, never nest inside of body or your main div.

Ok, back to our example. So you've just shown the site to the customer. It was too wide for his smartphone, so he would like his site to be 800px wide instead. Oh, and he wants his colors to be a light blue and a dark blue, and the green should be an orange. Assuming he wants to keep the same proportions, how many lines do we have to change for the new layout and theme? Just four:

        @pageWidth: 800px;
        @color2: #9CDCF8;
        @color3: #0E0E7A;
        @color4: #F89500;

In the old days, with a larger site, this could mean digging through dozens of CSS files, redoing width calculations and updating multiple colors throughout. With LESS, it is trivial. Later we'll explore how you could take this concept even further.

Takeaways

  • LESS's syntax is similar to CSS.
  • LESS can do math on colors and units of measurement.
  • LESS variables can be used in multiple places in your CSS.
  • LESS can duplicate code, so use the same judgement you would with CSS.
  • LESS makes adjusting an entire site's CSS trivial.

A Flexible, Semantic, and Lightweight Grid

I don't know how many of you have worked with some of the popular CSS grid systems around, so let me introduce you to a few problems.

        <div id="wrap" class="w16">
            <div id="content" class="w12">
                <h2 class="g12">My Content</h2>
                <p class="g4">Sample text</p>
                <p class="g4">More sample text</p>
                <p class="g4">And some more sample text</p>
            </div>
            <div id="skyscraper" class="w4">
                <h2 class="g4">Still waiting for some ads!</h2>
                <p class="g4">Space is available</p>
            </div>
        </div>

This code hyperbole has a point: frameworks can cause gross code. Our markup has developed a serious case of 'classitis', not to mention that none of the classes are semantic. The CSS isn't too much better:

        g1, g2, g3 ... {
            display: inline;
            float: left;
            margin: 0 10px;
        }
        wrap2, wrap3 ... {
            margin: 0;
        }
        g1 { width: XXpx; }
        g2 { width: XXpx; }
        ...
        wrap2 { width: XXpx; }
        wrap3 { width: XXpx; }
        ...
        wrap12 g2, wrap12 g3 ... {
            width: XXpx;
        }
        ...
        alpha, omega, push, pull...

It ends up being dozens upon dozens of lines...minified. Most grid systems also suffer from a problem we encountered before. What if I want to add a border to an element, or some padding? Up until now, if you were working in one of these systems, you either had to add an extra div inside your "grid div" which had the border and padding, or you had to overwrite your grid rule saying #content { width: 699px; } because it has a border and some padding. We also run into a pretty bad problem if the client changes the site width, as this CSS has been tailor made to a specific content width and column count.

Flexible Grid with LESS

Ready to build a semantic, flexible grid system...in fourteen lines of code? Using only the knowledge we've already learned? Introducing grid.less:

        @unit: @pageWidth / 24;
        @gm: 10px;
        @g(@u: 20; @margin: @gm; @marginTop: 0; @marginBottom: 0;) {
            @gpadding: @gpadding + 0;
            @gborder: @gborder + 0;
            display: inline;
            float: left;
            margin: @marginTop @margin @marginBottom @margin;
            width: (@u * @unit) - ((@margin * 2) + @gpadding + @gborder);
        }
        @shift(@pu: -20){
            left: @pu * @unit;
            position: relative;
        }
        @newRow {
            clear: left;
        }

Ok, so it was seventeen lines, but I don't want to count the closing brackets. Let's step through the code. First, we are defining a LESS variable, called unit. Each unit is 1/24th of the page width. Feel free to pick any number you are comfortable with. I chose 24 because both it and its half (12) can be divided nicely by 2, 3, and 4. Shortly, we'll find that it doesn't matter what number you put here, grid.less can handle anything you throw at it. One thing to note however: Webkit browsers would like you to pick a number of columns that is divisible by your site width, or else they may lose one or two pixels if you put a bunch of small grid units on a line. Chrome and Safari don't like percent-based layouts either if the percent ends up with partial pixels. Firefox and IE (surprisingly) don't care: they handle the difference by adding in the extra pixels throughout the elements, a pixel here and a pixel there.

On the second line, @gm stands for grid margin; on the next, @g stands for grid unit. Both are short to make it easier to type when applying, but having short names won't reduce your file size at all, because LESS variables and @-prefixed mixins don't get output to the final CSS file when the LESS files are parsed. You don't have to advertise your grid system in your CSS any longer since the grid magically disappears. Thanks LESS!

Back to the code. In the @g mixin, the first parameter is what unit the element is: 2, 4.8, 6, 12, etc. Wait...4.8? Sure; you wanted five same-sized boxes filling a row on the page, didn't you? 24/5.

The second parameter is the default margin we have between our page elements; it takes the value defined by @gm. Want them closer? @gm: 5px. Further? @gm: 10px or @gm: 12px. LESS can do it.

Looking at the code once more, you may notice we are lacking a wrapping element that most grid systems have. Or are we? Apply @g(12; 0;) to a rule. Done.

We also have the option to pass margin-top and margin-bottom into our grid function as the third and fourth parameters. This is nice for two reasons. First, it allows us to utilize the margin shorthand code; and second, we only have to manage the margins in one place, the mixin call.

Inside the mixin, we are pulling a slight trick. With LESS PHP the @gpadding and @gborder, which are used to do our width calculation, are always 0 unless they are defined in the CSS/LESS rule before we apply the @g mixin. In other versions of LESS, you may need to engineer a different reset such as passing it in as a parameter into the mixin.

@shift is used primarily to aid in SEO purposes. Generally, it is good practice to put your main content as early in the document as you can for search engines to give it a good weight. Sometimes, the designs we are given indicate that other content—such as a sidebar or advertisement—comes first. With shift, we can maintain good markup and still achieve our desired design. @shift can accept either positive or negative units. @newRow is merely a convenient reminder; it isn't really needed, as you can easily type clear: left on your own. If you do, we're back to those 14 lines I was hoping for earlier.

Our Grid in Use

Enough talk, it's time to see an example so we can wrap our heads around all of this. Let's start the code fresh. In styles.php we will now have 3 less files: settings.less, grid.less, and styles.less. Settings.less will get our color scheme variables, the heading code, and the site width. Don't forget to wipe styles.less so we can work with some new markup. Let's also make movie5.html.

Your file array in styles.php should look like the following:

$files = array(
    'settings.less',
    'grid.less',
    'styles.less'
);

The markup for movies5.html:

        <div id="page">
            <h1>My Movies</h1>
            <ul id="site-navigation">
                <li><a href="#">Movie 1</a></li>
                <li><a href="#">Movie 2</a></li>
                <li><a href="#">Movie 3</a></li>
                <li><a href="#">Movie 4</a></li>
                <li class="selected"><a href="#">Movie 5</a></li>
                <li><a href="#">Movie 6</a></li>
            </ul>
            <div id="movie">
                <h2 id="movie-title">Movie 5</h2>
                <div id="dvd-cover">
                    <img alt="movie poster">
                </div>
                <p id="short-description">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
                <div id="characters">
                    <div class="character">
                        <h3>Farmer</h3>
                        <img alt="feeling cold">
                        <p>About this character...</p>
                    </div>
                    <div class="character">
                        <h3>Gunslinger</h3>
                        <img alt="nice tent">
                        <p>About this character...</p>
                    </div>
                    <div class="character">
                        <h3>Princess</h3>
                        <img alt="fiery temper">
                        <p>About this character...</p>
                    </div>
                    <div class="character">
                        <h3>Sorcerer</h3>
                        <img alt="out of breath">
                        <p>About this character...</p>
                    </div>
                    <div class="character">
                        <h3>Frog</h3>
                        <img alt="swamp country">
                        <p>About this character...</p>
                    </div>
                    <div class="character">
                        <h3>Trusty Dog</h3>
                        <img alt="walking carpet">
                        <p>About this character...</p>
                    </div>
                </div>
            </div>
            <div id="main-ad" class="ad">Ad space available</div>
            <div id="secondary-ad" class="ad">Advertise here</div>
        </div>

Our new stylesheet for styles.less:

        #page {
            background: @color1;
            border: 1px solid @color3;
            overflow: hidden;
            margin: 0 auto;
            padding: @gm 0;
            width: @pageWidth;
        }

        h1 {
            @gpadding: 6;
            @g(6);
            @header();
        }

        #site-navigation {
            padding: 0;

            @g(18; 0;);

            li {
                background: @color2;
                padding: @gm / 2;
                @gpadding: @gm;
                @g(3; @gm; @gm;);

                &.selected {
                    background: @color4;
                }
            }
            a {
                color: #FFF;
                text-decoration: none;
            }
        }

        @midBlockHeight: 468px;

        #movie {
            @shift(3);
            @g(18; 0; @gm;);
            @newRow;
            min-height: @midBlockHeight;

            h2 {
                @gpadding: 6;
                @g(18; @gm; 0; @gm;);
                @header();
            }
        }

        #short-description {
            background: @color1 + #111;
            padding: @gm / 2;
            @gpadding: @gm;
            @g(12);
        }

        #dvd-cover {
            background: @color2;
            padding: @gm;
            @gpadding: @gm * 2;
            @g(6);

            img {
                background: #FFF;
                display: block;
                height: 100px;
                width: 100%;
            }
        }

        #characters {
            border-top: 1px solid @color1 - #222;
            @g(18; 0; @gm;);
        }

        .character {
            background: @color1 - #111;
            padding: 5px;
            @gpadding: 10px;
            @g(6; @gm; @gm;);

            h3 {
                margin: 0 0 5px;
                @header();
            }

            img {
                background: #FFF;
                display: block;
                float: left;
                margin-right: @gm;
                height: 50px;
                width: 50px;
            }
        }

        .ad {
            border: 1px solid @color2;
            height: @midBlockHeight;
            @gborder: 2;
            @g(3; @gm; @gm;);
        }

        #main-ad {
            @shift(-18);
        }

Assuming copy-and-paste worked, you should end up with something close to this:

example grid layout

A few notes on the previous LESS code. If you take a moment to study it, you will notice multiple times that I used the @g mixin to control vertical height, as well as the regular grid specification of the number of columns an element takes up. When defining top and bottom margin, if the element was not a wrapping element, I always passed in @gm as the second parameter to the mixin. I did this instead of entering the value that @gm was. The reason for this is that it only leaves one place if we decide we want to adjust the horizontal spacing between grid elements.

Also of note: whenever you define border or padding, you need to set @gborder or @gpadding to the sum of the respective horizontal units. Be sure to remember that a rule like padding: 5px; means 10px of total padding. Lastly you may have noticed an & in the code. With LESS PHP this is a joiner and results in li.selected. I could have just as easily moved .selected out of the li rule but still within the #site-navigation rule to achieve the same effect (it is actually 2 characters smaller in the CSS if you do).

It may not be THE perfect grid system, but I definitely think it can be with a little more work. It allows for semantic markup, avoids classitis, and can result in potentially a much smaller CSS footprint, since you only output the code you actually need. If you have any suggestions, please let me know by using the comments below.

I would be ungrateful if I didn't offer some special thanks to Kyle Blomquist, with whom I tossed around a few ideas.

Takeaways

  • Mixins can be used to define more complex systems. Figure out a problem that can be solved with math and create your mixin to put idea to browser.

Cross-browser CSS3

What's a tutorial these days without CSS3? LESS can make it easier on designers since they don't have to remember the cross-browser syntax differences. Create css3.less and add it to styles.php. We'll start with something basic, like a nice linear gradient.

        @linearGradient(@color1: #000000; @color2: #FFFFFF;) {
            background: -moz-linear-gradient(@color1, @color2);
            background: -webkit-gradient(linear, left top, left bottom, from(@color1), to(@color2));
            filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=@color1, endColorstr=@color2);
            -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=@color1, endColorstr=@color2)"; */
            -moz-background-clip: padding;      /* Firefox 1.0-3.6 */
            -webkit-background-clip: padding-box;  /* Safari, Chrome */
            background-clip: padding-box;  /* Firefox 4.0+, Opera */
        }

Well, if you haven't done it much, you'll soon realize that CSS3 rules are implemented somewhat differently when it comes to Firefox, Webkit, or not at all when it comes to IE pre-9. As a result, it limits LESS...slightly. For example, Firefox allows you to specify the degrees a gradient travels, but Webkit is limited to top left bottom left type definition. IE only has a filter with two types: vertical and horizontal. As a result, if we want another direction, we have to make another LESS mixin.

        @linearGradientH(@color1: #000000; @color2: #FFFFFF;) {
            background: -moz-linear-gradient(0deg,@color1, @color2);
            background: -webkit-gradient(linear, left top, right top, from(@color1), to(@color2));
            filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=@color1, endColorstr=@color2, GradientType=1);
            -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=@color1, endColorstr=@color2, GradientType=1)";
            -moz-background-clip: padding;      /* Firefox 1.0-3.6 */
            -webkit-background-clip: padding-box;  /* Safari, Chrome */
            background-clip: padding-box;  /* Firefox 4.0+, Opera */
        }

We can avoid a little of the duplication if we move the clipping properties to their own mixin.

        @backgroundClip {
            -moz-background-clip: padding;      /* Firefox 1.0-3.6 */
            -webkit-background-clip: padding-box;  /* Safari, Chrome */
            background-clip: padding-box;  /* Firefox 4.0+, Opera */
        }

        @linearGradient(@color1: #000000; @color2: #FFFFFF;) {
            background: -moz-linear-gradient(@color1, @color2);
            background: -webkit-gradient(linear, left top, left bottom, from(@color1), to(@color2));
            filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=@color1, endColorstr=@color2);
            -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=@color1, endColorstr=@color2)"; */
            @backgroundClip;
        }

Still not great, and now we have two rules, but gradients should be working. So what else can we do with filters so IE can play too? Drop shadows? While we can attempt a few things using the other filters Microsoft provided, such as blur and shadow, most attempts don't match up well with the CSS3 implementations in modern browsers. Frankly, most attempts end up looking pretty awful or require extra markup. Gradients are by far the best looking. There are also some things that can't be done with filters such as rounded corners. I don't know about you, but I would like to use more properties than just gradients.

CSS Pie

Thankfully, I found a nice project out there called CSS3 Pie. This Progressive IE Enhancement tool uses behaviors, a.k.a. what Microsoft was hoping would be reusable JavaScript snippits, to achieve many of the CSS3 effects. If you do want to go the filter route (since PIE relies on JavaScript being enabled), let us know if you fare better than I did using the comments section below.

We'll start by downloading the latest copy of PIE from the site. The file we need is PIE.htc; place it in your CSS directory. CSS3 PIE allows us to make use of linear gradients, rounded corners, and box shadows. Since we are a little more free now, let's see what we can come up with:

        @linearGradient(@color1: #000000; @color2: #FFFFFF; @lgd: 90deg; @wkd1: left top; @wkd2: left bottom;) {
            background: -moz-linear-gradient(@lgd, @color1, @color2);
            background: -webkit-gradient(linear, @wkd1, @wkd2, from(@color1), to(@color2));

            *position: relative;
            *z-index: 1;
            *zoom: 1;
            -pie-background: linear-gradient(@lgd, @color2, @color1);

            -moz-background-clip: padding;      /* Firefox 1.0-3.6 */
            -webkit-background-clip: padding-box;  /* Safari, Chrome */
            background-clip: padding-box;  /* Firefox 4.0+, Opera */
        }

We ended up with one linearGradient mixin after all. Note that PIE requires position relative and has-layout because it is drawing a VML element behind our original html element. If you have some absolutely-positioned elements, you may need to be careful and re-define this property for IE.

        @borderRadius(@radius: 5px;) {
            -moz-border-radius: @radius;
            -webkit-border-radius: @radius;
            border-radius: @radius;
            *position: relative;
            *z-index: 1;
            *zoom: 1;
            behavior: url(PIE.htc);
        }

        @borderRadiusSpecific(@rtl: 0; @rtr: 0; @rbr: 0; @rbl: 0;) {
            -moz-border-radius-topleft: @rtl;
            -moz-border-radius-topright: @rtr;
            -moz-border-radius-bottomright: @rbr;
            -moz-border-radius-bottomleft: @rbl;

            -webkit-border-top-left-radius: @rtl;
            -webkit-border-top-right-radius: @rtr;
            -webkit-border-bottom-right-radius: @rbr;
            -webkit-border-bottom-left-radius: @rbl;

            border-top-left-radius: @rtl;
            border-top-right-radius: @rtr;
            border-bottom-right-radius: @rbr;
            border-bottom-left-radius: @rbl;
        }

Border radius is pretty straightforward; note however, that if we only want to round a portion of the element or have variable rounding, IE won't be able to since the VML is a rounded rectangle with a specific radius.

        @boxShadow(@horizontalOffset: 5px; @verticalOffset: 5px; @blurRadius: 8px; @shadowColor: #555; @spreadRadius: 0px; @inset: ;) {
            -moz-box-shadow: @inset @horizontalOffset @verticalOffset @blurRadius @spreadRadius @shadowColor;
            -webkit-box-shadow: @inset @horizontalOffset @verticalOffset @blurRadius @spreadRadius @shadowColor;

            box-shadow: @inset @horizontalOffset @verticalOffset @blurRadius @spreadRadius @shadowColor;
            *position: relative;
            *z-index: 1;
            *zoom: 1;
            behavior: url(PIE.htc);
        }

While I included some extra properties such as spreadRadius and inset here, CSS3 PIE doesn't yet support them. So, if IE is important to you, hold off on those for now.

        @textShadow(@color: #555; @horizontalOffset: 2px; @verticalOffset: 2px; @blur: 3px;) {
            -moz-text-shadow: @color @horizontalOffset @verticalOffset @blur;
            -webkit-text-shadow: @color @horizontalOffset @verticalOffset @blur;
            /*
                filter: progid:DXImageTransform.Microsoft.dropshadow(OffX=@horizontalOffset, OffY=verticalOffset, Color='@color', Positive='true');
                -ms-filter: "progid:DXImageTransform.Microsoft.dropshadow(OffX=@horizontalOffset, OffY=verticalOffset, Color='@color', Positive='true')";
                *zoom: 1;
            */

            text-shadow: @color @horizontalOffset @verticalOffset @blur;
        }

While IE does have a drop shadow filter, it doesn't work reliably. Text shadows, if subtle, don't cause that big of a difference when it comes to page appearance, so being a little less cross-browser friendly here doesn't hurt too much.

Time for a quick example. Make styles.css3 and add it to your files array in styles.php.

        #page {
            @boxShadow(0; 0; 20px; #777);
            @linearGradient(@color1 - #222; #FFF;);
            @borderRadius(10px;);
        }

        #dvd-cover {
            @boxShadow(3px; 3px; 6px; #555;);
            @borderRadius(15px;);
        }

        #site-navigation li {
            @borderRadius(5px;);
        }

        #short-description {
            @boxShadow(0; 0; 5px; #777);
            @linearGradient(#FFF; @color4; 45deg; left bottom; right top;);
        }

Can you spot which is IE? Sadly, there is a clue, but it's a non-css one, so I guess that is OK.

Firefox, IE 8, and Chrome with css3 applied

RGBA

All right, one last CSS3 property; let's see if we can do RGBA.

        @rgba(@red: 0; @green: 0; @blue: 0; @alpha: .8; @iehex: "#99000000";) {
            background:transparent;
            background: rgba(@red, @green, @blue, @alpha);
            filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=@iehex,endColorstr=@iehex);
            -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr={@iehex},endColorstr={@iehex})";
            zoom: 1;
        }

Well, it's definitely not the prettiest function, but it works. For cool browsers, simply pass in the first four properties like normal. If LESS PHP could do if statements, IE wouldn't require any special parameters, but no such luck. The long-looking hex is made up of two parts. The first two positions set the alpha, 00 for completely transparent, FF for no transparency. The last six positions is for the hex equivalent of the rgb. You may have to do a little math, but it works (make sure the long hex is in quotes). Also, in IE, you can't have rounded corners and RGBA, at least with this implementation. If you do try, RGBA will take precedence.


Getting the Most...Continue to Innovate

A few ideas for where you can go from here:

  • Make your stylesheet accept several $_GET parameters and set your @color1, @color2 ... variables, or even page width with these parameters.
  • In styles.php set up some color schemes. Makes it easy for you to change themes quickly to get that perfect combination for a client.
  • Make a JavaScript theme-switcher based on the same idea.
  • Take the variables even further using mixins. Have a menu? Prepare for both horizontal and vertical using two mixins. Switch using parameters.
  • Skin your favorite applications/frameworks—such as WordPress, Magento, Drupal, CakePHP, etc—using LESS. It makes things a lot easier if the site isn't confined to a single application and you need your app to match someone's HTML page and its existing color scheme.

I've only been playing with LESS for a few weeks and these are the ideas I've come up with so far. What are yours? Please share them with the community below.


You Also Might Like

Advertisement