Expressing colors in CSS

As part of my research in Web Typography I got reacquainted with the many ways you can express colors in CSS. This was originally in my typography document but I think it’s better if I move it out to prevent an already long document from becoming unmanageable.

sRGB colors (3 or 6 digits)

This was what I always associted with colors in CSS and until not too long ago it was the only way to express colors. You can use either syntax but, as you can see in the example below, the 6 digit syntax allows for more precision in defining your colors.

The three definitions of a div container express the same color.

div {
  color: #0f0;   // The color 'lime' defined using the 3-digit hexadecimal notation
}

div {
  color: #00ff00; // The color 'lime' defined using the 6-digit hexadecimal notation
}

div {
  color: rgb(0, 255, 0) // The color 'lime' expressed with RGB notation
}

RGBa colors

This should be run behind a modernizr test as it’s not widely supported

This allows us to fill areas with transparent color; the first thee numbers representing the color in RGB values and the fourth representing a transparency value between 0 and 1 (zero being fully transparent and one being fully opaque). We have long had the opacity property, which is similar, but opacity forces all decendant elements to also become transparent and there is no way to fight it (except weird positional hacks) Cross-browser opacity is also a bit sloppy.

With RGBa, we can make a box transparent and leave its descendants alone

div {
   background: rgba(200, 54, 54, 0.5); 
}

Declaring a fallback color

Not all browsers support RGBa, so if the design permits, you should declare a “fallback” color. This color will be most likely be solid (fully opaque). Not declaring a fallback means no color will be applied in browsers that don’t support it. This fallback does fail in some really old browsers.

div {
   background: rgb(200, 54, 54); /* The Fallback */
   background: rgba(200, 54, 54, 0.5); 
}

Table below taken from the Mozilla Documentation Project. Credited according to a Creative Common Attribution-Share Alike License

Color keywords

Color keywords are case-insensitive identifiers which represent a specific color, e.g. red, blue, brown, lightseagreen. The name describes the color, though it is mostly artificial. The list of accepted values varied a lot through the different specification:

  • CSS Level 1 only accepted 16 basic colors, named the VGA colors as they were taken from the set of displayable colors on VGA graphic cards
  • CSS Level 2 added the orange keyword
  • From the beginning, browsers accepted other colors, mostly the X11 named colors list as some early browsers were X11 applications, though with a few differences. SVG 1.0 was the first standard to formally define these keywords; CSS Colors Level 3 also formally defined these keywords. They are often referred as the extended color keywords, the X11 colors, the SVG colors

There are a few caveats to consider when using keywords:

  • Except the 16 basic colors which are common with HTML, the others cannot be used in HTML. HTML will convert these unknown values with a specific algorithm which will lead to completely different colors. These keywords should only be used in SVG & CSS
  • Unknown keywords make the CSS property invalid. Invalid properties being ignored, the color will have no effect. This is a different behavior than the one of HTML.
  • No keyword-defined colors in CSS have any transparency, they are plain, solid colors.
  • Several keywords denote the same colors:
    • darkgray / darkgrey
    • darkslategray / darkslategrey
    • dimgray / dimgrey
    • lightgray / lightgrey
    • lightslategray / lightslategrey
    • gray / grey
    • slategray / slategray
  • Though the names of the keywords have been taken by the usual X11 color names, the color may diverge from the corresponding system color on X11 system as these are tailored for the specific hardware by the manufacturer .
