14 min read

(For more resources related to this topic, see here.)

A basic gauge structure

Let’s begin with a new project; as usual we need to create an index.html file. This time the markup involved is so small and compact that we can add it right now:

Go Go Gauges

The gauge widget is identified by the data-gauge attribute and defined with three other custom data attributes; namely, data-min, data-max, and data-percent, which indicate the respective minimum and maximum value of the range and the current arrow position expressed in percentage value.

Within the element marked with the data-gauge attribute, we have defined a div tag that will become the arrow of the gauge.

To start with the styling phase, we first need to equip ourselves with a framework that is easy to use and can give us the opportunity to generate CSS code. We decide to go for SASS so we first need to install Ruby (http://www.ruby-lang.org/en/downloads/) and then enter the following from a command-line terminal:

gem install sass

You would probably need to execute the following command if you are working in Unix/ Linux environments:

sudo gem install sass

Installing Compass

For this project we’ll also use Compass, a SASS extension able to add some interesting features to our SASS stylesheet.

To install Compass, we have to just enter gem install compass (or sudo gem install compass) in a terminal window. After the installation procedure is over, we have to create a small config.rb file in the root folder of our project using the following code:

# Require any additional compass plugins here. # Set this to the root of your project when deployed: http_path = YOUR-HTTP-PROJECT-PATH css_dir = "css" sass_dir = "scss" images_dir = "img" javascripts_dir = "js" # You can select your preferred output style here (can be overridden via

the command line): # output_style = :expanded or :nested or :compact or :compressed # To enable relative paths to assets via compass helper functions. Uncomment: relative_assets = true # To disable debugging comments that display the original location of your

selectors. Uncomment: # line_comments = false preferred_syntax = :sass

The config.rb file helps Compass to understand the location of the various assets of the project; let’s have a look at these options in detail:

  • http_path: This must be set to the HTTP URL related to the project’s root folder

  • css_dir: This contains the relative path to the folder where the generated CSS files should be saved

  • sass_dir: This contains the relative path to the folder that contains our .scss files

  • images_dir: This contains the relative path to the folder that holds all the images of the project

  • javascripts_dir: This is similar to images_dir, but for JavaScript files

There are other options available; we can decide whether the output CSS should be compressed or not, or we can ask Compass to use relative paths instead of absolute ones. For a complete list of all the options available, see the documentation at http://compass-style.org/help/tutorials/configuration-reference/.

Next, we can create the folder structure we just described, providing our project with the css, img, js, and scss folders. Lastly, we can create an empty scss/application.scss file and start discovering the beauty of Compass.

CSS reset and vendor prefixes

We can ask Compass to regenerate the CSS file after each update to its SCSS counterpart. To do so, we need to execute the following command from the root of our project using a terminal:

compass watch .

Compass provides an alternative to the Yahoo! reset stylesheet we used in our previous project. To include this stylesheet, all we have to do is add a SASS include directive to our application.scss file:

@import "compass/reset";

If we check css/application.css, the following is the result (trimmed):

/* line 17, ../../../../.rvm/gems/ruby-1.9.3-p194/gems/compass- 0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font: inherit; font-size: 100%; vertical-align: baseline; } /* line 22, ../../../../.rvm/gems/ruby-1.9.3-p194/gems/compass- 0.12.2/frameworks/compass/stylesheets/compass/reset/_utilities.scss */ html { line-height: 1; } ...

Notice also how the generated CSS keeps a reference to the original SCSS; this comes in handy when it’s a matter of debugging some unexpected behaviors in our page.

The next @import directive will take care of the CSS3 experimental vendor prefixes. By adding @import “compass/css3” on top of the application.scss file, we ask Compass to provide us with a lot of powerful methods for adding experimental prefixes automatically; for example, the following snippet:

.round { @include border-radius(4px); }

Is compiled into the following:

.round { -moz-border-radius: 4px; -webkit-border-radius: 4px; -o-border-radius: 4px; -ms-border-radius: 4px; -khtml-border-radius: 4px; border-radius: 4px; }