Specifications Color Keyword RGB cubic coordinates Live Example
CSS3 CSS2 CSS1   black rgb(  0,   0,   0)  
  silver rgb(192, 192, 192)  
  gray[*] rgb(128, 128, 128)  
  white rgb(255, 255, 255)  
  maroon rgb(128,   0,   0)  
  red rgb(255,   0,   0)  
  purple rgb(128,   0, 128)  
  fuchsia rgb(255,   0, 255)  
  green rgb(  0, 128,   0)  
  lime rgb(  0, 255,   0)  
  olive rgb(128, 128,   0)  
  yellow rgb(255, 255,   0)  
  navy rgb(  0,   0, 128)  
  blue rgb(  0,   0, 255)  
  teal rgb(  0, 128, 128)  
  aqua rgb(  0, 255, 255)  
    orange rgb(255, 165,   0)  
    aliceblue rgb(240, 248, 255)  
  antiquewhite rgb(250, 235, 215)  
  aquamarine rgb(127, 255, 212)  
  azure rgb(240, 255, 255)  
  beige rgb(245, 245, 220)  
  bisque rgb(255, 228, 196)  
  blanchedalmond rgb(255, 235, 205)  
  blueviolet rgb(138,  43, 226)  
  brown rgb(165,  42,  42)  
  burlywood rgb(222, 184, 135)  
  cadetblue rgb( 95, 158, 160)  
  chartreuse rgb(127, 255,   0)  
  chocolate rgb(210, 105,  30)  
  coral rgb(255, 127,  80)  
  cornflowerblue rgb(100, 149, 237)  
  cornsilk rgb(255, 248, 220)  
  crimson rgb(220,  20,  60)  
  darkblue rgb(  0,   0, 139)  
  darkcyan rgb(  0, 139, 139)  
  darkgoldenrod rgb(184, 134,  11)  
  darkgray[*] rgb(169, 169, 169)  
  darkgreen rgb(  0, 100,   0)  
  darkgrey[*] rgb(169, 169, 169)  
  darkkhaki rgb(189, 183, 107)  
  darkmagenta rgb(139,   0, 139)  
  darkolivegreen rgb( 85, 107,  47)  
  darkorange rgb(255, 140,   0)  
  darkorchid rgb(153,  50, 204)  
  darkred rgb(139,   0,   0)  
  darksalmon rgb(233, 150, 122)  
  darkseagreen rgb(143, 188, 143)  
  darkslateblue rgb( 72,  61, 139)  
  darkslategray[*] rgb( 47,  79,  79)  
  darkslategrey[*] rgb( 47,  79,  79)  
  darkturquoise rgb(  0, 206, 209)  
  darkviolet rgb(148,   0, 211)  
  deeppink rgb(255,  20, 147)  
  deepskyblue rgb(  0, 191, 255)  
  dimgray[*] rgb(105, 105, 105)  
  dimgrey[*] rgb(105, 105, 105)  
  dodgerblue rgb( 30, 144, 255)  
  firebrick rgb(178,  34,  34)  
  floralwhite rgb(255, 250, 240)  
  forestgreen rgb( 34, 139,  34)  
  gainsboro rgb(220, 220, 220)  
  ghostwhite rgb(248, 248, 255)  
  gold rgb(255, 215,   0)  
  goldenrod rgb(218, 165,  32)  
  greenyellow rgb(173, 255,  47)  
  grey rgb(128, 128, 128)  
  honeydew rgb(240, 255, 240)  
  hotpink rgb(255, 105, 180)  
  indianred rgb(205,  92,  92)  
  indigo rgb( 75,   0, 130)  
  ivory rgb(255, 255, 240)  
  khaki rgb(240, 230, 140)  
  lavender rgb(230, 230, 250)  
  lavenderblush rgb(255, 240, 245)  
  lawngreen rgb(124, 252, 0)  
  lemonchiffon rgb(255, 250, 205)  
  lightblue rgb(173, 216, 230)  
  lightcoral rgb(240, 128, 128)  
  lightcyan rgb(224, 255, 255)  
  lightgoldenrodyellow rgb(250, 250, 210)  
  lightgray[*] rgb(211, 211, 211)  
  lightgreen rgb(144, 238, 144)  
  lightgrey[*] rgb(211, 211, 211)  
  lightpink rgb(255, 182, 193)  
  lightsalmon rgb(255, 160, 122)  
  lightseagreen rgb( 32, 178, 170)  
  lightskyblue rgb(135, 206, 250)  
  lightslategray[*] rgb(119, 136, 153)  
  lightslategrey[*] rgb(119, 136, 153)  
  lightsteelblue rgb(176, 196, 222)  
  lightyellow rgb(255, 255, 224)  
  limegreen rgb( 50, 205,  50)  
  linen rgb(250, 240, 230)  
  mediumaquamarine rgb(102, 205, 170)  
  mediumblue rgb(  0,   0, 205)  
  mediumorchid rgb(186,  85, 211)  
  mediumpurple rgb(147, 112, 219)  
  mediumseagreen rgb( 60, 179, 113)  
  mediumslateblue rgb(123, 104, 238)  
  mediumspringgreen rgb(  0, 250, 154)  
  mediumturquoise rgb( 72, 209, 204)  
  mediumvioletred rgb(199,  21, 133)  
  midnightblue rgb( 25,  25, 112)  
  mintcream rgb(245, 255, 250)  
  mistyrose rgb(255, 228, 225)  
  moccasin rgb(255, 228, 181)  
  navajowhite rgb(255, 222, 173)  
  oldlace rgb(253, 245, 230)  
  olivedrab rgb(107, 142,  35)  
  orangered rgb(255,  69,   0)  
  orchid rgb(218, 112, 214)  
  palegoldenrod rgb(238, 232, 170)  
  palegreen rgb(152, 251, 152)  
  paleturquoise rgb(175, 238, 238)  
  palevioletred rgb(219, 112, 147)  
  papayawhip rgb(255, 239, 213)  
  peachpuff rgb(255, 218, 185)  
  peru rgb(205, 133,  63)  
  pink rgb(255, 192, 203)  
  plum rgb(221, 160, 221)  
  powderblue rgb(176, 224, 230)  
  rosybrown rgb(188, 143, 143)  
  royalblue rgb( 65, 105, 225)  
  saddlebrown rgb(139,  69,  19)  
  salmon rgb(250, 128, 114)  
  sandybrown rgb(244, 164,  96)  
  seagreen rgb( 46, 139,  87)  
  seashell rgb(255, 245, 238)  
  sienna rgb(160,  82,  45)  
  skyblue rgb(135, 206, 235)  
  slateblue rgb(106,  90, 205)  
  slategray[*] rgb(112, 128, 144)  
  slategrey[*] rgb(112, 128, 144)  
  snow rgb(255, 250, 250)  
  springgreen rgb(  0, 255, 127)  
  steelblue rgb( 70, 130, 180)  
  tan rgb(210, 180, 140)  
  thistle rgb(216, 191, 216)  
  tomato rgb(255,  99,  71)  
  turquoise rgb( 64, 224, 208)  
  violet rgb(238, 130, 238)  
  wheat rgb(245, 222, 179)  
  whitesmoke rgb(245, 245, 245)  
  yellowgreen rgb(154, 205,  50)  

[*] The ‘e’-grey colors (with an e) (grey, darkgrey, darkslategrey, dimgrey, lightgrey, lightslategrey) are only supported since IE 8.0. IE 3 to IE 6 only support the ‘a’ variants: gray, darkgray, darkslategray, dimgray, lightgray, lightslategray.

See the Mozilla Documentation Project CSS Color page for more information.

HSLa colors

This needs to be used behind a modernizr test, it is not fully supported

Using HSLa is similar to RGBa in that you declare three values determining the color and then a fourth value for its transparency level. You can read more about browser support below, but it’s basically any browser that supports rgba supports hsla too.

#some-element {
   background-color: hsla(170, 50%, 45%, 1);
}
  • Hue Think of a color wheel. Around 0o and 360o are reds 120o are greens, 240o are blues. Use anything in between 0-360. Values above and below will be modulus 360
  • Saturation 0% is grayscale. 100% is fully saturated (full color)
  • Lightness 0% is completely dark (black). 100% is completely light (white). 50% is average lightness
  • alpha Opacity/Transparency value. 0 is fully transparent. 1 is fully opaque. 0.5 is 50% transparent.

Using SASS smartly

This is a continuation to my earlier post on SASS: SASS, SCSS and Modular Design. This presents new ideas and more advanced mixins, tools and concepts.

During development, watch what you’re doing

An additional parameter to the SASS command line tool allows you to watch files for changes and then automatically recompile them. This reduces the commands that you have to type at the terminal and it makes it easier to track changes. The command I use to watch the folder containing my SASS is:

sass \
  --scss \ # tells SASS that we're using the SCSS syntax
  --update \ # compile the files to CSS
  --style expanded \ # use the expanded SASS/SCSS syntax
  --line-numbers \ # prints originating SCSS file and line number for debugging
  --line-comments \ 
  --watch \ # Makes sass update on change
  scss/:css/ # source SCSS and target CSS folders

I usually create a shell script with this command so I don’t have to type it over and over.

Start small, build big

Partials, mixins and SASS extension mechanism give you the ability to create small building blocks that you can then use as needed in larger blocks and applications.

Extension and placeholder selectors

(http://12devs.co.uk/articles/handy-advanced-sass/)

Your SASS stylesheets will grow quickly if you use the @include directive; this is unavoidable since it duplicates all the instructions that it is adding to each rule it is used in. Fortunately SASS gives us @extend which will group declarations together instead of copying from one into another

A Basic Example

.inline-block {
  display: inline-block; 
  vertical-align: baseline; 
  *display: inline; 
  *vertical-align: auto;
  *zoom: 1;
}

.btn {
  @extend .inline-block;
  padding: 0.5em 1em;
}

.foo {
  @extend .inline-block;
  color: red;
}

CSS Output

// selectors are grouped
.inline-block, .btn, .foo {
  display: inline-block; 
  vertical-align: baseline; 
  *display: inline; 
  *vertical-align: auto;
  *zoom: 1;
}

.btn {
  padding: 0.5em 1em;
}

.foo {
  color: red;
}

Instead of adding the styles from .inline-block into both of our .btn and .foo declarations, the extend directive grouped our classes alongside the .inline-block class, reducing duplication significantly.

Placeholder Selectors

In the previous example our .inline-block class was also generated amongst the group of declarations and had we not even extended the .inline-block class, it still would have been output in our final CSS file. This is not ideal and is exactly where Placeholder Selectors come in handy.

Any CSS declared within a placeholder will not be compiled in your final CSS file unless it has been extended (think abstract classes in languages like Java or C++). It also means that other developers cannot inadvertently hook onto these styles in their markup. It may be a better and less troublesome method to extend classes to extend placeholders instead.

Placeholders are declared and extended exactly the same way as classes except the dot is replaced with a percentage symbol:

%inline-block {
  display: inline-block; 
  vertical-align: baseline; 
  *display: inline; 
  *vertical-align: auto;
  *zoom: 1;
}

.btn {
  @extend %inline-block;
  padding: 0.5em 1em;
}

.foo {
  @extend %inline-block;
  color: red;
}

CSS Output

.btn, .foo {
  display: inline-block; 
  vertical-align: baseline; 
  *display: inline; 
  *vertical-align: auto;
  *zoom: 1;
}

.btn {
  padding: 0.5em 1em;
}

.foo {
  color: red;
}

Now, the selectors are grouped as before but without an .inline-block class in the group. Much better 🙂

Partials are your friends

I hate having to search a large file for a given piece of code. Thankfully I don’t have to work with just one file… that’s what partials are for. Similar to Ruby on Rails partials, partials in SASS allow you to break large stylesheets based on function, use or any other criteria you want to use.

In order to create a partial just follow your standard development practices and save the file with an underscore as the first character in the file name. To create a partial that contains your base variables, you can name it _base-vars.scss. and reference it from all other files using the variables or functionality implemented in the partial.

As discussed elsewhere we can then structure our content with only the partials that we need for a given project. Furthermore we can experiment with ideas and concepts without having to worry about polluting our production code until the experiment is ready to go

Build variable libraries

Rather than repeat values over and over in many different stylesheets, we can build a library with values of things that are not likely to change or that will repeat themselves multiple times at different levels.

Note that this is a suggested way of organizing SASS content, not the only one. Use whatever makes the most sense for you and your project

Take the following snippet of a variable library:

# Link Colors
$magenta: #FF00FF
$blue: #000011
$dark_grey: #eee

# Border Properties
# We have 3 different thickness 
$thin: 1 * 1px
$medium: $thin * 2px
$thick: $thin * 3px
$base_border_style: solid    
$border_color: $dark_grey

We can then use all the properties we defined in another round of mixins that will be used directly on our SASS stylesheets, like so:

@mixin solid-thin {
  border-width: $thin;
  border-style: solid;
  border-color: $border_color 
}

And finally use our solid-thin mixing wherever we need the same characteristics, like so:

blockquote {
  @include solid-thin;
}

So now if we ever need to change the vaues for any of our solid thin mixin, we only need to change them in one place and then recompile our SASS. The resulting CSS will pick the changes without having to worry if we got them everywhere… we know we did.

Keep your CSS DRY

A concept that is popular in nthe development community (I think it originated in Rails) is DRY, don’t repeat yourself. This is particularly dangerous in large development teams or large codebases.

Mixins with variable attribute values

(and default values when we need them)

Although this is not needed anymore (I believe all browsers now support border radius natively) this was the first time when I realized the power of mixing and how far we can really take them.

$radius: 5px !default;

@mixin border-radius($radius) {
  -webkit-border-radius: $radius;
     -moz-border-radius: $radius;
      -ms-border-radius: $radius;
       -o-border-radius: $radius;
          border-radius: $radius;
}

The code snipet above sets a variable for the default radius (5 pixels) and uses it when we don’t provide a value. These two declarations are the same:

.case1 {
  @include border-radius();
}

.case1 {
  @include border-radius(5px);
}

Extending our SASS

Don’t reinvent the wheel

I’m all for learning with your own code but there are times when it’s worth taking a look at what other people have done. There are several SASS-based frameworks available. The two I refer to the most are:

Useful Mixins and Tools for your own code

Better mediaqueries using @content

Mediaqueries are a pain in the ass. So much repetition and so error prone makes for cranky developers and cranky developers make things not fun for people around them.

In addition to bubbling media queries, we can use the @content variable to feed in the content that is specific to that media query.

// breakpoints defined in settings
$break-medium:  31em !default;
$break-large:   60em !default;
$break-x-large: 75em !default;          

@mixin breakpoint($type, $fallback:false, $parent:true) {

  @if $type == medium {
    @media (min-width: $break-medium) {
      @content;
    }
  }

  @if $type == large {
    @media (min-width: $break-large) {
      @content;
    }
  } 

  @if $type == x-large {
    @media (min-width: $break-x-large) {
      @content;
    }
  }

  @if $fallback {
    @if $parent {
      .#{$fallback} & {
        @content;
      }
    } @else {
      .#{$fallback} {
        @content;
      }
    }
  }
}