Equipped with this new knowledge, we can now start deploying the project.

Using rem

For this project we want to introduce rem, a measurement unit that is almost equivalent to em, but is always relative to the root element of the page. So, basically we can define a font size on the html element and then all the sizes will be related to it:

html{ font-size: 20px; }

Now, 1rem corresponds to 20px; the problem of this measurement is that some browsers, such as Internet Explorer version 8 or less, don’t actually support it. To find a way around this problem, we can use the following two different fallback measurement units:

  • em: The good news is that em, if perfectly tuned, works exactly as rem; the bad news is that this measurement unit is relative to the element’s font-size property and is not relative to html. So, if we decide to pursue this method, we then have to take extra care every time we deal with font-size.

  • px: We can use a fixed unit pixel size. The downside of this choice is that in older browsers, we’re complicating the ability to dynamically change the proportions of our widget.

In this project, we will use pixels as our unit of measurement. The reason we have decided this is because one of the rem benefits is that we can change the size of the gauge easily by changing the font-size property with media queries. This is only possible where media queries and rem are supported.

Now, we have to find a way to address most of the duplication that would emerge from having to insert every statement containing a space measurement unit twice (rem and px). We can easily solve this problem by creating a SASS mixin within our application.scss file as follows (for more info on SASS mixins, we can refer to the specifications page at http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#mixins):

@mixin px_and_rem($property, $value, $mux){ #{$property}: 0px + ($value * $mux); #{$property}: 0rem + $value; }

So the next time instead of writing the following:

#my_style{ width: 10rem; }

We can instead write:

#my_style{ @include px_and_rem(width, 10, 20); }

In addition to that, we can also save the multiplier coefficient between px and rem in a variable and use it in every call to this function and within the html declaration; let’s also add this to application.scss:

$multiplier: 20px; html{ font-size: $multiplier; }

Of course, there are still some cases in which the @mixin directive that we just created doesn’t work, and in such situations we’ll have to handle this duality manually.

Basic structure of a gauge

Now we’re ready to develop at least the basic structure of our gauge, which includes the rounded borders and the minimum and maximum range labels. The following code is what we need to add to application.scss:

div[data-gauge]{ position: absolute; /* width, height and rounded corners */ @include px_and_rem(width, 10, $multiplier); @include px_and_rem(height, 5, $multiplier); @include px_and_rem(border-top-left-radius, 5, $multiplier); @include px_and_rem(border-top-right-radius, 5, $multiplier); /* centering */ @include px_and_rem(margin-top, -2.5, $multiplier); @include px_and_rem(margin-left, -5, $multiplier); top: 50%; left: 50%; /* inset shadows, both in px and rem */ box-shadow: 0 0 #{0.1 * $multiplier} rgba(99,99,99,0.8), 0 0 #{0.1 * $multiplier} rgba(99,99,99,0.8) inset; box-shadow: 0 0 0.1rem rgba(99,99,99,0.8), 0 0 0.1rem rgba(99,99,99,0.8) inset; /* border, font size, family and color */ border: #{0.05 * $multiplier} solid rgb(99,99,99); border: 0.05rem solid rgb(99,99,99); color: rgb(33,33,33); @include px_and_rem(font-size, 0.7, $multiplier); font-family: verdana, arial, sans-serif; /* min label */ &:before{ content: attr(data-min); position: absolute; @include px_and_rem(bottom, 0.2, $multiplier); @include px_and_rem(left, 0.4, $multiplier); } /* max label */ &:after{ content: attr(data-max); position: absolute; @include px_and_rem(bottom, 0.2, $multiplier); @include px_and_rem(right, 0.4, $multiplier); } }

With box-shadow and border, we can’t use the px_and_rem mixin, so we duplicated these properties using px first and then rem.

The following screenshot shows the result:

Gauge tick marks

How to handle tick marks? One method would be by using images, but another interesting alternative is to benefit from multiple background support and create those tick marks out of gradients. For example, to create a vertical mark, we can use the following within the div[data-gauge] selector:

linear-gradient(0deg, transparent 46%,
rgba(99, 99, 99, 0.5) 47%, rgba(99, 99, 99, 0.5) 53%, transparent 54%)

Basically, we define a very small gradient between transparent and another color in order to obtain the tick mark. That’s the first step, but we’re yet to deal with the fact that each tick mark must be defined with a different angle. We can solve this problem by introducing a SASS function that takes the number of tick marks to print and iterates up to that number while also adjusting the angles of each mark. Of course, we also have to take care of experimental vendor prefixes, but we can count on Compass for that.

The following is the function. We can create a new file called scss/_gauge.scss for this and other gauge-related functions; the leading underscore is to tell SASS to not create a .css file out of this .scss file, because it will be included in a separate file.

@function gauge-tick-marks($n, $rest){ $linear: null; @for $i from 1 through $n { $p: -90deg + 180 / ($n+1) * $i; $linear: append($linear, linear-gradient( $p, transparent 46%, rgba (99,99,99,0.5) 47%, rgba(99,99,99,0.5) 53%, transparent 54%), comma); } @return append($linear, $rest); }

We start with an empty string adding the result of calling the linear-gradient Compass function, which handles experimental vendor prefixes, with an angle that varies based on the current tick mark index.

To test this function out, we first need to include _gauge.scss in application.scss:

@import "gauge.scss";

Next, we can insert the function call within the div[data-gauge] selector in application.scss, specifying the number of tick marks required:

@include background(gauge-tick-marks(11,null));

The background function is also provided by Compass and it is just another mechanism to deal with experimental prefixes. Unfortunately, if we reload the projects the results are far from expected:

Although we can see a total of 11 stripes, they are of the wrong sizes and in the wrong position. To resolve this, we will create some functions to set the correct values for background-size and background-position.

Dealing with background size and position

Let’s start with background-size, the easiest. Since we want each of the tick marks to be exactly 1rem in size, we can proceed by creating a function that prints 1rem 1rem as many times as the number of the passed parameter; so let’s add the following code to _gauge.scss:

@function gauge-tick-marks-size($n, $rest){ $sizes: null; @for $i from 1 through $n { $sizes: append($sizes, 1rem 1rem, comma); } @return append($sizes, $rest, comma); }

We already noticed the append function; an interesting thing to know about it is that the last parameter of this function lets us decide if some letter must be used to concatenate the strings being created. One of the available options is comma, which perfectly suits our needs.

Now, we can add a call to this function within the div[data-gauge] selector:

background-size: gauge-tick-marks-size(11, null);

And the following is the result:

Now the tick marks are of the right size, but they are displayed one above the other and are repeated all across the element. To avoid this behavior, we can simply add background-repeat: no-repeat just below the previous instruction:

background-repeat: no-repeat;

On the other hand, to handle the position of the tick marks we need another SASS function; this time it’s a little more complex and involves a bit of trigonometry. Each gradient must be placed in the function of its angle—x is the cosine of that angle and y the sine. The sin and cos functions are provided by Compass, we need just to handle the shift, because they are referred to the center of the circle whereas our css property’s origin is in the upper-left corner:

@function gauge-tick-marks-position($n, $rest){ $positions: null; @for $i from 1 through $n { $angle: 0deg + 180 / ($n+1) * $i; $px: 100% * ( cos($angle) / 2 + 0.5 ); $py: 100% * (1 - sin($angle)); $positions: append($positions, $px $py, comma); } @return append($positions, $rest, comma); }

Now we can go ahead and add a new line inside the div[data-gauge] selector:

background-position: gauge-tick-marks-position(11, null);

And here’s the much-awaited result:

The next step is to create a @mixin directive to hold these three functions together, so we can add the following to _gauge.scss:

@mixin gauge-background($ticks, $rest_gradient, $rest_size, $rest_position)
{ @include background-image( gauge-tick-marks($ticks, $rest_gradient) ); background-size: gauge-tick-marks-size($ticks, $rest_size); background-position: gauge-tick-marks-position($ticks, $rest_position); background-repeat: no-repeat; }

And replace what we placed inside div[data-gauge] in this article with a single invocation:

@include gauge-background(11, null, null, null );

We’ve also left three additional parameters to define extra values for background, background-size, and background-position, so we can, for example, easily add a gradient background:

@include gauge-background(11, radial-gradient(50% 100%, circle, rgb(255,255,255), rgb(230,230,230)), cover, center center );

And following is the screenshot:

Creating the arrow

To create an arrow we can start by defining the circular element in the center of the gauge that holds the arrow. This is easy and doesn’t introduce anything really new; here’s the code that needs to be nested within the div[data-gauge] selector:

div[data-arrow]{ position: absolute; @include px_and_rem(width, 2, $multiplier); @include px_and_rem(height, 2, $multiplier); @include px_and_rem(border-radius, 5, $multiplier); @include px_and_rem(bottom, -1, $multiplier); left: 50%; @include px_and_rem(margin-left, -1, $multiplier); box-sizing: border-box; border: #{0.05 * $multiplier} solid rgb(99,99,99); border: 0.05rem solid rgb(99,99,99); background: #fcfcfc; }

The arrow itself is a more serious business; the basic idea is to use a linear gradient that adds a color only to half the element starting from its diagonal. Then we can rotate the element in order to move the pointed end at its center. The following is the code that needs to be placed within div[data-arrow]:

&:before{ position: absolute; display: block; content: ''; @include px_and_rem(width, 4, $multiplier); @include px_and_rem(height, 0.5, $multiplier); @include px_and_rem(bottom, 0.65, $multiplier); @include px_and_rem(left, -3, $multiplier); background-image: linear-gradient(83.11deg, transparent, transparent 49%, orange 51%, orange); background-image: -webkit-linear-gradient(83.11deg, transparent, transparent 49%, orange 51%, orange); background-image: -moz-linear-gradient(83.11deg, transparent, transparent 49%, orange 51%, orange); background-image: -o-linear-gradient(83.11deg, transparent,
transparent 49%, orange 51%, orange); @include apply-origin(100%, 100%); @include transform2d( rotate(-3.45deg)); box-shadow: 0px #{-0.05 * $multiplier} 0 rgba(0,0,0,0.2); box-shadow: 0px -0.05rem 0 rgba(0,0,0,0.2); @include px_and_rem(border-top-right-radius, 0.25, $multiplier); @include px_and_rem(border-bottom-right-radius, 0.35, $multiplier); }

To better understand the trick behind this implementation, we can temporarily add border: 1px solid red within the &:before selector to the result and zoom a bit:

Moving the arrow

Now we want to position the arrow to the correct angle depending on the data-percent attribute value. To do so we have to take advantage of the power of SASS. In theory the CSS3 specification would allow us to valorize some properties using values taken from attributes, but in practice this is only possible while dealing with the content property.

So what we’re going to do is create a @for loop from 0 to 100 and print in each iteration a selector that matches a defined value of the data-percent attribute. Then we’ll set a different rotate() property for each of the CSS rules.

The following is the code; this time it must be placed within the div[data-gauge] selector:

@for $i from 0 through 100 { $v: $i; @if $i div[data-arrow]{ @include transform2d(rotate(#{180deg * $i/100})); } }

If you are too scared about the amount of CSS generated, then you can decide to adjust the increment of the gauge, for example, to 10:

@for $i from 0 through 10 { &[data-percent='#{$i*10}'] > div[data-arrow]{ @include transform2d(rotate(#{180deg * $i/10})); } }

And the following is the result:

 

 


Subscribe to the weekly Packt Hub newsletter

* indicates required

LEAVE A REPLY

Please enter your comment!
Please enter your name here