here are some uses for the mixing above:

.features__item {
  width: 100%;
}

.foo {
  color: red;
}

.bar {
  color: green;

  // use inside a declaration
  @include breakpoint(medium) {
    color: red;
  }

  @include breakpoint(large, lt-ie9) {
    color: blue;
  }
}

// use outside any declarations
@include breakpoint(medium) {
  .features__item {
    width: 50%;
  }

  .foo {
    color: blue;
  }
}

// remember to tell the fallback that 
// it's not within a declaration
@include breakpoint(large, lt-ie9, false) {
  .features__item {
    width: 25%;
  }

  .foo {
    color: green;
  }
}

and the resulting CSS:

features__item {
  width: 100%;
}

.foo {
  color: red;
}

.bar {
  color: green;
}

@media (min-width: 31em) {
  .bar {
    color: red;
  }
}

@media (min-width: 60em) {
  .bar {
    color: blue;
  }
}

.lt-ie9 .bar {
  color: blue;
}

@media (min-width: 31em) {
  .features__item {
    width: 50%;
  }

  .foo {
    color: blue;
  }
}

@media (min-width: 60em) {
  .features__item {
    width: 25%;
  }

  .foo {
    color: green;
  }
}

.lt-ie9 .features__item {
  width: 25%;
}

.lt-ie9 .foo {
  color: green;
}

Clearfix

@mixin clearfix() {
    &:before,
    &:after {
        content: "";
        display: table;
    }
    &:after {
        clear: both;
    }
}

Normalize.css

Vertical Rhythm

From: http://codepen.io/sturobson/pen/jFKlJ

Vertical Rhythm is simply when a body of text is aligned to evenly spaced horizontal lines (think of your lined paper from grade school), making it more cohesive and easier to read.

@mixin font-size($size, $keyword: null, $line-height:$doc-line-height) {
// note that the numeric font-size is required to allow the line-height to be generated correctly.

// the addition of the $keyword has been borrowed from this technique - http://seesparkbox.com/foundry/scss_rem_mixin_now_with_a_better_fallback

  @if $keyword{ 
    font-size: $keyword; 
  }
  @else {
    font-size: 0px + $size;
    font-size: $size / $doc-font-size +rem;
  } 
  // because you have to include the font size as a number for the keyword you can still get the line-height 
    
  line-height: round($line-height / $size*10000) / 10000;
  margin-bottom: 0px + $line-height;
  margin-bottom: ($line-height / $doc-font-size)+rem ;
}

Calculate REM size

One of the issues I find with using em as my sizing unit is that the values don’t remain constant; they are relative to the parent element.

CSS3 introduced a new unit for sizing: rem.

The em unit is relative to the font-size of the parent, which causes the compounding issue. The rem unit is relative to the root—or the html—element. That means that we can define a single font size on the html element and define all rem units to be a percentage of that.

html { font-size: 62.5%; /* =12px*/ } 
body { font-size: 1.4rem; } /* =14px */
h1   { font-size: 2.4rem; } /* =24px */

I’m defining a base font-size of 62.5% to have the convenience of sizing rems in a way that is similar to using px.

Code and idea originally from Stubornella

@function calculateRem($size) {
  $remSize: $size / 16px;
  @return #{$remSize}rem;
}
@mixin fontSize($size) {
  font-size: $size; //Fallback in px
  font-size: calculateRem($size);
}
h1 {
  @include fontSize(32px);
}

Becomes:

h1 {
  font-size: 32px;
  font-size: 2rem;
}

Build a grid with SASS

Programming-like functions and commands.

Please note that the material discussed below is not meant for day to day coding but more for larger projects where you’re building the infrastructure for other people to use. Hide as much of these details from your developers 🙂

SASS also allows you to create program-like funtionality that we can later include into our SASS files and then into our CSS. Let’s take a look at the example below:

@function cp($target, $container) {
  @return ($target / $container) * 100%;
}

It removes the need to run the same calculation every time we need to calculate an element’s width in relation to a container. Say we have a div we call nav that we want to be 650px width in a container that is 1000px wide; the sass declaration for this element’s width will look like this:

#nav {
  width: calc-percent(650px, 1000px);
}

and will produce the following CSS:

#nav {
  width: 65%;
}

Other control mechanisms

@if

The @if directive takes a condition to evaluate and returns the nested styles if the condition is truthy (not false or null).

p {
  @if 1 + 1 == 2 { border: 1px solid;  }
  @if 5 < 3      { border: 2px dotted; }
  @if null       { border: 3px double; }
}

Since only the first condition is true, the rule is compiled to:

p {
  border: 1px solid; 
}

Specifying what to return if the condition is falsey can be done using the @else statement. If the @if statement fails, the @else if statements are tried in order until one succeeds or the @else is reached. For example in the following SASS rule:

$type: monster;
p {
  @if $type == ocean {
    color: blue;
  } @else if $type == matador {
    color: red;
  } @else if $type == monster {
    color: green;
  } @else {
    color: black;
  }
}

the first else statement is true, so the rule resuls in:

p {
  color: green; 
}

@for

The @for directive iterates over it’s contents a set number of times passing a variable whose value increases for each iteration. For instance, if you find yourself repeating similar CSS you can save yourself a lot of manual work by using a for loop.

The directive has two forms: @for $var from <start> through <end> and @for $var from <start> to <end>. For the form from … through, the range includes the values of and , but the form from … to runs up to but not including the value of .

$var can be any variable name, like $i; <start> and <end> are SassScript expressions that should return integers.

@for $i from 1 through 3 {
  .item-#{$i} { width: 2em * $i; }
}

is compiled to:

.item-1 {
  width: 2em; }
.item-2 
  width: 4em; }
.item-3 {
  width: 6em; }

If you use the @for $var from <start> to <end> the result will be different. Fore example:

$columns: 4;

@for $i from 1 to $columns {
  .cols-#{$i} {
    width: ((100 / $columns) * $i) * 1%;
  }
}

This will only iterate 3 times instead of 4 so would not generate our final .cols-4 declaration in the example above.

@each

The @each directive takes the item from the list and outputs styles with the listed values

$people: Aragorn, Legolas, Gimli, Gandalf;

@each $name in $people {
  .icon-#{$name} {
    background-image: url(/images/icons/#{$name}.png);
  }
} 

compiles to:

.icon-Aragorn {
  background-image: url(/images/icons/Aragorn.png);
}

.icon-Legolas {
  background-image: url(/images/icons/Legolas.png);
}

.icon-Gimli {
  background-image: url(/images/icons/Gimli.png);
}

.icon-Gandalf {
  background-image: url(/images/icons/Gandalf.png);
}

@while

The @while statement will continually iterate and output it’s nested styles until it’s condition evaluates to false. This can be used to achieve more complex looping than the @for statement is capable of. For example:

$column: 4;

@while $column > 0 {
  .cols-#{$column} {
    width: 10px * $column;
  }
  $column: $column - 2;
}

This will only loop twice as we are subtracting 2 from the $column variable in each iteration so when it attempts to loop a third time, $column will not be greater than 0 and the output would be:

.cols-4 {
  width: 40px;
}

.cols-2 {
  width: 20px;
}

HTML5 video captioning using VTT

Introducing VTT

WebVTT (Web Video Text Tracks), formerly known as WebSRT, is a W3C community proposal for synchronized video caption playback. It is a time-indexed file format and it is referenced by HTML5 video and audio elements.

As with many assistive technologies, it would be a mistake to assume that they are only meant as a way to provide for accessibility accomodations. We can enable captions when the ambient noise is too loud to listen to a recorded presentation, we can use chapters to navigate through a long lecture video just like DVD or Blue Ray movies.

Captions can also improve our movies’ discoverability. Google indexes the content of our captions. Both YouTube and Google search can report results based on the video captions available for a given file.

WebVTT files provide open captions, independent of the audio or video files they are attached to, they are not “hard coded” into pixels. This also means that creating VTT files requires nothing more than a text editor; although there are more specialized tools to create the captions.

Browser support

Based on Silvia Pfeiffer’s post to the VTT community group dated August, 2012, and updated with new information about Firefox, the following browsers support VTT tracks for video and audio:

Browser Version First Supported Format Supported Notes
Internet Explorer IE 10 Developer Preview 4 VTT and TTML
Google Chrome Version 18 VTT
  • Basic tutorial hosted at HTML5 Rocks
  • Based on Webkit’s implementation
Apple Safari Version 6 VTT
  • Based on Webkit’s implementation
Opera Since August, 2012 VTT
Firefox Nightly VTT
  • Tested with version 29.0a1 (12/14/2013)
  • Feature enabled by default
  • See the Mozilla Developer Documentation for more information
  • If the size of the video doesn’t match the size attributes of the video tag, the video will display on white/gray background

Polyfills and alternatives

I will use one of the many polyfils available for HTML5 Video Tracks. Playr seems to be the most feature complete polyfill for HTML5 video tracks. The downside is 2 more files (one CSS and one JavaScript) to download for the video page but until VTT is widely supported the extra files are worth the effort to create accessible content.

One way to ensure that we only load our polyfill if the browser doesn’t support tracks natively is to use Modernizr.load to conditionally load Playr’s CSS and JavaScript when the browser does not support HTML5 video tag natively.

Modernizr.load([
 {
    // test whether we support video
    test : Modernizr.video,
    // Load the corresponding assetts for the polyfill you want to use
    // in this case we are using the playr polyfill
    nope : ['playr.js', 'playr.css']
  },
])

The code below uses plain JavaScript to test if a browser supports HTML5 video by creating an empty video element and testing for the video’s canPlayType property. It will not load the code for a polyfill like the Modernizr example.

var canPlay = false;
  var h, plink, pscript;

  // Create an empty video element
  var v = document.createElement('video');
  // If the video can playType and can play MP4 video
  if(v.canPlayType) {
    // Set canPlay to true
    canPlay = true;
    // Display an alert telling them so
    alert('Can Play HTML5 video')
  }
  else {
    // Append Playr CSS and JS to the head of the page to
    // provide a fallback
    h = document.getElementsByTagName('head')[0];
    plink = document.createElement('link');
    plink.setAttribute('href', 'css/playr.css');
    plink.setAttribute('media', 'screen');
    h.appendChild(plink);
    pscript = document.createElement('script');
    pscript.setAttribute('src', 'js/playr.js');
    h.append('pscript');
  }

This is the simplest test for video support; a more elaborate version can include support for specific formats and write the <source> tags only for the supported formats. The example below makes the following assumptions:

  • You have encoded a video in all three formats (webm, mp4 and ogg)
  • You are testing for support for HTML5 video in general and specific formats
  • If HTML5 video is not supported you have a flash-based fallback solution
var canPlay = false;
  // Get the video by selecting the video tag
  var v = document.getElementsByTagName('video');
  // Optionally add video attribtues as needed
  // At a minimum set height, width and controls
  // as shown below
  v.setAttribute('height', '640');
  v.setAttribute('width', '480');
  v.setAttribute('control', 'control');

  // If the video can playType and can play MP4 video
  if (v.canPlayType && v.canPlayType('video/webm'; codecs="vp8, vorbis"').replace(/no/, '')){
    // append the appropriate source track
    var webm = v.appendChild(source);
    webm.setAttribute("source", "myvideo.webm");
    webm.setAttribute("type", "video/webm");
  }
  else if (v.canPlayType && v.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, '')){
    // append the appropriate source track
    var mp4 = v.appendChild(source);
    mp4.setAttribute("source", "myvideo.mp4");
    mp4.setAttribute("type", "'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'");
  }

Also note that we’re testing for specific audio and video codec combinations. WebM supports a single combination of video and audio codecs but MP4 supports multiple profiles, not all of which are supported in HTML5 video. See http://mpeg.chiariglione.org/faq/what-are-different-profiles-supported-mpeg-4-video for an introduction to the different profiles supported by MPEG4.

Players and Polyfills

Playr is by no means the only polyfil or the only player that supports VTT. It is the one that I found the most feature complete for what I needed. The selection below represents a set of players and polyfills available.

Different types of VTT tracks and their structures

Captioning Tracks

Captioning is text that appears on a video, which contains dialogue and audio cues such as music or sound effects that occur off-screen. The purpose of captioning is to make video content accessible to those who are deaf or hard of hearing, and for other situations in which the audio cannot be heard due to noise or a need for silence.

Captions can be either open (always visible, aka “burned in”) or closed, but closed is more common because it lets each viewer decide whether they want the captions to be turned on or off.

From http://www.cpcweb.com/faq/

The simplest and most often used type of text track, captions provide alternative text content for people with visual dissabilities, for people who choose to play the video without audio, and others.

Depending on the player you may have open captions, where the captions are always visible on screen, or closed captions where you have to manually activate the display of captions; Either open or closed, the captions are independent of the content they are attached to.

WEBVTT (1)

railroad (2)
00:00:10.000 --> 00:00:12.500 (3) [Optional Settings] (4)
Left uninspired by the crust of railroad earth (5)

manuscript
00:00:13.200 --> 00:00:16.900
that touched the lead to the pages of your manuscript.

Explanation of the cue above:

  1. WEBVTT must be the first item on the file, on the first line and in a line of its own. Optionally there may be lines of metadata. This section must be followed by a blank line
  2. The name of the cue. This is also optional
  3. Immediately below the name of the cue come the beginning and end time for the cue expressed in hours:minutes:seconds:milliseconds format. Hours, Minutes and Seconds must have 2 digits and be padded with zeros if necessary. Miliseconds must have 3 digits and be zero padded if not long enough
  4. Optional Cue Settings separated from the time one or more SPACE or TAB characters
  5. The text for the cue

Subtitles Tracks

Subtitle Tracks are similar to Caption Tracks but are not meant to address accessibility issues as Captions are. Subtitle tracks are used primarily to convey the dialogue in a language other than the one being spoken in the video. Take, for example a Japanese movie where the subtitles translate the content to English.

Subtitles are not expected to convey additional non-verbal cues. Once again, subtitles are only meant to provide a translation of the words being spoken although some delivery formats such as Blue Ray do not follow this recommendation.

What’s the difference between captions and subtitles?

The main difference is that subtitles usually only transcribe the spoken dialog, and are mainly aimed at people who are not hearing impaired, but lack fluency in the spoken language. Closed captions are aimed at the deaf and hearing impaired, who need additional non-verbal audio cues (such as “[GUN SHOT]” or “[SPOOKY MUSIC]”) to be transcribed in the text. Closed captions are also useful for situations in which video is being shown but the sound is muted or difficult to hear, such as for a noisy bar, convention floor, video signage & billboards, etc.

From http://www.cpcweb.com/faq/

Other than the content for each type of track, HTML5 video structures the track element the same way. In the example below, the only difference are the kind attributes for each track.

<!-- This is the captions track -->
<track kind="captions" lang="en" srclang="en" label="English" src="sintel.vtt" />
<!-- This is the subtitles track for Spanish -->
<track kind="subtitles" lang="es" srclang="es" label="Español" src="sintel-es.vtt" />

Chapter Tracks

Chapter tracks help you navigate through the video by associating certain “chapters” with time codes. This will let you navigate to different sections of your video using some sort of visual cue.

In the example below, chapter 1 is 10 second long and titled Introduction to HTML5.

WEBVTT (1)

Chapter 1 (2)
00:00:01.000 --> 00:00:10.000 (3)
Introduction to HTML5(4)

Chapter 2
00:00:10.001 --> 00:00:15.000
Introduction to HTML5

Explanation of the chapter above:

  1. WEBVTT must be the first item on the file, on the first line and in a line of its own. It must be followed by a blank line
  2. The name of the chapter
  3. Immediately below the name of the chapter come the beginning and end time expressed in hours:minutes:seconds:milliseconds format. Hours, Minutes and Seconds must have 2 digits and be padded with zeros if necessary. Miliseconds must have 3 digits and be zero padded if not long enough
  4. The title of the chapter

Description Tracks

Description tracks are used primarily as an assistive technology helper, these tracks will be read by assistive technology devices for people with visual disabilities (blind or low vission). The cues can be arbitrarily long as long as they don’t contain empty lines (they would signal the beginning of a new cue)

VTT - Description for Sintel trailer

Sintel's Search -- beginning of the search
00:00:01.000 --> 00:00:52.000
Woman walks up a mountain
Fights an unknown man
Smoking man (covering full frame) speaks
Little dragon flies towards the woman before a larger dragon snatches it and flies away. The woman screams trying to grab the smaller flying creature

Metadata Tracks

Metadata Tracks are used to convey any additional information (such as base64 encoded images, JSON, additional text or any additional text-based file format) the developer needs to include in the page based on time indexes. A web app can listen for cue events, extract the text of each cue as it fires, parse the data and then use the results to make DOM changes (or perform other JavaScript or CSS tasks) synchronised with media playback.

WEBVTT - Example metadata track containing JSON payload

multiCell
00:01:15.200 --> 00:02:18.800
{
"title": "Multi-celled organisms",
"description": "Multi-celled organisms have different types of cells that perform specialised functions.
  Most life that can be seen with the naked eye is multi-cellular. These organisms are though to have evolved around 1 billion years ago with plants, animals and fungi having independent evolutionary paths.",
"src": "multiCell.jpg",
"href": "http://en.wikipedia.org/wiki/Multicellular"
}

insects
00:02:18.800 --> 00:03:01.600
{
"title": "Insects",
"description": "Insects are the most diverse group of animals on the planet with estimates for the total
  number of current species range from two million to 50 million. The first insects appeared around
  400 million years ago, identifiable by a hard exoskeleton, three-part body, six legs, compound eyes
  and antennae.",
"src": "insects.jpg",
"href": "http://en.wikipedia.org/wiki/Insects"
}

We can then use Javascript to parse the track content and do something with the track’s content.

textTrack.oncuechange = function (){
  // "this" is a textTrack
  var cue = this.activeCues[0]; // assuming there is only one active cue
  var obj = JSON.parse(cue.text);
  // do something
}

Building the tracks

We can build our caption file using the text above as an example, and this is the most common way to caption a video for accessibility.

We can also build multiple caption tracks as well as a variety of other tracks. Most polyfills will support a subset of the full VTT specification, Playr, the polyfill I’ve selected for these examples, supports captions, descriptions and chapter tracks.

Getting the captions to work

Building the tracks

(built with information from http://demosthenes.info/blog/584/Creating-And-Validating-WebVTT-Subtitles)

There are no programs that support VTT as a native captioning format. However there are plenty of programs that will create SRT captions, which is very similar to VTT (we’ll discuss the differences later in this section).

Choose whatever tool will work best for you to generate the SRT file; then follow the instructions below to convert them to VTT files.

Converting SRT to VTT

Due to their close relationship, conversion from .srt into .vtt is very simple. A typical .srt file will look something like this:

1
00:01:21,700 --> 00:01:24,675
Life on the road is something
I was raised to embrace.

The process is little more than a find-and-replace:

  • Add WEBVTT to the first line of the file
  • Convert the comma before the millisecond mark in every timestamp to a decimal point
  • Add styling markup to the subtitle text if needed
    • Special characters must be escaped as in HTML (&amp;, &gt;, &lt;)
    • You can use CSS classes defined in your CSS file by using &gt;c.XXX&lt;
    • See the section Cue Payload Tags for more information about the specific tags you can use to style your content

The resulting VTT file will look like this:

WEBVTT

Life
01:21.700 --> 01:24.675
Life on the road is something
I was <i>raised</i> to embrace.

Save the file with a .vtt extension and link to it from a <track> element in your video.

Validating A VTT File

It is not hard to make mistakes when creating a VTT track fille. Fortunately there is an online validator to help with authoring.

VTT Validator

It is essentially a two step process:

  • Paste the text of your VTT file
  • Select the type of track you’re working on

The results will display automatically.

Optional Cue Settings

Cues can also be styled and moved around the screen relative to the borders of the video. The table below summarizes the settings avalable for cues.

Vertical Alignment

Name: vertical

Values: rl (right to left) – lr (left to right)

What is used for: Vertical text alignment for languages like Japanese that can be read from top to bottom

Example: vertical:lr (makes the cue display vertically from left to right)

Line Placement / Top Alignment

Name: line

Value [-][0 or larger] (negative or possitive number) or [0-100]%

What is used for: Absolute references to a particular line number the cue is to be displayed on.
What is used for: Percentage value indicating the position relative to the top of the frame (when using percentages)

  • Line numbers are based on the size of the first line of the cue.
  • A negative number counts from the bottom of the frame

  • Positive numbers from the top

Cue Box Size

Name: size

Value: [0-100]%

What it’s used for: Indicates the size of the cue box. The value is given as a percentage of the width of the frame

Text Align

Name: align

Values: start | middle | end

What it’s used for: Specifies the alignment of the text within the cue. The keywords are relative to the text direction and are the same alignment keywords used in SVG

The alignment values are similar to those used in SVG. For users of CSS that uses a different terminology, the equivalency is:

  • Start alignment: The cue box’s left side (for horizontal cues) or top side (otherwise) is aligned at the text position.
  • Middle alignment: The cue box is centered at the text position.
  • End alignment: The cue box’s right side (for horizontal cues) or bottom side (otherwise) is aligned at the text position.

Note: if no cue settings are set, the positioning default to the middle, at the bottom of the frame.

Cue positioning

Name: position

Value [0-100]%

What is used for:
Percentage value indicating the horizontal alignment relative to the edge of the frame where the text begins (e.g. the left edge in English)
The value is dependent on the alignment of the cue:
* For left aligned or start aligned cues: 0%.
* For middle aligned cues: 50%.
* For right aligned or end aligned cues: 100%.

Note: Since the default value of the text track cue text alignment is middle, if there is no text track cue text alignment setting for a cue, the text track cue text position defaults to 50%.

Note: Even for horizontal cues with right-to-left paragraph direction text, the cue box is positioned from the left edge of the video frame. This allows defining a rendering space template which can be filled with either left-to-right or right-to-left paragraph direction text. If you define such a cue box template with start or end aligned text, make sure to control its size unless you want text to flip from one side of the video frame to the other.

Cue Payload Tags

These are additional tracks that will allow you to customize the appearance of your tracks. ** You cannot use payload tags with chapter tracks**

Timestamp Tags (Karaoke Style and Paint On Caption Text)

Using timestamp tags can build Karaoke Style tracks. You build the track by inserting the correct time stamp where you want the text to change, subject to the following restrictions:

  • The timestamp must be greater that the cue’s start timestamp, greater than any previous timestamp in the cue payload, and less than the cue’s end timestamp.
VTT - Example Karaoke Style Track

1
00:16.500 --> 00:18.500
When the moon <00:17.500>hits your eye

2
00:00:18.500 --> 00:00:20.500
Like a <00:19.000>big-a <00:19.500>pizza <00:20.000>pie

3
00:00:20.500 --> 00:00:21.500
That's <00:00:21.000>amore

In the example above:

  • The active text is the text between the timestamp and the next timestamp or to the end of the payload if there is not another timestamp in the payload.
  • Any text before the active text in the payload is previous text .
  • Any text beyond the active text is future text. We can use the previous and future tracks to create the Kraoke experience.

A possible CSS rule to style the content looks like this.


::cue:past {
  color:yellow
}

::cue:future {
  text-shadow: black 0 0 1px;
}

Timestamp tags can also be used for Paint On captions, which placed independently from each other and don’t erase what was already on the screen. They are written one letter at a time and they appear to ‘paint on’ the screen.

Speaker Semantics

You can use a combination of cue positioning and specific markup on individual cues to further emphazise who is speaking in a given caption or subtitle where appropriate.

WEBVTT - Sintel Caption File With Speaker Semantics

Sage
00:00:12.000 --> 00:00:15.000 A:middle T:10%
<v.gatekeeper>What brings you to the land
of the gatekeepers?

Searching
00:00:18.500 --> 00:00:20.500 A:middle T:80%
<v.sintel>I'm searching for someone.

We can style the speaker semantic classes using CSS. For example we can add a different color for each speaker, something like the example below:

video::cue(v.gatekeeper) {
  color:lime;
}

video::cue(v.sintel) {
  color: #ff00ff;
}
Addtional Style tags

The following tags require opening and closing tags.

Class tag

Style the contained text using a CSS class.

Cue 14 - Class tag example
<c.classname>text</c>

Italics tag

Italicize the contained text.

Example 15 - Italics tag
<i>text</i>

Bold tag

Bold the contained text.

Example 16 - Bold tag
<b>text</b>

Underline tag

Underline the contained text.

Example 17 - Underline tag
<u>text</u>

Ruby tag / Ruby text tag

Used together to display ruby characters (i.e. small annotative characters above other characters). Ruby annotations are primarily used in languages with logographic alphabets (Japanese, Chinese, Korean) where a single character may represent a complete word and where the meaning of the character may not be familiar to the reader.

Ruby characters are small, annotative glosses that can be placed above or to the right of a Chinese character when writing languages with logographic characters such as Chinese or Japanese to show the pronunciation. Typically called just ruby or rubi, such annotations are used as pronunciation guides for characters that are likely to be unfamiliar to the reader.

From Wikipedia

Example 18 - Ruby tag and Ruby text tag
<ruby>WWW<rt>World Wide Web</rt>oui<rt>yes</rt></ruby>

Adding the tracks to the video

Either in a supported browser or using one of the polyfills available (like we’ve chosen to do with Playr) we add <track> elements, one for each language of captions that we make available.

There is one non-standard attribute we will add to the video to make it work with Playr. The code below shows what a video track looks with associated an associated caption track for English.

<!DOCTYPE html>
<html>
<head>
  <title>Sample Captioned Video</title>
  <script src="playr.js"></script>
  <link rel="stylesheet" href="playr.js"></head>
</head>
<body>
<video
  id="myvideo"
  controls="controls"
  class="playr_video"
  width="640" height="480"
  poster="http://media.w3.org/2010/05/sintel/poster.png"
>
<!--
  These are the three sources. This should cover most of our
  deployed player base
-->
<source src="//media.w3.org/2010/05/sintel/trailer.mp4" type="video/mp4" />
<source src="//media.w3.org/2010/05/sintel/trailer.webm" type="video/webm" />
<source src="//media.w3.org/2010/05/sintel/trailer.ogv" type="video/ogg" />
<!--
  This is the captions track
-->
<track kind="captions" lang="en" srclang="en" label="English" src="sintel.vtt" />
</video>
</body>
</html>

The working example is located at http://labs.rivendellweb.net/vtt-demo/basic.html and an example without a polyfill (meant to test native browser support) is located at http://labs.rivendellweb.net/vtt-demo/basic-plain.html

The same example without polyfill support and supporting captions in English and Spanish with the English caption being the default. The default attribute will also display the captions automatically

<!DOCTYPE html>
<html>
<head>
  <title>Sample Captioned Video</title>
</head>
</head>
<body>
<video
  id="myvideo"
  controls="controls"
  width="640" height="480"
  poster="http://media.w3.org/2010/05/sintel/poster.png"
>
<!--
  These are the three sources. This should cover most of our
  deployed player base
-->
<source src="//media.w3.org/2010/05/sintel/trailer.mp4" type="video/mp4" />
<source src="//media.w3.org/2010/05/sintel/trailer.webm" type="video/webm" />
<source src="//media.w3.org/2010/05/sintel/trailer.ogv" type="video/ogg" />
<!--
  This is the captions track
-->
<track kind="captions" lang="en" srclang="en" label="English" src="sintel-en.vtt" default />
<track kind="captions" lang="es" srclang="es" label="Spanish" src="sintel-es.vtt" />
</video>
</body>
</html>

The final example contains multiple caption tracks, subtitles in Spanish and descriptions for the video.

<!DOCTYPE html>
<html>
<head>
  <title>Sample Captioned Video</title>
</head>
</head>
<body>
<video
  id="myvideo"
  controls="controls"
  width="640" height="480"
  poster="http://media.w3.org/2010/05/sintel/poster.png"
>
<!--
  These are the three sources. This should cover most of our
  deployed player base
-->
<source src="//media.w3.org/2010/05/sintel/trailer.mp4" type="video/mp4" />
<source src="//media.w3.org/2010/05/sintel/trailer.webm" type="video/webm" />
<source src="//media.w3.org/2010/05/sintel/trailer.ogv" type="video/ogg" />
<!--
  These are the captions track
-->
<track kind="captions" lang="en" srclang="en" label="English" src="sintel-en.vtt" default />
<track kind="captions" lang="es" srclang="es" label="Spanish" src="sintel-es.vtt" />
<track kind="captions" lang="de" srclang="de" label="Spanish" src="sintel-de.vtt" /></video>
<!--
  This is the subtitles track
-->
<track kind="subtitles" lang="es" srclang="es" label="Subtitulos en Español" src="sintel-es-subtitles.vtt" />
<!--
  These are the description tracks
-->
<track kind="captions" lang="en" srclang="en" label="English" src="sintel-en.vtt" default />
</body>
</html>

Text tracks and audio

Text tracks are not limited to working with just video. They work just the same with audio. The example below (taken from http://mattcrouch.net/experiments/music-sync/) provides synchronized captions to an audio track.

Using jQuery, an extract of the audio for the Sintel video and the same captions that we used for the video examples, we change the cues programatically using the video API to display the cues at the matching time.

As you can see, description tracks would be particularly useful in this case as they would provide a more complete context to the audio.

jQuery(document).ready(function() {
    // Step below is optional. I don't like taking
    // the option from the user and autoplay the video
    $('audio').trigger("play");
    var audio = document.querySelector("audio");
    // log the name of the track we're working with
    console.log(audio.textTracks[0]);

    audio.textTracks[0].oncuechange = function (){
      $("#output").html(""); // Clear the content of our output region
        if(this.activeCues !== null) {
          for(var i=0;i");
            }
          }
        }
      }
    });

Additional Tutorials And Tools