Creating printable content from the web

Creating printable content from the web

All the stylesheets are written in SCSS and converted to CSS through the build process.

In early 2015 I played with the idea of creating a custom XML vocabulary and the associated style sheets (XSLT, CSS and CSS Paged Media) to convert it to HTML, EPUB and PDF. It worked, the files were fairly easy to work with (at least for me who created them) and it was a good way to learn how to write XML schemas, XSLT style sheets and hone my skills with CSS. The repository is available

While it works the project relies on a custom XML vocabulary and it would take some work to convert to use (x)HTML5. I think I would start with the CSS stylesheet, change it to suit the needs of shorter content, integrate the PDF creation system to the existing Gulp build system and host the PDF content in a third party space like Amazon S3.

Differences with @print media

As far as I understand the differences is that @print media type was designed to make content printable but not to create a printed version of the content we’re working with but it can be printed directly from the browser.

In an ideal world Paged Media would be fully supported by all browsers and some of the work we’re doing in this project would be unnecessary but it is not an ideal world.

In this world we create pages for each type of content we want and associate portions of HTML to each of the page types we created. We can then add additional material as needed. We then use a third party tool (all commercial unfortunately) to process the HTML with a the paged media style sheet we just created and produce PDF or other formats depending on the formatter

For a model to follow when using the stylesheet check Oreilly’s HTMLBook specification

A good example of a book created with this specification and edited with Atlas is Lea Verou’s CSS Secrets

Creating the paged media work

We’ll break this process into three parts:

  1. Creating the additional paged media style sheet
  2. Modify the HTML template and add additional HTML to our markup
  3. Create additional Gulp tasks to automate the process

Creating the additional paged media style sheet

This stylesheets sets up a printed stylesheet with a basic set of parameters. The stylesheet works as is but it’s also meant as a starting point for printed media work and you can certainly refine it based on your needs.

The style sheet will control the print layout but not any other attribute controlled by CSS. In this case we’ve taken care of this in the stylesheet associated with the template, otherwise you’ll have to provide your own styles.

We begin the process by defining our standard page as an 8.5 x 11 inch page (US Letter) and give it default margins of 1 inch. We also define a footnote at-rule to increase the count on each page.

Next we define add defaults attributes for the book defined as body[data-type="book"].

In this element we define the default text color. Paged media agents (at least PrinceXML) supports CMYK colors so we use that format for opaque black. If the rendering engine doesn’t support CMYK we fallback to RGBA for the same color.

If you’re using Pantone colors you can use this handy converter to get the fallback RGBA color.

Lastly we define the default hyphenation

The HTML version of the document centers the .container element and makes it 48em (768 pixels) wide. For printed media we want to work with the full width of the document so we remove the centering and make the width 100%. It will not affect the HTML version of the document because it’s in a separate style sheet that will never be called together with the main.scss styles.

/* DEFINE THE DEFAULT PAGE */
@page {
  size: 8.5in 11in;
  margin: 1in;
  /* Footnote related attributes */
  @footnote {
    counter-increment: footnote;
    float: bottom;
    column-span: all;
    height: auto;
    }
  }

/* Default definitions for the book*/
body[data-type='book'] {
  color: cmyk(0%,0%,100%,100%);
  color: rgba(0, 0 ,0 ,1);
  hyphens: auto;
}

.container {
  width: 100%;
}

This is a small block to define how we’ll in handle increasing the page number counter.

The first part of the book and the first article will reset the page number counter (page) because we would normally have front matter using a different numbering system and we don’t want that number to carry over to the beginning of our content.

THe second selector in this block will not reset counters when a article child of body is followed by a part. We have to be explicit to use this rule because, otherwise, it would trigger the :first-of-type rule immediately above.

/* PAGE COUNTERS */
body[data-type='book'] > div[data-type='part']:first-of-type,
body[data-type='book'] > section[data-type='article']:first-of-type { 
  counter-reset: page;
  counter-reset: footnote;
}
body[data-type='book'] >  section[data-type='article'] + div[data-type='part'] { 
   counter-reset: none; }

Other than book (defined as an attribute of body) and part (defined as attribute of div elements) the rest of our book is defined in section elements. This is a two step process.

In the first step (shown below) we associate a given type (data-type) with a page pseudo element we’ll define later in the stylesheet. In this block we also define page specific attributes for each type of page. Most of the pages break before (page-break-before) and after (page-break-after). THe only additional element that I’ve placed in this block is the dotted leader for the table of content’s page number.

section[data-type='toc'] nav ol li a:after will place a dotted leader followed by the target content’s page number. Because we want this to work only on the table of contents we have to be too specific. In most other circumstances I may shorten the selector.

To create additional content type, create a new definition here and a @page definition later in the document.

/* MATCH THE PARTS OF OUR BOOK FILE TO EACH OF OUR PAGES DEFINED LATER */
/* Title Page*/
section[data-type='titlepage'] { 
  page: titlepage;
  page-break-before: always;
  page-break-after: always;
}

/* Copyright page */
section[data-type='copyright'] { 
  page: copyright; 
  page-break-before: always;
  page-break-after: always;
}

/* Dedication */
section[data-type='dedication'] {
  page: dedication;
  page-break-before: always;
  page-break-after: always;
}

/* TOC */
section[data-type='toc'] {
  page: toc;
  page-break-before: always;
  page-break-after: always;
}
/* Leader for toc page */
section[data-type='toc'] nav ol li a:after {
  content: leader(dotted) ' ' target-counter(attr(href, url), page);;
}

/* Foreword  */
section[data-type='foreword'] { page: foreword; }

/* Preface*/
section[data-type='preface'] { page: preface; }

/* Part */
div[data-type='part'] { page: part; }

/* Chapter */
section[data-type='article'] {
  page: article;
  page-break-before: always;
}

/* Appendix */
section[data-type='appendix'] {
  page: appendix;
  page-break-before: always;
}

/* Glossary*/
section[data-type='glossary'] { page: glossary; }

/* Bibliography */
section[data-type='bibliography'] { page: bibliography; }

/* Index */
section[data-type='index'] { page: index; }

/* Colophon */
section[data-type='colophon'] { page: colophon; }

These are the page definitions. We have two options, defining both left and right versions of the page (toc:right and toc:left) for content that changes between both sides or create a single page template (toc) that will apply equally to left and right sides.

It is here where we set specific attributes for the different types of pages we’re working with. For example, the front-matter pages are formatted with lower case roman numerals where the main body uses arabic numerals (this is another reason why we reset the counter on first part or article).

/* Comon Front Mater Page Numbering in lowercase ROMAN numerals*/
@page toc:right {
  @bottom-right-corner { content: counter(page, lower-roman);; }  
  @bottom-left-corner { content: normal; }
}

@page toc:left  {
  @bottom-left-corner { content: counter(page, lower-roman); }
  @bottom-right-corner { content: normal; }
}


@page foreword:right {
  @bottom-center { content: counter(page, lower-roman); }
  @bottom-left-corner { content: normal; }
}

@page foreword:left  {
  @bottom-left-corner { content: counter(page, lower-roman); }
  @bottom-right-corner { content: normal; }
}


@page preface:right {
  @bottom-center {content: counter(page, lower-roman);}
  @bottom-right-corner { content: normal; }
  @bottom-left-corner { content: normal; }
}

@page preface:left  {
  @bottom-center {content: counter(page, lower-roman);}
  @bottom-right-corner { content: normal; }
  @bottom-left-corner { content: normal; }
}

/* Common Content Page Numbering  in Arabic numerals 1... 199 */
@page titlepage{ 
/* Need this to clean up page numbers in titlepage in Prince*/
  margin-top: 18em;
  @bottom-right-corner { content: normal; }
  @bottom-left-corner { content: normal; }
}

@page dedication { 
/* Need this to clean up page numbers in titlepage in Prince*/
  page-break-before: always;
  margin-top: 18em;
  @bottom-right-corner { content: normal; }
  @bottom-left-corner { content: normal; }

}

@page article {
  @bottom-center {
    vertical-align: middle;
    text-align: center;
    content: element(heading);
  }
}

@page article:blank { /* Need this to clean up page numbers in titlepage in Prince*/
  @top-center { content: "This page is intentionally left blank"; }
  @bottom-left-corner { content: normal;}
  @bottom-right-corner {content:normal;}
}

@page article:right  {
  @bottom-right-corner { content: counter(page); }
  @bottom-left-corner { content: normal; }
}

@page article:left {
  @bottom-left-corner { content: counter(page); }
  @bottom-right-corner { content: normal; }
}

@page appendix:right  {
  @bottom-right-corner { content: counter(page); }
  @bottom-left-corner { content: normal; }
}

@page appendix:left {
  @bottom-left-corner { content: counter(page); }
  @bottom-right-corner { content: normal; }
}

@page glossary:right {
  @bottom-right-corner { content: counter(page); }
  @bottom-left-corner { content: normal; }
}

@page glossary:left {
  @bottom-left-corner { content: counter(page); }
  @bottom-right-corner { content: normal; }
}

@page bibliography:right  {
  @bottom-right-corner { content: counter(page); }
  @bottom-left-corner { content: normal; }
}

@page bibliography:left {
  @bottom-left-corner { content: counter(page); }
  @bottom-right-corner { content: normal; }
}

@page index:right  {
  @bottom-right-corner { content: counter(page); }
  @bottom-left-corner { content: normal; }
}

@page index:left {
  @bottom-left-corner { content: counter(page); }
  @bottom-right-corner { content: normal; }
}

To create a running footer we’ll use the running() value to use the element represented with the rh to do the following:

  • Take it out of the regular flow of the document
  • Use it as the text of the running header
  • Make the text italic and center it
p.rh {
  position: running(heading);
  text-align: center;
  font-style: italic;
}

Footnotes are a little more complicated than most of what we’ve seen so far

Footnote terminology as it relates to generated page media

Now that we’ve the terminology let’s dive into specifics

footnote element (span.footnote)
The element containing the content of the footnote, which will be removed from the flow and displayed as a footnote.
footnote marker (also known as footnote number) (::footnote-marker)
A number or symbol adjacent to the footnote body, identifying the particular footnote. The footnote marker should use the same number or symbol as the corresponding footnote call, although the marker may contain additional punctuation (the content of the ::fotnote-marker::after selector).
footnote body (content inside span.footnote)
The footnote marker is placed before the footnote element, and together they represent the footnote body.
footnote call (also known as footnote reference)
A number or symbol, found in the main text, which points to the footnote body. The default is to use Arabic numbers but it can be customized to use other symbols.
footnote area
The page area used to display footnotes. Usually located at the bottom of a page
footnote rule (also known as footnote separator)
A horizontal rule is often used to separate the footnote area from the rest of the page. The separator (and the entire footnote area) cannot be rendered on a page with no footnotes.

The footnote element will be removed from the flow of text and replaced by the footnote call symbol.

The footnote counter number increases.

The footnote body is placed in the footnote area in document order

/* Footnotes */
span.footnote {
  float: footnote;
}

::footnote-marker {
  content: counter(footnote);
  list-style-position: inside;
}

::footnote-marker::after {
  content: '. ';
}

::footnote-call {
  content: counter(footnote);
  vertical-align: super;
  font-size: 65%;
}

Creating cross references is as simple as creating an internal link with the xref class like this: <a href="#target" class="xref". The style sheet will then append the page where the reference is located using the target-counter() function.

/* XReferences */
a.xref[href]::after {
    content: ' [See page ' target-counter(attr(href), page); ']'
}

The last section of the style sheet is the PDF bookmarks.

The bookmarks use heading tags (h1 through h6) as the indicators for where they will land and what content to display as the label for the bookmark. The first three levels of headings will also be open so you can see what the content is. Since I seldom use headings beyond h3 I don’t think it’s necessary to show them; however they are available in case I need them for a specific project.

We also use vendor prefixes for the bookmark attributes. Even though bookmarks are part of the CSS Generated Content for Paged Media Module vendors have impemented the feature under prefix (boo!)

<

div class=”message info”>

One enhancement I definitely want to do is to change the selector for the bookmarks. It currently only works for article content. I definitely want to make all headings (particularly those in front matter and bibliography sections) visible as well. It should be a matter of removing the attribute selector.

/* PDF BOOKMARKS */
section[data-type='article'] h1 {
  -ah-bookmark-level: 1;
  -ah-bookmark-state: open;
  -ah-bookmark-label: content();
  prince-bookmark-level: 1;
  prince-bookmark-state: open;
  prince-bookmark-label: content();
}

section[data-type='article'] h2 {
  -ah-bookmark-level: 2;
  -ah-bookmark-state: open;
  -ah-bookmark-label: content();
  prince-bookmark-level: 2;
  prince-bookmark-state: open;
  prince-bookmark-label: content();
}

section[data-type='article'] h3 {
  -ah-bookmark-level: 3;
  -ah-bookmark-state: open;
  -ah-bookmark-label: content();
  prince-bookmark-level: 3;
  prince-bookmark-state: open;
  prince-bookmark-label: content();
}

section[data-type='article'] h4 {
  -ah-bookmark-level: 4;
  prince-bookmark-level: 4;
}

section[data-type='article'] h5 {
  -ah-bookmark-level: 5;
  prince-bookmark-level: 5;
}

section[data-type='article'] h6 {
  -ah-bookmark-level: 6;
  prince-bookmark-level: 6;
}

Making articles instead of books

The style sheet we discussed in the previous section works well enough for most documents. We can create book-like and article-like content. Rather than use the generic stylesheet I looked at what would it take to minimize the stylesheet for articles since I think articles better represent the content of the blog posts.

The style sheet is an abbreviated version of the stylesheet we discussed in the last section. I’ll point out the differences where necessary.

In the first section we do all our imports. We import variables and mixins, create our font-face declarations and import the rest of our SCSS rules.

// PRELIMINARY IMPORTS
//
// Import variables and mixins
@import 'partials/variables';
@import 'partials/mixins';

//fonts
@import 'partials/fonts';

/* Monospaced font for code samples */
@include font-declaration('notomono_regular', '../fonts/notomono-regular-webfont', normal, normal);
/* Regular font */
@include font-declaration('notosans_regular', '../fonts/notosans-regular-webfont', normal, normal);
/* Bold font */
@include font-declaration('notosans_bold', '../fonts/notosans-bold-webfont', 700 , normal);
/* Italic Font */
@include font-declaration('notosans_italics', '../fonts/notosans-italic-webfont', normal , italic);
/* bold-italic font */
@include font-declaration('notosans_bolditalic', '../fonts/notosans-bolditalic-webfont', 700 , italic);

// Import other partials
@import 'partials/pm/columns';
@import 'partials/pm/marginalia';
@import 'partials/pm/paragraphs';
@import 'partials/pm/lists';
@import 'partials/pm/images';
@import 'partials/pm/headings';

.container {
  width: 100%;
}

The following section is similar to the default page setup on the previous section. We set up the default page as a US Letter page with 1 inch margins. We then do the footnote-related work.

We associate the [data-type="article"] with our article @page instead of [data-type="book"]. We also assign the font we want to use to the article, including a fallback font and a generic font. We do the same thing with our [data-type="bibliography] and the bibliography @page. I kept bibliography because this is the only section I think I may use in my posts.

@page {
  size: 8.5in 11in;
  margin: 1in;
  /* Footnote related attributes */
  counter-reset: figure;
  @footnote {
    counter-increment: footnote;
    float: bottom;
    column-span: all;
    height: auto;
  }
}

body[data-type="article"] {
  font-family: notosans_regular, Verdana, sans-serif;
  font-size: 12pt;
  line-height: 1.375;
  // Widow and orphan control
  orphans: 4;
  widows: 2;
  // counter resets
  counter-reset: footnote;
  counter-reset: figures;
  // page related 
  page: article;
  page-break-before: always;
  page-break-after: always;

}

section[data-type='bibliography'] {
  font-family: notosans_regular, Verdana, sans-serif;
  font-size: 12pt;
  line-height: 1.375;
  // Widow and orphan control
  orphans: 4;
  widows: 2;

  page: bibliography;
  page-break-before: always;
  page-break-after: always;

  p {
    text-align: left;
    text-indent: 0 !important;
  }
}

For some reason, I believe markup related, Prince is not picking up the fonts correctly. It sometimes picks the web font (notosans_regular)and some times it picks the fallback (Verdana).

I’ve fixed the issue by specifying the font to use for most tags. It’s not a show stopper but it’s important to research the cause and I wonder if it’s the way the styles sheet is written.

h1, h2, h3, h4, h5, h6,
p, li {
  font-family: notosans_regular, Verdana, sans-serif;
}

pre, code {
  font-family: notomono_regular, Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
}

strong, b {
  font-family: notosans_bold, Verdana, sans-serif;
}

em, i {
  font-family: notosans_italic, Verdana, sans-serif;
}

When defining the pages I’ve included both page numbering and any generated text that I want to use beyond the content of the Markdown/HTML content.

@page article:left {
  @bottom-right {
    content: counter(page)
  }
}

@page article:right {
  @bottom-right {
    content: counter(page)
  }
}

@page article:blank {
  /* Need this to clean up page numbers in titlepage in Prince*/
  @top-center {
    content: "This page is intentionally left blank"
  }
  @bottom-left-corner {
    content: normal;
  }
  @bottom-right-corner {
    content: normal;
  }
}

@page bibliography:left {
  @bottom-right {
    content: counter(page)
  }
}

@page bibliography:right {
  @bottom-right {
    content: counter(page)
  }
}

The definition of footnotes and cross references remain the same as it did for our book-like content.

/* Footnotes */
span.footnote {
  float: footnote;
}

::footnote-marker {
  content: counter(footnote);
  list-style-position: inside;
}

::footnote-marker::after {
  content: '. ';
}

::footnote-call {
  content: counter(footnote);
  vertical-align: super;
  font-size: 65%;
}

/* XReferences */
a.xref[href]::after {
  content: ' [See page ' target-counter(attr(href), page) ']'
}

We use the same code for bookmarks. I only included the bookmarks for h1 to h3. The full stylesheet also include the code for h4 to h6.

/* PDF BOOKMARKS */
section[data-type='article'] h1 {
  -ah-bookmark-level: 1;
  -ah-bookmark-state: open;
  -ah-bookmark-label: content();
  prince-bookmark-level: 1;
  prince-bookmark-state: open;
  prince-bookmark-label: content();
}

section[data-type='article'] h2 {
  -ah-bookmark-level: 2;
  -ah-bookmark-state: open;
  -ah-bookmark-label: content();
  prince-bookmark-level: 2;
  prince-bookmark-state: open;
  prince-bookmark-label: content();
}

section[data-type='article'] h3 {
  -ah-bookmark-level: 3;
  -ah-bookmark-state: open;
  -ah-bookmark-label: content();
  prince-bookmark-level: 3;
  prince-bookmark-state: open;
  prince-bookmark-label: content();
}

The last block is styles that do not affect the page layout. The only style that is interesting is the automatic figure numbering that we do for the figcaption element. We use the figures counter and increase it for every figure element in the page. The figcaption element then takes that counter attribute and adds text around it to create the counter that will be prepended to the image.

small,
.font-small {
  font-size: .833em;
}

.justified {
  text-align: justify;
}

pre {
  background-color: #ddd;
  padding: 1em;
  overflow-wrap: break-word;
  white-space: pre-wrap;
  word-wrap: break-word;
}

.footnote {
  font-family: notosans_regular, verdana, sans-serif ;
}

figure {
  counter-increment: figures;
  img {
    width: min-content;
  }

  figcaption::before {
    content: 'Fig. ' counter(figures) ' - ';
    font-size: 75%;
  }

  figcaption {
    font-size: 75%;
    max-width: min-content;
    color: #ddd;
  }
}

/* Informational messages */
.message {
  border: 1px solid black;
  border-radius: 10px;
  display: block;
  padding: .5em;
  margin: 1em 0;

  &.info {
    background-color: lightblue;
    font-weight: bold;
  }

  &.warning {
    background-color: lightyellow;
    font-weight: bold;
  }

  &.danger {
    background-color: indianred;
    font-weight: bold;
  }
}

Changes we need to make to our HTML document (or template)

As I was working on implementing the style sheets for printed media I realized a few things that would require changes on the HTML template. These things are:

  • The styles on main.css do not display properly in a printed style sheet. Haven’t figured out if it’s an issue with the way I set up the styles for web content
  • Prism styles are not fully supported in PrinceXML and entire selectors are ignored as a result
  • Still need a way to do syntax highlighting
  • I need to add an attribute (data-type="article") to the body tag
<html lang="en">
<head>
<!--<link rel="stylesheet" href="css/main.css">--> <!-- 1 -->
<!--<link rel="stylesheet" href="css/prism.css">--> <!-- 2 -->
<!-- <script async src="scripts/vendor/prism.js"></script> -->  <!-- 2 -->
<link rel="stylesheet" href="paged-media/highlight/styles/solarized-light.css"> <!-- 3 -->
<script src="paged-media/highlight/highlight.pack.js"></script> <!-- 3 -->      
<!--<script async src="scripts/vendor/fontfaceobserver.standalone.js"></script>-->
<!--<script async src="scripts/load-fonts.js"></script>-->  <!-- 4 -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1,maximum-scale=1">
<title></title>
</head>

<body data-type="article"> <!-- 5 -->
<div class="container">
    <%= contents %>
</div>

</body>
</html>

The new template performs the following tasks:

  1. Removes main.css and the styles in it
  2. Removes Prism style sheet and the Prism script tag
  3. Adds Highlight.js script and the solarized-light theme style sheet
  4. Removes fontfaceobserver.js and load-fonts.js scripts. Since we don’t have to worry about font loading timing out and I’m not sure if PrinceXML supports the way we observe font loading it’s better if we remove them altogether
  5. Adds data-type="article" as an attribute to the body tag. This will match with selectors on the paged media style sheet and make it work on PDF

The template is located in the template directory as template-pm.html

Additional Gulp tasks

Now that we know what we want to do and have the paged media template and style sheets we need to look at how to integrate them into the build process. Just to recap, we want to do the following:

  • Convert markdown into an HTML fragment
  • Insert the fragment into an HTML template customized for paged media output
  • Run a Paged Media Processor (PrinceXML) on the output
  • Copy the files to a specific directory for the fact that none of my tools will let me easily do it
  • Upload the PDF files to an Amazon S3 bucket

We’ve already got tasks for the first two items in our list. The first task, converting Markdown files to HTML fragments doesn’t need to change so we’ll leave it as is.

We’ll customize the task to complete the second item on the list and change the template file and destination for the custom HTML content. We still make this task depend on the Markdown converter task to make sure we have the newest content to work. The new template looks like this:

gulp.task('build-pm-template', ['markdown'], () =>{
  gulp.src('./src/html-content/*.html')
    .pipe($.wrap({src: './src/templates/template-pm.html'}))
    .pipe(gulp.dest('./src/pm-content'))
});

I originally thought about using gulp-shell to run the PrinceXML command that I run to dogfood the code I present in these posts. The command is shown below:

prince \
--verbose \
--input=html \
--javascript \
--style css/article-styles.css \
paged-media-revisit.html -o doc-name.pdf

Where you see a backslash treat it as a continuation character, the command and all the parameters could be written in a single line. Also note that we run this command from inside the src directory. We’ll see how this is different when we run the Gulp task

gulp-shell presented too many unknowns. Since it’s blacklisted by Gulp so I’m not 100% sure how well it would work. So I chose to use gulp-exec instead. I took the template from the plugin’s readme page and plugged in the data for the prince command.

This is a new plugin so we need to install it and save it as a development dependency. The command should look familiar:

npm install --save-dev gulp-exec 

then require the task at the top of the gulpfile.

var exec = require('gulp-exec');

The build-pdf task depends on build-pm-template to make sure we have the latest content to convert to PDF. We then run the same Prince command we used to test the content from the command line with one difference. The path to the style sheet needs to be modified because we are running Gulp from the root of the repository. The full path to the style sheet becomes src/css/article-styles.css.

gulp.task('build-pdf', ['build-pm-template'], () => {
  let options = {
    continueOnError: false, 
    pipeStdout: false, 
  };
  let reportOptions = {
    err: true, 
    stderr: true, 
    stdout: true 
  };
  return gulp.src('./src/pm-content/*.html')
    .pipe(exec('prince --verbose --input=html --javascript --style ./src/css/article-styles.css <%= file.path %>', options))
    .pipe(exec.reporter(reportOptions));
});

Because I can’t figure out how to move the output of build-pdf I’m creating a workaround task to copy the PDF content form pm-content to a dedicated pdf directory. It uses Gulp built-in methods. It takes the source as the list of files that we want to copy and pipes it to the destination directory we want to put the files in.

gulp.task('copy-pdf', ['build-pdf'], () => {
  gulp.src('src/pm-content/*.pdf')
    .pipe(gulp.dest('src/pdf'))
});

The last task I want to build is one to upload the PDF files to an Amazon S3 bucket. I will link to the bucket items from my blog or from other places where I want to use this information.

I’ve chosen to use gulp-s3-upload for the first iteration of the task. Whenever I use Amazon I struggle with how to configure the task with Amazon Web Services is how to configure the login credentials. I’ve managed to stay under the radar for a while and have yet to pay for AWS hosting. I want to keep it that way.

So let’s see if it works. First install the gulp-s3-upload plugin and save it to your package.json’s dev dependencies.

npm install --save-dev gulp-s3-upload

Requiring the plugin is unusual, to me, because you attach the configuration to the require statement. I’ve left the credentials blank here but they are in the gulpfile, for now.

var s3 = require('gulp-s3-upload')({
  accessKeyId: "enter yours",
  secretAccessKey: "enter yours"
})

The task itself is fairly simple. we take all the PDF files located in src/pm-content and pipe them through S3 to upload them to the blog-post-pdf bucket with a public-read ACL. If I understood the settings correctly this would allow me to link to the files from external servers and not have to worry about CORS or any other types of restriction.

gulp.task('s3-upload', () => {
  gulp.src("./src/pm-content/*.pdf")
  .pipe(s3({
    Bucket: 'blog-post-pdf',
    ACL: 'public-read'
  }))
});

Another option I’ve been considering is moving all my externally stored files from AWS to Google Cloud and their storage system. This is totally experimental and may not be my long term solution; I’m presenting it here as an alternative to AWS.

For Google Cloud I’ve chosen to use gulp-gzip and gulp-gcloud-publish. I’ve chosen to gzip the files on upload to reduce the size of the payload being uploaded and the storage requirements… Google Cloud will unzip the files on download.

To install the plugins we follow the standard Node procedure. Make sure you save them as dependencies with a command like the one below

npm install --save-dev gulp-gcloud-publish gulp-gzip

The plugins takes all the PDF files in the specified directory, gzips them and then uploads them to Google Cloud Storage with the information provided. Because this requires a private key, it is essential that you keep the key referenced by keyFileName private. I’ve added the file to .gitignore and will not share than information. Please respect that and get your own account.

gulp.task('gcs-publish', () => {
  return gulp.src('src/pdf/*.pdf')
    .pipe(gzip()) // optional
    .pipe(gcPub({
      bucket: 'use your own',
      keyFilename: 'use your own',
      projectId: 'use your own',
      base: '/pdf',
      public: true
    }));
});

Whatever infrastructure you use is up to you. Most of it will depend on your existing infrastructure you use and whatever you’re comfortable with.

.gitignore

To make sure I don’t upload product files to Github I’ve created a .gitignore file that removes most of the generated files from the Git upload process.

If you use commercial fonts you definitely dont’ want to upload them to a public repository as it would break all sorts of licenses and make you liable.

We don’t want our Node modules uploaded to Github to make sure that the modules match your system rather than mine. This is particularly important for binary compiled modules… don’t be lazy and install your own to make sure they work as intended.

# Project specific files #
##########################
.idea
node_modules
.sass-cache
fonts/
src/**/*.html
dist/**/*.html
src/pm-content/
src/pdf
src/md-content/*.md
src/html-content/*.html

Conclusion

We’ve taken the same Markdown code, converted it to an HTML fragment, inserted the fragment into a template, used PrinceXML to process the file and a special printed media style sheet to generate PDF and upload the resulting PDF files to an Amazon S3 bucket or to a Google Cloud Storage bucket.

We can further customize the project by creating new CSS stylesheets for HTML and Paged Media content to take advantage of additional functionality available to either the printed or HTML versions of the content.

Let me know how this works out for you. If you have bugs or ideas to report, use the repository’s issue tracker for communication.

Known issues

  • There is at least one instance where the fallback font is used where the primary (web) font is used in other parts of the document
  • PrinceXML doesn’t support videos. This is problematic since Youtube and Vimeo use iframes and doesn’t offer a way to add a poster frame and causes either a blank or black space (depending on the video provider). Only way to deal with the issue is to swap the video for an image as a pre-processor step or using Javascript
  • Everything gets rebuilt every time we run the commands. There are ways to only work on files that have changed. This is particularly important when uploading content to the S3 bucket, we don’t want to use excessive bandwidth and have to pay for it
  • S3 credentials are in the clear. Figure out a way to either use a config file in the local file system or some other way to hide the credentials from users other than me
  • Haven’t quite figured out if the tool will overwrite the content already in the S3 bucket
  • The first time you run make-pdf or pdf-make (the full task) it may produce no result. Running it a second time fixes the issue
  • When running pdf-make (the full task) copy-pdf runs before the PDF files are generated even when running it in sequence. If make-pdf runs synchronously i need to figure out why and how to solve the problem

links and resources

What does it take to develop a WordPress site?

I had a very interesting conversation at my coffee shop the other day. It revolved around WordPress and how easy it is (or isn’t) to modify and customize. In the 10+ years I’ve been using it things have gotten to the point where you don’t really need to know PHP as much as you used to.

In this post we’ll cover the following aspects of customizing a WordPress theme and associated tools and functionality:

  • CSS customizations
  • Local changes using filters and actions
  • Creating child themes
  • Customizing templates
  • Using WordPress REST API to create a completely different front end
  • When it’s best to create a theme from scratch

We’ll also talk about the minimal skillset necessary for creating a WordPress theme with the understanding that it will depend on the changes you want to make.

My personal biases against WordPress business model

I make no secret of the issues I have against WordPress and their business model. In GPL and why I don’t develop for WordPress I’ve documented my opposition against the heavy handed approach Matt Mullenweg and Automattic took against an independent theme developer.

In short, because I release all my code under MIT, I would not be able to offer my code, plugins or themes in WordPress.org. They will not host code released under licenses other than GPL even if they are more permissive.

I am philosophically opposed to GPL/LGPL and Affero general public licenses. I should be the one deciding the license I release my code under, not the Free Software Foundation or Automattic.

That said, not wanting to develop for WordPress doesn’t equal not knowing how to develop for WordPress. Sharing this post doesn’t equal endorsing their policies.

CSS cutomizations

The easiest way to change the looks of a WordPress app is to change the theme’s CSS. Dev Tools comes in very handy when you’re trying to figure out the specific elements to style. For this section I’m presenting 3 different cases

Changing the fonts for a theme could be done with something like this. Note that we import the fonts in the CSS rather than enqueuing the fonts in functions.php like we’ll do later in the post. These styles will apply to the entire content.

@import  "https://fonts.googleapis.com/css?family=Handlee";
@import  "https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic,300,300italic";
@import  "https://fonts.googleapis.com/css?family=Source+Code+Pro:400,300";

body {
    font-family: Lato, Verdana, Helvetica, sans-serif;
    font-size: 16px;
/* 1rem = 16px */
    font-weight: 300;
}

h1,
h2,
h3 {
    clear: both;
    font-family: 'Handlee', cursive !important;
    margin-bottom: .25em;
    text-rendering: optimizeLegibility;
}

When changing the font for h1 tags only when they appears in single post pages we need to figure out the class associated with the content that you want to change. To change the h1 tag in the title we need to change the .entry-title class.

h1.entry-title {
  clear: both;
  font-family: 'Handlee', cursive !important;
  margin-bottom: .25em;
  text-rendering: optimizeLegibility;
}

Adding styles for manually entered CSS is simple. I write embeds for Youtube and Vimeo videos using a syntax like the one below

<div class="video">
<iframe width="560" height="315" 
  src="https://www.youtube.com/embed/FaOSCASqLsE?rel=0" 
  frameborder="0" allowfullscreen></iframe>
</div>

And use the following CSS to make sure the videos take the full available screen width.

.video iframe {
  clear: both;
  display: block;
  margin: 1em auto;
  max-width: inherit;
  text-align: center
}

When working with these types of customization we need to be very, very careful when dealing with specificity issues. Check Estelle Weyl’s post on CSS specificity for more information on how to work specificity.

Local changes using hooks and filters

functions.php is a theme specific customization file set aside for local customizations. Rather than updating entire templates we can update with actions and filters to customize and change the way

The idea is to build a function that does what we want and then use the add_filter function and pass the following parameters:

  • The name of the filter
  • The code to execute then the filter is triggered
  • An integer for priority
  • An integer for the number of argument

What does this mean in the real world? We’ll look at two examples of how to customize WordPress. We’ll use a filter to change the way WP inserts images into a post using more semantically rich HTML5 that is easier to style later on.

function html5_insert_image($html, $id, $caption, $title, $align, $url) {
  $html5 = "<figure id='post-$id media-$id' class='align-$align'>";
  $html5 .= "<img src='$url' alt='$title' />";
  if ($caption) {
    $html5 .= "<figcaption>$caption</figcaption>";
  }
  $html5 .= "</figure>";
  return $html5;
}
add_filter( 'image_send_to_editor', 'html5_insert_image', 10, 9 );

The second example uses an action to add the parent’s theme CSS to save ourselves from copying the CSS to a child theme. Actions

  • Create a PHP function that should execute when a specific WordPress event occurs, in your plugin file.
  • Hook this function to the event by using the add_action() function.
<?php
function enqueue_parent_styles() {
    wp_enqueue_style( 'parent-style', get_template_directory_uri().'/style.css' );
}
add_action( 'wp_enqueue_scripts', 'enqueue_parent_styles' );
?>

Rather than make the article way longer than it needs to be by giving you a full list of the hooks available for add_action and add_filter I’ll refer you to the WordPress Code Reference for Hooks.

Also make sure you use some way to namespace your functions to avoid conflict with functions already in WordPress core or other plugins. Nothing sucks more than your action calling someone else’s code (speaking from experience).

Difference between filters, actions and plugins?

Taken from: http://wordpress.stackexchange.com/questions/1007/difference-between-filter-and-action-hooks

Action Hooks

Actions Hooks are intended for use when WordPress core or some plugin or theme is giving you the opportunity to insert your code at a certain point and do one or more of the following:

  • Use echo to inject some HTML or other content into the response buffer,
  • Modify global variable state for one or more variables, and/or
  • Modify the parameters passed to your hook function (assuming the hook was called by do_action_ref_array() instead of do_action() since the latter does not support passing variables by-reference.)

Filter Hooks

Filter Hooks behave very similar to Action Hooks but their intended use is to receive a value and potentially return a modified version of the value. A filter hook could also be used just like an Action Hook i.e. to modify a global variable or generate some HTML, assuming that’s what you need to do when the hook is called. One thing that is very important about Filter Hooks that you don’t need to worry about with Action Hooks is that the person using a Filter Hook must return (a modified version of) the first parameter it was passed. A common newbie mistake is to forget to return that value!

So what’s the Real Difference?

In reality Filter Hooks are pretty much a superset of Action Hooks. The former can do anything the latter can do and a bit more albeit the developer doesn’t have the responsibility to return a value with the Action Hook that he or she does with the Filter Hook.

Giving Guidance and Telegraphing Intent

But that’s probably not what is important. I think what is important is that by a developer choosing to use an Action Hook vs. a Filter Hook or vice versa they are telegraphing their intent and thus giving guidance to the themer or plugin developer who might be using the hook. In essence they are saying either “I’m going to call you, do whatever you need to do” OR “I’ve going to pass you this value to modify but be sure that you pass it back.”

We can use both filters and actions both in functions.php or a plugin. I usually wait until I’ve accumulated enough related filters or actions (usually 5) to move them to a plugin. There shouldn’t be a difference in how the filters or actions work in either implementation.

Creating Child Themes

Before we start customizing our theme’s template we’ll take a little detour and talk about child themes. Every WordPress theme can be used as the source of a child theme (also known as the parent theme) but there are frameworks like the Sitepoint Base theme or the Genesis Framework by Studio Press that are specifically made to be used as parent themes.

Child themes completely depend on their parents to work. The parent theme has to be installed in your WordPress installation for the child to work at all. The child theme will modify and overwrite the parent with the changes you make without changing the parent at all. If you decide to start over all you have to do is remove and recreate the child theme.

There are numerous advantages to using a child theme:

  • You don’t have to start from scratch. The bulk of the work is already done for you so you can concentrate on the modifications that will work best for your site and content
  • Upgrading the parent theme will not affect the child theme or its customizations
  • You can use as much or as little of the parent as you want and can make as many or as few changes as you need or want to
  • At a minimum a child theme needs three things:
    • A folder
    • A style sheet
    • A local functions.php file

The Creation Process

I normally do this directly on the server using Unix shell tools and programs. If you’re not comfortable doing this then it’s a good idea to setup a local development environment and then zip the content of your child theme and install it using the theme installer.

If you choose to go the local route Google creating child theme wordpress for general instructions.

Your choice

Connect to your host and create your child theme’s folder in wp-content/themes. I normally ssh to my host to make these changes so, once i’ve logged in and changed to path/to/my/blog/wp-content/themes I run the following commands:

-bash-3.2$ mkdir twentyseventeen-child
-bash-3.2$ cd twentyseventeen-child/

Next we’ll create our style. In the child theme directory (twentyseventeen-child) run the following commands to create the style.css file and open it for editing:

touch style.css
vi style.css

press the i key to go into interactive mode and copy the content below.

/*
 Theme Name:   Twenty Seventeen Child
 Theme URI:    http://example.com/twenty-seventeen-child/
 Description:  Twenty Seventeen Child Theme
 Author:       John Doe
 Author URI:   http://example.com
 Template:     twentyseventeen
 Version:      1.0.0
 License:      MIT
 License URI:  https://caraya.mit-license.org/
 Tags:         light, dark, two-columns, right-sidebar, responsive-layout, accessibility-ready
 Text Domain:  twenty-seventeen-child
*/
  • Theme name This is the name that will show up for your theme in the WordPress back end
  • Theme URI This points to the website or demonstration page of the theme at hand. This or the author’s URI must be present in order for the theme to be accepted into the WordPress directory
  • Description This description of your theme will show up in the theme menu when you click on “Theme Details”
  • Author This is the author’s name
  • Author URI You can put your website’s address here if you want.
  • Template This part is crucial. Here goes the name of the parent theme, meaning its folder name. Be aware that it is case-sensitive, and if you don’t put in the right information, you will receive an error message!
  • Version This displays the version of your child theme. Usually, you would start with 1.0.
  • License This is the license of your child theme. WordPress themes in the directory are usually released under a GPL license. This is only important if you will add your theme to the WordPress directory.
  • License URI This is the address where your theme’s license is explained
  • Tags The tags help others find your theme in the WordPress directory. Thus, if you include some, make sure they are relevant.
  • Text domain This part is used for internationalization and to make themes translatable. This should fit the “slug” of your theme.

Most of this information is only relevant if you choose to publish your child theme. If you will not publish it, either because it’s a personal project or because it’s for a client then the minial stylesheet header looks like this:

/*
 Theme Name:   Twenty Seventeen Child Theme
 Description:  A child theme of the Twenty Seventeen WordPress theme
 Author:       Carlos Araya
 Template:     twentyseventeen
 Version:      1.0.0
*/

Once it has been copied press escape and then wq to save your work and quit the editor.

The next step is to create functions.php and add a function to enqueue the parent theme’s styles and the child styles.

To create the file run these commands.

touch functions.php
vi functions.php

Copy the content below to the functions.php file. Press i to enter interactive mode and then paste the script below. This will use WordPress’ system to import the parent theme and the child theme’s stylesheets in the correct order to make the cascade work for you.

<?php
// function taken from: 
// http://justintadlock.com/archives/2014/11/03/loading-parent-styles-for-child-themes
function my_enqueue_styles() {
  /* If using a child theme, auto-load the parent theme style. */
  if ( is_child_theme() ) {
    wp_enqueue_style( 'parent-style', trailingslashit( get_template_directory_uri() ) . 'style.css' );
  }

  /* Always load active theme's style.css. */
  wp_enqueue_style( 'style', get_stylesheet_uri() );
}
add_action( 'wp_enqueue_scripts', 'my_enqueue_styles' );



function html5_insert_image($html, $id, $caption, $title, $align, $url, $size, $alt) {
  $src  = wp_get_attachment_image_src( $id, $size, false );
  $url = str_replace(array('http://','https://'), '//', $src[0]);
  $html = get_image_tag($id, '', $title, $align, $size);
  $html5 = "<figure>";
  $html5 .= "<img src='$url' alt='$alt' class='size-$size' />";
  if ($caption) {
    $html5 .= "<figcaption class='wp-caption-text'>$caption</figcaption>";
  }
  $html5 .= "</figure>";
  return $html5;
}
add_filter( 'image_send_to_editor', 'html5_insert_image', 10, 9 );
?>

Once it has been copied press escape and then wq to save your work and quit the editor.

If you want to change templates from the parent theme in your child you will have to copy the structure from the parent to the child. Because we’ll make changes to a template, we’ll copy it now. To do so run the following commands from the root of the child theme:

mkdir -p template-parts/post
cd template-parts/post
# copies content from parent
cp ../../../twentyseventeen/template-parts/post/*.php . 

This will make all the post specific templates available to edit in the child theme.

Good reads

Customizing Templates

The next step in customizing a WordPress implementation is to edit a template. Unless you’re working in a one-off theme for a client I strongly suggest you work on a child theme like we did above… this will save you from a lot of headaches during development.

In this section we’ll assume you created a

dding a link to the accelerated mobile pages (AMP) version of a post when you’re viewing individual posts.

You have to edit the specific template. In the case of the theme I’m using (twentyseventeen) the template to edit is template-parts/post/content-single.php. What I want to do is add the link to the header either next to or below the post title. I’ve taken the full template for this demo.

<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<?php
  if ( is_sticky() && is_home() ) :
    echo twentyseventeen_get_svg( array( 'icon' => 'thumb-tack' ) );
  endif;
?>
<header class="entry-header">
  <?php
    if ( 'post' === get_post_type() ) :
      echo '<div class="entry-meta">';
        if ( is_single() ) :
          twentyseventeen_posted_on();
        else :
          echo twentyseventeen_time_link();
          twentyseventeen_edit_link();
        endif;
      echo '</div><!-- .entry-meta -->';
    endif;

    if ( is_single() ) {
      the_title( '<h1 class="entry-title">', '</h1>' );?>
      <p class='amp-link'>Also available in <a href='<?php the_permalink(); ?>/amp'>AMP</a></p>
    <?php } else {
      the_title( '<h2 class="entry-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></h2>' );
    }
  ?>
</header><!-- .entry-header -->

<?php if ( '' !== get_the_post_thumbnail() && ! is_single() ) : ?>
  <div class="post-thumbnail">
    <a href="<?php the_permalink(); ?>">
      <?php the_post_thumbnail( 'twentyseventeen-featured-image' ); ?>
    </a>
  </div><!-- .post-thumbnail -->
<?php endif; ?>

<div class="entry-content">
  <?php
    /* translators: %s: Name of current post */
    the_content( sprintf(
            __( 'Continue reading<span class="screen-reader-text"> "%s"</span>', 'twentyseventeen' ),
            get_the_title()
    ) );

    wp_link_pages( array(
            'before'      => '<div class="page-links">' . __( 'Pages:', 'twentyseventeen' ),
           'after'       => '</div>',
           'link_before' => '<span class="page-number">',
           'link_after'  => '</span>',
    ) );
  ?>
</div><!-- .entry-content -->

<?php if ( is_single() ) : ?>
  <?php twentyseventeen_entry_footer(); ?>
<?php endif; ?>

</article><!-- #post-## -->                                

We don’t need to implement AMP functionality. That has been implemented through a third-party plugin that we’ll discuss in more detail in the next section.

When to use third party plugins?

In the previous section we added a link to the AMP version of single posts but we didn’t have to worry about how to convert the posts into AMP, I used a plugin from Automattic to generate the AMP content. It will add code and customizable templates to make AMP work with your content; you can then choose to customize the code or use it as is.

This is a good point to stop and ask ourselves why do we need plugins and how safe are we installing third party plugins (particularly form a security stand point).

First things first. Only download plugins from sites you trust. Plugins can modify the way your WordPress installation works and cause all sorts of unsafe and unpredictable consequences.

Next. Make sure that you actually need the features you’re installing plugins for. Just like with all web content the more plugins you have installed the bigger an impact on performance.

Lastly. Test the plugins in a development server. Before putting anything in a production server you need to test it in development server that mirrors as closely as possible your production environment. If you can afford it it would also help to have third party reviews of your plugins and how they interact with your code.

Using WordPress REST API to create a completely different front end

WordPress is moving towards becoming a fully-fledged application framework, and we needed new APIs. This project was born to create an easy-to-use, easy-to-understand and well-tested framework for creating these APIs, plus creating APIs for core.

The API exposes a simple yet powerful interface to WP Query, the posts API, post meta API, users API, revisions API and many more. Chances are, if you can do it with WordPress, WP API will let you do it.

from the wp-api plugin page

The API is included in the core WordPress installation as of version 4.7 and newer. There is a plugin available for versions between 4.4 and 4.6

So why would I use this API rather than the traditional WordPress API? The API gives you the freedom to create your UI and UX in whatever language you want and in whatever way your users will be most comfortable with. To test the API, and give myself a good refresher on Polymer, I created a Polymer element that fetches the latest 10 posts from my blog and converts them to HTML.

The code looks like this:

<template>
    <!-- iron-ajax action -->
    <iron-ajax
      auto
      url="http://rivendellweb.net/wp-json/wp/v2/posts"
      params='{"page": 1, "_embed": 1}'
      handle-as="json"
      last-response="{{posts}}"
      debounce-duration="300"></iron-ajax>

    <!-- local DOM for your element -->

    <div class="cards-container">
      <template is="dom-repeat" items="{{posts}}" as="post">
        <paper-card heading tab-index='0'>
          <h1>
            <marked-element markdown="[[post.title.rendered]]">
              <div class="markdown-html" tabindex="0"></div>
            </marked-element>
          </h1>
          <div class="post-meta">
            <p>created: [[post.date]]</p>
            <p>last update: [[post.modified]]</p>

            <p>Posted by: [[post._embedded.author.0.name]] in [[post._embedded.wp:term.0.0.name]]</p>
          </div> <!-- closes post-meta -->
          <div class="card-content">
            <a href="[[post._embedded.wp:featuredmedia.0.link]]"><img src="[[post._embedded.wp:featuredmedia.0.media_details.sizes.large.source_url]]"></a>

            <template is="dom-if" if="[[post.content.protected]]">

              <h1>Private Post</h1>

              <p>This is a protected post and the content is not available outside the original blog it was posted in</p>
            </template>
            <marked-element markdown="[[post.content.rendered]]">
              <div class="markdown-html" tab-index='0'></div>
            </marked-element>
          </div> <!-- closes card-content -->
        </paper-card>
      </template>
    </div> <!-- closes cards-container -->
</template>

Examples of apps built with the REST API

When it’s best to create a theme from scratch

The biggest and most complex customization option is to build a theme from scratch. I seldom start completely from scratch but use something like Sitepoint’s base theme, underscores. I used to work with Thesis but dropped it when the development became too complicated to do without using individual blocks and being forced into visual design.

Before you jump into this adventure consider the following list of pros and cons that I consider essential when considering if you want to develop a theme from scratch or not:

Pros

  • You can use whatever technology stack you’re most comfortable with (I uses SCSS for my styles and have used Coffeescript to write scripts before converting them to ES5)
  • You have total control over the looks and styles of your site. You can create as many or few templates as you want

Cons

  • You need to know (or know where to find information about) how to fully build templates and the PHP Way to do things
  • You still need to have a design ready to go. Tania’s article referenced later uses a Bootstrap template as the basis for a theme. Zurb Foundation also provides a blog template using their platform.

Rather than tell you how to do it, mostly because it would take several posts just as long as this one to cover in the level of detail that this needs. Tania Rascia has a very good series of articles covering how to build a theme from scratch

Closing

Customizing a WordPress theme can be as complicated as you need it to be. You can work on different levels of customization to achieve your desired result. With the suite of technologies we’ve discussed the possibilities are only limited by your imagination.

Looking at animations again… GSAP

Green Sock Animation Platform (GSAP) is an animation powerhouse. It’s a Javascript based library designed to animate most CSS and SVG properties.

GSAP components are (Taken from Getting Started with GSAP):

  • TweenLite: the core of the engine which handles animating just about any property of any object. It is relatively lightweight yet full-featured and can be expanded using optional plugins (like CSSPlugin for animating DOM element styles in the browser, or ScrollToPlugin scrolling to a specific location on a page or div, etc.)
  • TweenMax: TweenLite’s beefy big brother; it does everything TweenLite can do plus non-essentials like repeat, yoyo, repeatDelay, etc. It includes many common plugins too like CSSPlugin so that you don’t need to load as many files. The focus is on being full-featured rather than lightweight.
  • TimelineLite: a powerful, lightweight sequencing tool that acts like a container for tweens, making it simple to control them as a whole and precisely manage their timing in relation to each other. You can even nest timelines inside other timelines as deeply as you want. This allows you to modularize your animation workflow easily.
  • TimelineMax: extends TimelineLite, offering exactly the same functionality plus useful (but non-essential) features like repeat, repeatDelay, yoyo, currentLabel(), and many more. Again, just like TweenMax does for TweenLite, TimelineMax aims to be the ultimate full-featured tool rather than lightweight.

The platform provides additional tools such as easing, plugins, utilities like Draggable, and others. Check the GSAP/JS documentation for more information.

I see two downsides to libraries like GSAP. You’re loading additional libraries that may impact application performance. The other downside is now much of the library do you need to know in order to accomplish what you want. I’ll explore this in more detail as I go through this demos with the understanding that this is not a full tutorial… there is no way I can (or want to) learn everything there is about GSAP. As with most of these posts, it’s meant as a starting point for current and future research.

Loading the library

Place any of these scripts at the bottom of the page, before the closing body tag and before any scripts that use the GSAP library. If you want to use a CDN use the links below instead of local references.

We have three options to load the library, all depending on the level of complexity you need for your application. The first one is to load TweenLite.js and TimelineLite.min to work with a minimal set of functionality at a small file size.

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.1/TweenLite.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.1/TimelineLite.min.js"></script>

TweenMax and TimelineMax include the lite version of each plugins with additional functionality (discussed in the description of the platform) that it’s meant as a single resource to load (with less HTTP requests).

<!--CDN link for  TweenMax-->
<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenMax.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TimelineMax.min.js"></script>

If you’re working on an HTTP2 server or are more concerned with the size of the download you can pick and choose which core components and plugins to load (a common lightweight choice is TweenLite, CSSPlugin, and EasePack). For example:

<!--CDN links for TweenLite, CSSPlugin, and EasePack-->
<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/plugins/CSSPlugin.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/easing/EasePack.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenLite.min.js"></script>

Basic effects

Now that we’ve covered how to load the scripts we’ll work on some basic tweening. We’ll use the following HTML to define the element we want to animte and load the scripts at the bottom of the page before the closing body tag.

<div class="boxes" id="box1"></div>

<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenMax.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TimelineMax.min.js"></script>
<script src="scripts/gsap1.js"></script>

I prefer to give the element we’ll animate an initial state using CSS. It’s important to remember that if you don’t give explicit dimensions to your elements they will not appear on screen. This little issue bit me several time when devloping these examples… this is why we set a height and width.

.boxes {
  background-color: #3d6644;
  border: 1px solid black;
  height: 50px;
  width: 50px;
}

The content of the gsap1.js script is listed below. I chose to use TweenLite.to because i’ve already configured the intial size of the animation. There are other values like TweenLite.from() and TweenLite.fromTo()

let box1 = document.getElementById('box1');

TweenLite.to("#box1", 4, 
  { backgroundColor:"#ff00ff", width:"50%", height:"250px", ease:Power2.easeInOut }
);

Note that since we use TweenLite.to() the values we provide will be treated as the final values for the animation. The color we defined in the CSS (#3d66644) and width and height at their default values (0% and 0px).

We can also create animations that have a starting and ending position. In this example we’ll animate a circle (actually a div with border radius of 50%) when the user clicks a button.

we begin with our styles that will define the starting properties of the object to animate and will be the values of the object when animations don’t work.

body {
    background: #eee;
    text-align: center;
}

#o1 {
    border-radius: 50%;
    border: 1px solid crimson;
    background-color: indianred;
    margin: 0 auto;
}

div > #player {
    display: block;
}

We then define the HTML. A button and the div container we will animate; we give IDs to both of them to reference them from the script

<div>
    <button id="player">Play</button>
</div>

<div id="o1"></div>

The script does two things:

  • it sets up the animation, including the easing we’ll use and the initial state for the animation. We override the default of starting the animation immediately and defer to the user clicking the button to begin the animation work
  • it creates a click handler for the button where we test if the animation is working using the isTweening property of the TweenMax object and toggles the status of the animation (play if it’s paused and paused if it’s playing) and the text of the button using innerText.
let o1 = document.getElementById('o1');
let player = document.getElementById('player');

// animates width and height from 0 to 200
anim = TweenLite.fromTo(o1, 10,
// Initial state
{width:0, height:0},
// Final state plus options
{width:200, height:200, ease: linear.easeNone, paused: true }

);

player.addEventListener('click', () => {
    if (TweenMax.isTweening(o1) === false) {
        anim.play();
        player.innerText="Pause";
    } else {
        anim.pause();
        player.innerText='Play';
    }
});

Easing

GSAP provides an extensive easing library. TweenLite comes with a default set of easing functions and TweenMax provides an additional set. Check the easing visualizer to get an idea of what’s available and how you can use those functions in your animations.

special properties

There is an additional set of properties for TweenLite that provide additional control over the animation. Rather than try to explain them, I’ve adapted their description from the TweenLite documentation

  • delay: Number – Amount of delay in seconds (or frames for frames-based tweens) before the animation should begin
  • ease: Ease (or Function or String) – You can choose from various eases to control the rate of change during the animation, giving it a specific “feel”. You can also define an ease by name (string) like Strong.easeOut or reverse style (like jQuery uses) easeOutStrong. The default is Quad.easeOut
  • paused: Boolean – If true, the tween will pause itself immediately upon creation.
  • immediateRender: Boolean – Normally when you create a tween, it begins rendering on the very next frame (update cycle) unless you specify a delay. However, if you prefer to force the tween to render immediately when it is created, setimmediateRender to true. Or to prevent a from() from rendering immediately, set immediateRender to false. By default, from() tweens set immediateRender to true
  • overwrite: String (or integer) – Controls how (and if) other tweens of the same target are overwritten. There are several modes to choose from, but “auto” is the default:
    • “none” (0) (or false) – no overwriting will occur.
    • “all” (1) (or true) – immediately overwrites all existing tweens of the same target even if they haven’t started yet or don’t have conflicting properties.
    • “auto” (2) – when the tween renders for the first time, it will analyze tweens of the same target that are currently active/running and only overwrite individual tweening properties that overlap/conflict. Tweens that haven’t begun yet are ignored. For example, if another active tween is found that is tweening 3 properties, only 1 of which it shares in common with the new tween, the other 2 properties will be left alone. Only the conflicting property gets overwritten/killed. This is the default mode and typically the most intuitive for developers.
    • “concurrent” (3) – when the tween renders for the first time, it kills only the active (in-progress) tweens of the same target regardless of whether or not they contain conflicting properties. Like a mix of “all” and “auto”. Good for situations where you only want one tween controlling the target at a time.
    • “allOnStart” (4) – Identical to “all” but waits to run the overwrite logic until the tween begins (after any delay). Kills tweens of the same target even if they don’t contain conflicting properties or haven’t started yet.
    • “preexisting” (5) – when the tween renders for the first time, it kills only the tweens of the same target that existed BEFORE this tween was created regardless of their scheduled start times. So, for example, if you create a tween with a delay of 10 and then a tween with a delay of 1 and then a tween with a delay of 2 (all of the same target), the 2nd tween would overwrite the first but not the second even though scheduling might seem to dictate otherwise. “preexisting” only cares about the order in which the instances were actually created. This can be useful when the order in which your code runs plays a critical role
  • onComplete: Function – A function that should be called when the animation has completed
    • onCompleteParams: Array – An Array of parameters to pass the onComplete function. For example,TweenLite.to(element, 1, {left:”100px”, onComplete:myFunction, onCompleteParams: [element, "param2"]}); To self-reference the tween instance itself in one of the parameters, use “{self}“, like: onCompleteParams:["{self}", "param2"]
    • onCompleteScope: Object – Defines the scope of the onComplete function (what “this” refers to inside that function).
  • onReverseComplete: Function – A function that should be called when the tween has reached its beginning again from the reverse direction. For example, if reverse() is called the tween will move back towards its beginning and when itstime reaches 0, onReverseComplete will be called. This can also happen if the tween is placed in a TimelineLite or TimelineMax instance that gets reversed and plays the tween backwards to (or past) the beginning.
    • onReverseCompleteParams: Array – An Array of parameters to pass the onReverseComplete function. For example, TweenLite.to(element, 1, {left:"100px", onReverseComplete:myFunction, onReverseCompleteParams:[mc, "param2"]}); To self-reference the tween instance itself in one of the parameters, use “{self}“, like: onReverseCompleteParams:["{self}", "param2"]
    • onReverseCompleteScope: Object – Defines the scope of the onReverseComplete function (what “this” refers to inside that function)
  • onStart: Function – A function that should be called when the tween begins (when its time changes from 0 to some other value which can happen more than once if the tween is restarted multiple times)
    • onStartParams: Array – An Array of parameters to pass the onStart function. For example, TweenLite.to(element, 1, {left:”100px”, delay:1, onStart:myFunction, onStartParams:[mc, “param2”]}); To self-reference the tween instance itself in one of the parameters, use “{self}”, like: onStartParams:[“{self}”, “param2”]
    • onStartScope: Object – Defines the scope of the onStart function (what “this” refers to inside that function)
  • onUpdate: Function – A function that should be called every time the animation updates (on every frame while the animation is active)
    • onUpdateParams: Array – An Array of parameters to pass the onUpdate function. For example, TweenLite.to(element, 1, {left:”100px”, onUpdate:myFunction, onUpdateParams:[mc, “param2”]}); To self-reference the tween instance itself in one of the parameters, use “{self}”, like: onUpdateParams:[“{self}”, “param2”]
    • onUpdateScope: Object – Defines the scope of the onUpdate function (what “this” refers to inside that function)
  • useFrames: Boolean – If useFrames is true, the tweens’s timing will be based on frames instead of seconds because it is initially added to the root frames-based timeline. This causes both its duration and delay to be based on frames. An animations’s timing mode is always determined by its parent timeline
  • lazy: Boolean – When a tween renders for the very first time and reads its starting values, GSAP will automatically “lazy render” that particular tick by default, meaning it will try to delay the rendering (writing of values) until the very end of the “tick” cycle which can improve performance because it avoids the read/write/read/write layout thrashing that some browsers do. If you would like to disable lazy rendering for a particular tween, you can set lazy:false. Or, since zero-duration tweens do not lazy-render by default, you can specifically give it permission to lazy-render by setting lazy:true like TweenLite.set(element, {opacity:0, lazy:true});
  • onOverwrite: Function – A function that should be called when the tween gets overwritten by another tween. The following parameters will be passed to that function:
    1. overwrittenTween: Animation – the tween that was just overwritten
    2. overwritingTween: Animation – the tween did the overwriting
    3. target: Object [only passed if the overwrite mode was “auto” because that’s the only case when portions of a tween can be overwritten rather than the entire thing] – the target object whose properties were overwritten. This is usually the same as overwrittenTween.target unless that’s an array and the overwriting targeted a sub-element of that array. For example, TweenLite.to([obj1, obj2], 1, {x:100}) and then TweenLite.to(obj2, 1, {x:50}), the target would be obj2
    4. overwrittenProperties: Array [only passed if the overwrite mode was “auto” because that’s the only case when portions of a tween can be overwritten rather than the entire thing] – an array of property names that were overwritten, like [“x”,”y”,”opacity”].
      Note: there is also a static TweenLite.onOverwrite that you can use if you want a quick and easy way to be notified when any tween is overwritten (great for debugging). This saves you the hassle of defining an onOverwrite on a tween-by-tween basis
  • autoCSS: Boolean – Animating css-related properties of DOM elements requires the CSSPlugin which means that normally you’d need to wrap css-related properties in a css:{} object like TweenLite.to(element, 2, {css:{left:”200px”, top:”100px”}, ease:Linear.easeNone}); to indicate your intent (and to tell GSAP to feed those values to the CSSPlugin), but since animating css-related properties is so common, GSAP implements some logic internally that allows you to omit the css:{} wrapper (meaning you could rewrite the above tween as TweenLite.to(element, 2, {left:”200px”, top:”100px”, ease:Linear.easeNone});)
  • callbackScope: Object – The scope to be used for all of the callbacks (onStart, onUpdate, onComplete, etc.). The scope is what “this” refers to inside any of the callbacks. The older callback-specific scope properties (onStartScope, onUpdateScope, onCompleteScope, onReverseComplete, etc.) are deprecated but still work.

Animating multiple elements with the same animation

Before we jump into working with time lines we’ll look at one last case. How to animate multiple object using the same animation parameters. We’ll use 2 small circles defined using HTML div elements and CSS for styling.

body {
    background: #eee;
    text-align: center;
}

.circles {
    border-radius: 50%;
    border: 1px solid crimson;
    background-color: indianred;
    height: 50px;
    width: 50px;
}

div > #player {
    display: block;
}

#o1 {
    margin-bottom: 1rem;
}

This time we define two circles and a play button. We also add a script tag pointing to the CDN version of TweenMax. We’ll have to play with this to make sure that the script is also available when offline.

<div>
  <button id="player">Play</button>
</div>

<div class="circles" id="o1"></div>
<div class="circles" id="o2"></div>

<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenMax.min.js"></script>

The script is not too different than what we’ve used before. The main difference is that instead of capturing a single element by its ID we use getElementsByClassName to get all the elements matching the given class.

We then use TweenLite.to to control the final position of the element and a linear easing function.

We reuse the player button code from earlier examples to check if the tween is running. If it is we pause it and if it’s not then we play it.

let circles = document.getElementsByClassName('circles');
let player = document.getElementById('player');

// creates a tween for margin-left from 0 to 800
anim = TweenLite.to(circles, 10, { marginLeft: 800, ease: Linear.easeNone });

player.addEventListener('click', function() {
    if (TweenMax.isTweening(circles) === false) {
        anim.play();
        player.innerText="Pause";
    } else {
        anim.pause();
        player.innerText='Play';
    }
});

Timelines

Timelines give us finer control and additional features over the tween animations offered by TweenMax.

Timeline parameters:

You can use the constructor’s vars parameter to define any of the special properties below (syntax example: new TimelineLite({onComplete:myFunction, delay:2});

  • delay: Number – Amount of delay in seconds (or frames for frames-based tweens) before the animation should begin
  • paused: Boolean – If true, the tween will pause itself immediately upon creation
  • onComplete: Function – A function that should be called when the animation has completed.
    onCompleteScope : Object – Defines the scope of the onComplete function (what “this” refers to inside that function)
  • useFrames: Boolean – If useFrames is true, the tweens’s timing will be based on frames instead of seconds because it is intially added to the root frames-based timeline. This causes both its duration and delay to be based on frames. An animations’s timing mode is always determined by its parent timeline
  • tweens: Array – To immediately insert several tweens into the timeline, use the tweens special property to pass in an Array of TweenLite/TweenMax/TimelineLite/TimelineMax instances. You can use this in conjunction with the align and stagger special properties to set up complex sequences with minimal code. These values simply get passed to the add() method
  • align: String – Only used in conjunction with the tweens special property when multiple tweens are to be inserted immediately. The value simply gets passed to the add() method. The default is “normal”. Options are:
    1. “sequence” : aligns the tweens one-after-the-other in a sequence
    2. “start” : aligns the start times of all of the tweens (ignores delays)
    3. “normal” : aligns the start times of all the tweens (honors delays)
    • The align special property does not force all child tweens/timelines to maintain relative positioning, so for example, if you use “sequence” and then later change the duration of one of the nested tweens, it does not force all subsequent timelines to change their position. The align special property only affects the alignment of the tweens that are initially placed into the timeline through the tweens special property of the vars object.
  • stagger: Number – Only used in conjunction with the tweens special property when multiple tweens are to be inserted immediately. It staggers the tweens by a set amount of time in seconds (or in frames if useFrames is true). For example, if the stagger value is 0.5 and the “align” property is set to “start”, the second tween will start 0.5 seconds after the first one starts, then 0.5 seconds later the third one will start, etc. If the align property is “sequence”, there would be 0.5 seconds added between each tween. This value simply gets passed to the add() method. Default is 0.
  • onStart: Function – A function that should be called when the tween begins (when its time changes from 0 to some other value which can happen more than once if the tween is restarted multiple times).
    • onStartScope: Object – Defines the scope of the onStart function (what “this” refers to inside that function).
  • onReverseComplete: Function – A function that should be called when the tween has reached its beginning again from the reverse direction. For example, if reverse() is called the tween will move back towards its beginning and when itstime reaches 0, onReverseComplete will be called. This can also happen if the tween is placed in a TimelineLite or TimelineMax instance that gets reversed and plays the tween backwards to (or past) the beginning.
    • onReverseCompleteScope: Object – Defines the scope of the onReverseComplete function (what “this” refers to inside that function).
  • onUpdate: Function – A function that should be called every time the animation updates (on every frame while the animation is active).
    • onUpdateScope: Object – Defines the scope of the onUpdate function (what “this” refers to inside that function).
  • autoRemoveChildren: Boolean – If autoRemoveChildren is set to true, as soon as child tweens/timelines complete, they will automatically get killed/removed. This is normally undesireable because it prevents going backwards in time (like if you want to reverse() or set the progress lower, etc.). It can, however, improve speed and memory management. The root timelines use autoRemoveChildren:true.
  • smoothChildTiming: Boolean – Controls whether or not child tweens/timelines are repositioned automatically (changing their startTime) in order to maintain smooth playback when properties are changed on-the-fly. For example, imagine that the timeline’s playhead is on a child tween that is 75% complete, moving element’s left from 0 to 100 and then that tween’s reverse() method is called. If smoothChildTiming is false (the default except for the root timelines), the tween would flip in place, keeping its startTime consistent. Therefore the playhead of the timeline would now be at the tween’s 25% completion point instead of 75%. Remember, the timeline’s playhead position and direction are unaffected by child tween/timeline changes. element’s left would jump from 75 to 25, but the tween’s position in the timeline would remain consistent. However, if smoothChildTiming is true, that child tween’s startTime would be adjusted so that the timeline’s playhead intersects with the same spot on the tween (75% complete) as it had immediately before reverse() was called, thus playback appears perfectly smooth. element’s left would still be 75 and it would continue from there as the playhead moves on, but since the tween is reversed now element’s left will travel back towards 0 instead of 100. Ultimately it’s a decision between prioritizing smooth on-the-fly playback (true) or consistent position(s) of child tweens/timelines (false). Some examples of on-the-fly changes to child tweens/timelines that could cause their startTime to change when smoothChildTiming is true are: reversed, timeScale, progress, totalProgress, time, totalTime, delay, pause, resume, duration, and totalDuration.
  • onCompleteParams: Array – An Array of parameters to pass the onComplete function. For example, new TimelineLite({onComplete:myFunction, onCompleteParams:[“param1”, “param2”]}); To self-reference the timeline instance itself in one of the parameters, use “{self}”, like: onCompleteParams:[“{self}”, “param2”]
  • onStartParams: Array – An Array of parameters to pass the onStart function. For example, new TimelineLite({onStart:myFunction, onStartParams:[“param1”, “param2”]}); To self-reference the timeline instance itself in one of the parameters, use “{self}”, like: onStartParams:[“{self}”, “param2”]
  • onUpdateParams: Array – An Array of parameters to pass the onUpdate function. For example, new TimelineLite({onUpdate:myFunction, onUpdateParams:[“param1”, “param2”]}); To self-reference the timeline instance itself in one of the parameters, use “{self}”, like: onUpdateParams:[“{self}”, “param2”]
  • onReverseCompleteParams: Array – An Array of parameters to pass the onReverseComplete function. For example, new TimelineLite({onReverseComplete:myFunction, onReverseCompleteParams:[“param1”, “param2”]}); To self-reference the timeline instance itself in one of the parameters, use “{self}”, like: onReverseCompleteParams:[“{self}”, “param2”]
    • callbackScope: Object – The scope to be used for all of the callbacks (onStart, onUpdate, onComplete, etc.). The scope is what “this” refers to inside any of the callbacks. The older callback-specific scope properties (onStartScope, onUpdateScope, onCompleteScope, onReverseComplete, etc.) are deprecated but still work.

The HTML and CSS are the same as the prior example animating multiple objects with the same animation. The script will change as we’ll leverage several features available on GSAP:

  1. We create a timeline to sequence the events
  2. We capture the objects to animate into variables that will be used later in the script
  3. We animate the objects in sequence
    1. We animate the objects as a group with the same animation
    2. We then animate each object individually with a different easing function
  4. We play the timeline
// captures the timeline
let timeline = new TimelineLite(); // 1
// all animatable elements
let circles = document.getElementsByClassName('circles'); // 2
// Individual animatable elements
let elem1 = document.getElementById('o1');
let elem2 = document.getElementById('o2');
// Play button
let play = document.getElementById('play');

// With a timeline we can work with multiple tweens
timeline.add(TweenLite.to(circles, 4, { // 3 - 1 
    marginLeft: 400, ease: Linear.easeNone }));
timeline.add(TweenLite.to(elem1, 2.5, { // 3 - 2
    ease: SlowMo.ease.config(0.7, 0.7, false), y: 500
}));
timeline.add(TweenLite.to(elem2, 2.5, {
    ease: RoughEase.ease.config({
        template:  Power0.easeNone, strength: 1, points: 20, 
        taper: "none", randomize:  true, clamp: false}), y: 500 }));
timeline.add(TweenLite.to(circles, 10, { 
    marginLeft: 800, ease: Linear.easeNone }));

timeline.play(); // 4

What we’re not covering about GSAP

Because it’s such a big and feature rich library there is no way that I can cover all of GSAP and still have time to do what I need to do and still have a life. It is meant as a starting point for future work and most of the code can definitely be improved.

There is a whole other area of using GSAP that I will not cover: animating SVG. There are some thing that are better done with SVG than PNG; see the animations for Jake Archibald’s Offline Cookbook as an example.

Animating infographics and illustration will be covered in later posts.

To get an idea what you can do with SVG and GSAP, see the presentation below by Sarah Drasner who covers SVG and GSAP very well and in enough detail to make it a good starting point.

Links and resources

Books about animation

Title Author Publisher
Transitions and Animations in CSS Estelle Weyl O’Reilly
Designing Interface Animation Val Head Rosenfeld Media
Learning CSS3 Animations and Transitions Alexis Goldstein Addison-Wesley

Articles and Tutorials

Title Appears in
What Disney’s Classic Animation Principles Could Teach Web Designers Fast Company
Safer Web Animation for Motion Sensitivity A List Apart
UI Animation & UX: A not-so-secret friendship A List Apart
Sketching Interface Animations – An Interview with Eva-Lotta Lamm Val Head’s Blog
Animation in Design Systems 24 ways
A Comparison of Animation Technologies CSS Tricks
12 basic principles of animation Article at Wikipedia
Nerding Out With Bezier Curves Medium
Using CSS to animate border-radius chrisruppel.com
Let the Web move you Web Directions
A better tool for cubic bezier easing lea.verou.me
Steps CSS Animations Designmodo
Web Animation Tutorial Roundup Val Head
Getting started with GSAP gsap.com
Greenson.com ihatetomatoes.net
GSAP + SVG for Power Users: Motion Along A Path davidwalsh.name
GSAP HTML5 documentation greensock.com

Mailing Lists

Looking at animations again… WAAPI

The Web Animation API seeks to unify CSS transitions and animations with SMIL-based SVG animations under one API making it easier to implement on the browser side and easier to learn for designers and developers.

The first thing to notice, this is a Javascript API that manipulates animations’ timings and controls. As such we need to make sure that the browser has scripting enabled and the browser supports WAAPI. If it doesn’t there’s a good polyfill maintained by Google that will bring older browser up to part with supporting browsers.

For this example we’ll brake the code into three sections, the first one is the html we’ll animate. It’s a simple div with a number inside.

<div class='boxes box1'>1</div>

I’ve added CSS to center the number 1 in the box both vertically and horizontally (yay for Flexbox) and provide size and initial background color for the box.

.boxes {
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;

  height: 100px;
  width: 100px;
  background-color: rebeccapurple;
}

In the Javascript I use querySelector to select the element I want to animate. I’m animating a single object. If I want to select more than one I would use querySelectorAll instead.

Then I apply the animate method and pass it two arrays:

  • An array of one or more object containing the properties we want to animate
  • An array with the properties of the animation (duration, count and direction in this case)
var elem = document.querySelector('.boxes');
var animation = elem.animate({
  transform: [
    'translateX(500px)',
    'translateY(500px)',
    'translateX(500px)',
    'translateY(500px)'
  ], 
  color: [
    'rebeccapurple',
    'red',
    'blue',
    'white'
  ],
  opacity: [
    1,
    0.5,
    0.75,
    1
  ],
}, {
  direction: 'alternate',
  duration: 4000,
  iterations: 10,
});

We can shorten the code by creating arrays inside the animation call. Instead of using different arrays for each set of properties (transform, color and opacity) we take one element of each array and populate an array with them.

The resulting code looks like the code below and the result of the the two versions is identical (at least when I tested both versions in Codepen). Note that you animation step arrays don’t need to have the same number of items.

var elem = document.querySelector('.boxes');
var animation = elem.animate([
  { transform: 'translateX(500px)', color: 'rebeccapurple', opacity: '1'  },
  { transform: 'translateY(500px)', color: 'red', opacity: '0.5' },
  { transform: 'translateX(500px)', color: 'blue', opacity: '0.75' },
  { transform: 'translateY(500px)', color: 'white', opacity: '1' }
], {
    duration: 4000, //milliseconds
    easing: 'ease-in-out', //'linear', a bezier curve, etc.
    // delay: 10, //milliseconds
    iterations: Infinity, //or a number
    direction: 'alternate', //'normal', 'reverse', etc.
    fill: 'none'
    // fill: 'forwards' //'backwards', 'both', 'none', 'auto'
});

You can animate the same set of properties than you can in CSS animations (although this may change in the future) in a more concise and fuller API. We’ll explore some of these additional features and how we’ll make it work in a similar way to CSS animations.

Player controls

One of the things I find most frustrating about CSS animations is that there is no way to pause or reset an animation after it has started. Using WAAPI we can programmatically control the play status of an animation.

First modification to our animation is to add buttons to control the playback status. I was lazy and chose not to do a toggle button for play and pause and keep them as separate buttons. In a real application I would take the extra time and code a toggle play/pause button.

<div class='boxes box1'>1</div>

<div class="nav">
  <button id="play">Play</button>
  <button id="pause">Pause</button>
  <button id="cancel">Cancel</button>

</div>

In the script we add variables to represent the buttons and event listeners that will cause the animation to do something (play, pause or reset the animation). We also start the animation paused to give the user the option of when to start it, if they want to start it at all.

// animation starts paused
animation.pause();

// add event listener to control animation playback
var play = document.getElementById("play");
var pause = document.getElementById("pause");
var cancel = document.getElementById("cancel");


play.addEventListener("click", () => {
    animation.play()
}, false);

pause.addEventListener("click", () => {
    animation.pause()
}, false);

cancel.addEventListener("click", () => {
    animation.cancel()
}, false);

controlling animation speed

We can also control the speed of the animation programmatically using the playbackRate method of WAAPI. There may be cases like animations explaining a procedure in an educational site or the relationship between two concepts where it would be awesome if you coulld slow down and/or speed up the animation.

We had three new nuttons to the page.

<h2>playback speed</h2>
  <button id="slower">0.5x</button>
  <button id="normal">1x</button>
  <button id="faster">2x</button>

And then add the associated click event handlers to make it play at half speed, normal speed and double speed. These values are hardcoded in, we can’t change how fast is the fast animation or how slow is the slow. We’ll address this in the next iteration.

var slower = document.getElementById("slower");
var normal = document.getElementById("normal");
var faster = document.getElementById("faster");

slower.addEventListener("click", () => {
  animation.playbackRate = 0.5; 
}, false);

normal.addEventListener("click", () => {
  animation.playbackRate = 1; 
}, false);

faster.addEventListener("click", () => {
  animation.playbackRate = 2; 
}, false);

In the previous example we hardcoded the values for the slower and faster speeds. It wold be cool if the values were customizable. One way to do so is to use assignment operators to change the values by a small step every time the button is clicked.

We modify the event listeners so that, instead of assigning a specific value to the playbackRate attribute we increase it or decrease it by 0.1 every time the corresponding button is clicked. The code now looks like this:

var slower = document.getElementById("slower");
var normal = document.getElementById("normal");
var faster = document.getElementById("faster");

slower.addEventListener("click", () => {
  animation.playbackRate -=0.1; 
}, false);

normal.addEventListener("click", () => {
  animation.playbackRate = 1; 
}, false);

faster.addEventListener("click", () => {
  animation.playbackRate += 0.1; 
}, false);

If you use a negative value for playbackRate the animation will play backwards. The code below creates a button to play the animation in reverse.

var reverse = document.getElementById("reverse");

reverse.addEventListener("click", () => {
  animation.playbackRate =-1;
}, false);

One last thing to note. The code to slow the animation will eventually stop it since decreasing the playback rate will eventually makes it 0. This may be ok for some cases and not for others. If this is not ok for a specific use case we can put an if statement in the slower function to make the lowest value something we can control, something like this:

slower.addEventListener("click", () => {
  animation.playbackRate -=0.1;
  // don't let the animation stop
  if (animation.playbackRate == 0) {
    animation.playbackRate = 0.1;
  }
}, false);

We also need to make sure that users can distinguish the difference between the steps of animation speed. Perhaps 0.1 is too subtle a speed increase or decrease. It all depends on your project and your users.

motion paths

Work in this section is adapted with many thanks from work by Dan Wilson presented in his blog and modified as I finally start to learn how this works.

I’ve always the idea of animating objects on a path, a predefined set of coordinates. I’ve seen this a lot in Flash and SVG/SMIL based animations but SMIL has been retired or at least deprecated in most browsers (if it was ever implemented at all) so developers were left with hacks and using SVG to create the animation (and hope that browsers will not remove SMIL for a while yet).

Motion is important and the W3C acknowledges that. They’ve put together a Motion Pat Module, level 1 that addresses how to use motion paths in CSS. WAAPI leverages this module when working with motion on a path.

We first create the HTML elements for the example. The content of the support class div will be populated from the script later in the process.

<h1>Motion Path Exercise</h1>

<div class="support"></div>

<div class="circle"><i>1</i></div>
<div class="circle"><i>2</i></div>
<div class="circle"><i>3</i></div>
<div class="circle"><i>4</i></div>
<div class="circle"><i>5</i></div>
<div class="circle"><i>6</i></div>
<div class="circle"><i>7</i></div>
<div class="circle"><i>8</i></div>
<div class="circle"><i>9</i></div>
<div class="circle"><i>10</i></div>

In the CSS area we define and format the HTML as circles with numbers within them. We use Flexbox to center the number inside the circle and use the will-change property. The descriptions and caveats from MDN are very important… if you abuse the property it will stop working so use it sparingly and with as few properties as possible.

.circle {
  z-index: 1;
  position: absolute;
  top: 6rem;
  left: 0;
  width: 3rem;
  height: 3rem;
  margin: 0 auto;
  display: none;
  justify-content: center;
  align-items: center;
}

.circle i {
  display: flex;
  justify-content: center;
  align-items: center; 
  width: 3rem;
  height: 3rem;  
  border-radius: 50%;
  border: 1px solid #000;
  background: #fff;
  color: #00f;
  transform-origin: 50% 50%;
  will-change: transform;
}

We use feature queries to detect the syntax that we need to use in a given version of a browser. If the browser doesn’t support Motion Paths at all neither of these queries will be added to the document and it’s left up to the developer to provide an alternative… we don’t want to exclude people from our project so we use motion path as a progressive enhancement and work with a different animation technique or library (possibly GSAP) is motion path is not supported in your target browsers.

The CSS below tells the browser what path to animate on. The Javascript will actually execute the animation.

I created the path in Illustrator, export the .ai file as svg, open it with my text editor and extract the path element and copied it to the CSS.

/* implemented in Chrome 46+ */
@supports (motion-offset: 0) {
  .circle {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    motion-offset: 100%;
    motion-path: path("M73.6462,149.5409c42.5436-42.5436,137.2421-16.8221,211.515,57.4508s99.9944,168.9713,57.4508,211.515s-137.2421,16.8221-211.515-57.4508S31.1026,192.0845,73.6462,149.5409z");
    will-change: motion-offset;
  }
}

/* This is the latest spec as of September 2016 */
@supports (offset-distance: 0) {
  .circle {
    display: block;
    offset-distance: 100%;
    offset-path: path("M73.6462,149.5409c42.5436-42.5436,137.2421-16.8221,211.515,57.4508s99.9944,168.9713,57.4508,211.515s-137.2421,16.8221-211.515-57.4508S31.1026,192.0845,73.6462,149.5409z");
    will-change: offset-distance;
  }
}

We’re almost there, promise. There are a few things to go in the script that we haven’t discussed before and we need to unpack.

As always we first capture all the elements we want to animate (all elements with class circle) using querySelectorAll and assign them to a variable (m).

We then use the CSS Support Javascript API to test what version of the Motion Path API we support.

The last step in this section is to define the keyframes object.

var m = document.querySelectorAll('.circle');

//This is the latest spec as of September 2016
var supportsOffsetDistance = CSS && CSS.supports && CSS.supports('offset-distance', 0);

// What's implemented in Chrome 46+
var supportsMotionOffset = CSS && CSS.supports && CSS.supports('motion-offset', 0);

//motion properties are the old spec
var keyframes = [{
  offsetDistance: '100%',
  motionOffset: '100%'
}, {
  offsetDistance: 0,
  motionOffset: 0
}];

This is the meat of the script. We only run this part of the script if we support Motion paths, otherwise it makes no sense to busy the browser with something we won’t be able to use anyways.

If we support either method of Motion Path, then create a for loop to animate each object in our ‘objects to be animated’ array.

The only other funky thing is the delay parameter. We set it to the value of the time variable times the element’s index divided by the length of our ‘objects to animate’ list (querySelectorAll doesn’t create an array).

if (supportsOffsetDistance || supportsMotionOffset) {
  var time = 9000;
  for (var i = 0, len = m.length; i < len; ++i) {
    var player = m[i].animate(keyframes, {
      duration: time,
      iterations: Infinity,
      fill: 'both',
      easing: 'ease-in',
      delay: time * (i / m.length)
    });
 }

After all the work is done and since this is a learning experience we tell the user if their browser supports motion path or not and, if it does, which version of the API works on their browser.

  document.querySelector('.support').innerHTML = 'This browser supports it via the <code>' + (supportsOffsetDistance ? 'offset' : 'motion') + '</code> properties';
} else {
  document.querySelector('.support').textContent = 'This browser does not support it';
}

Keyframe Constructor and KeyframeEffects

So far we’ve only used the animate style of buidling animations. To recap, this is the way we build an animation using animate.

var elem = document.getElementById('myAnimation');
var timings = {
  duration: 1000,
  fill: 'both'
}
var keyframes = [
  { opacity: 1 }.
  { opacity: 0 }
];

elem.animate(keyframes, timings);

KeyframeEffect

A KeyframeEffect takes three parameters: the element to animate, an array of keyframes, and our timing options. We’ve seen all these attributes before when using animate. The difference is that the effect will not play automatically and serve as the base for the other effectss we’ll discuss in this section.

var effect = new KeyframeEffect(elem, keyframes, timings);

KeyframeConstructor

Using the same timings and keyframes as the example above we can use a constructor-style approach to build an animation. We first build a keyframeEffect

The primary difference here is that the animation does not start playing immediately, so this will be useful when creating animations in advance to be played later.

We then create a new Animation object and pass it two parameters, the animation (in this case the keyframeEffect we created) and a timeline object (in this case we use ownerDocument to get the root document element and use its timeline).

When creating animations this way the animation will not play until we actually tell it to by calling the play method. This way we can build all the animations before playing them.

var kEffect = new KeyframeEffect(elem, keyframes, timings);
var player = new Animation(kEffect, elem.ownerDocument.timeline);
player.play();

The future: GroupEffects & SequenceEffects

Neither groupEffect or SequenceEffect made it to browsers or the level 1 specification, they are part of the level 2 spec drafts. They provide programmatic ways to group and sequence animations.

While these features haven’t made it to the browsers there is an experimental version of the Polyfill that supports these upcoming features.

GroupEffect

The GroupEffect groups one or more KeyframeEffects to play simultaneously.

We create an array of keyframeEffects and pass it to the groupEffect constructor. We can then play the entire group simultaneously in our default document timeline whenever we’re ready to do so.

In this example we create the following HTML. It’s important to remember that we are using forward-looking features so we must include the next polyfill to make this work. This will be required until native implementations of the level 2 specification start hitting browsers.

<div id="i0">1</div>
<div id="i1">2</div>
<div>
    <div id="o0">A</div>
    <div id="o1">B</div>
</div>
<button id="player">Play</button>

<script src="scripts/web-animations-next.min.js"></script>

We add some CSS to make it look pretty. The only special thing here is the use of attribute selector to match the items we want to animate.

body {
    background: #3d6644;
    text-align: center;
}

[id^="i"],
[id^="o"] {
    border-radius: 50%;
    margin-top: 1rem;
    font-size: 2rem;
    color: #f9f7fb;
    display: inline-block;
}

#player {
    margin-top: 3rem;
    font-size: 1rem;
    background: transparent;
    border: 2px solid #f9f7fb;
    color: #f9f7fb;
    padding: .6rem;
    border-radius: .6rem;
}

#player:active {
    transform: scale(.9);
}
#player:disabled {
    opacity: .2;
}

Again, because this is a Javascript API, this will be the largest part of the project.

We create two arrays using slice.call to convert a list of nodes returned by querySelectorAll. Rach array contains elements starting with a different letter (i and o). We also initialize two objects to hold our keyframe effects.

let ms = Array.prototype.slice.call(document.querySelectorAll('[id^=i]'));
let ts = Array.prototype.slice.call(document.querySelectorAll('[id^=o]'));
let keyframeEffects = [];
let keyframeEffects2 = [];

We then define our animation effects. The only thing to notice is the offset attribute for each step: it is a 0-to-1 equivalent to setting the percentages when working with CSS based keyframes. The objective is the same.

The last part of this section initializes the timings for the animations. We’ll use the same timing for both our animations so we keep a single array for the timings of the animations.

let effects = {
  translations1: [
      { transform: 'translateX(0px)', offset: 0 },
      { transform: 'translateX(500px)', offset: .7 },
      { transform: 'translateX(0px)', offset: 1 }
  ],
  translations2: [
      { transform: 'translateX(0px)', offset: 0 },
      { transform: 'translateX(-500px)', offset: .7 },
      { transform: 'translateX(0px)', offset: 1 }
  ]
};
let timing = {
  duration: 1000,
  easing: 'ease-in',
  fill: 'both',
  iterations: 1
};

Next we create keyframe effects and push them to our empty keyframeEffects array. This is how you create multiple objects with the same animation and timing functions. Also be aware that we are using keyframe effects rather than calling animate directly because we want to have more control regarding when we start the animations.

//Create a KeyframeEffect for each element (this will not kick off any animation)
ms.forEach((el) => {
  let effect = new KeyframeEffect(el, effects.translations1, timing);
  keyframeEffects.push(effect);
});
ts.forEach((el) => {
  let effect = new KeyframeEffect(el, effects.translations2, timing);
  keyframeEffects2.push(effect);
});

Using the keyframe effects we just create we create two group effects, one for each set of animations and a group effect to play them together. We only play the last effect we define

//add the six KeyframeEffects to a GroupEffect, and play it on the doucment timeline
let groupEffectA = new GroupEffect(keyframeEffects);
let anim = document.timeline.play(groupEffectA);

let groupEffectB = new GroupEffect(keyframeEffects2);
let anim2 = document.timeline.play(groupEffectB);

The last thing we do is create a button to play/pause the animations. We could create a separate button to control each animation independently but for the purpose of the dmeo one size controls all will be enough.

let btn = document.getElementById('player');

btn.addEventListener('click', function(e) {
    if (anim.playState !== 'running') {
        anim.play();
    } else {
        anim.pause();
    }
    if (anim2.playState !== 'running') {
        anim2.play();
    } else {
        anim2.pause();
    }
});

SequenceEffects

SequenceEffects, as the name implies, plays a group of animations one after the other. As defined in the polyfill, you can use GroupEffect and SequenceEffect together, having a grouping of multiple sequences without using delays or other tricks.

Using the code for our grouping example we’ll change it illustrate how sequences work. We first create two sequences, one for each group of animations, then we create a third sequence containing the two individual sequences.

We change the button to play/pause to only work with the third sequence, the one containing all the keyframe effects we built.

let sequenceEffectA = new SequenceEffect(keyframeEffects);
let sequenceEffectB = new SequenceEffect(keyframeEffects2);

let sequenceEffectC = new SequenceEffect([sequenceEffectA, sequenceEffectB]);
let anim3 = document.timeline.play(sequenceEffectC);

let btn = document.getElementById('player');

btn.addEventListener('click', () => {
    if (anim3.playState !== 'running') {
        anim3.play();
    } else {
        anim3.pause();
    }
});

Examples and demos

Looking at animations again… 3 ways to animate content on your page

We’ve been able to animate content both in 2D and 3D for a while now. The animation effects range from the subtle UX enhancements like material design ripples for buttons and floating action buttons to full on web motion comics, animation exercises and music videos like the example below from Rachel Nabors.

The simplest way of understanding an animation is that it is the change of presentational aspects of an element (height, width, color, etc.) over time. In short, animations are presentation, even if prior to CSS3 Transitions and Animations, they could only be achieved via JavaScript.

See the Pen Complete CSS3 + HTML5 music video by Rachel Nabors (@rachelnabors) on CodePen.

For this post I will concentrate in 2D animations since they are the ones with the biggest return of investment for the work we will do.

The post is divided in three sections:

  1. Using CSS 2D animations
  2. Using the Web Animation API
  3. Using GSAP (Green Sock Animation Platform)

Using CSS 2D animations

The easiest way to create an animation is using CSS. All CSS animations are keyframe based meaning that you write the number of steps that you want to use and what the status for the property you’re animating is at each one of those steps.

At its simplest a web animation looks like this with the following HTML representing the object I want to animate:

<div class='box'></div>

And the CSS below defining three items for our animation

  • The dimensions of the object we want to animate (height and width)
  • An animation property that defines:
    • The name of the animation
    • How long does it last
    • How many times it repeats itself

When defining the element we want to animate we give it the animation properties using the animation property. In the first example we use the animation shorthand property to indicate the name of the animation, how long do we want it to last and the direction we want it to move in (in this case we tell it to alternate forward and back).

/* Defines the object we'll animate */
.box {
  height: 100px;
  width: 100px;
  background-color: rebeccapurple;
  animation: move 2s 5 alternate;
}

You can also specify individual properties for each characteristic of the animation. The example below is equivalent to the one above but spells out each of the properties for the animation.

.box {
  height: 100px;
  width: 100px;
  background-color: rebeccapurple;
  animation-name: move;
  animation-duration: 2s;
  animation-iteration-count: 5;
  animation-direction: alternate;
}

We’ll discuss this in more detaul when we talk about the animation shorthand versus long hand syntax.

We then define the keyframes for our animation as two or more steps indicated by percentages and the condition we want for the property that stag e of the animation. In the example below we define a minimal @keyframes with an ending step (100%) with the background color #ff4136. We could add a starting keyframe (0%) with the initial color but the initial color for our box is already defined so a keyframe with it is not necessary.

@keyframes pulse {
  100% {
    background-color: #ff4136;
  }
}

We can also setup multiple steps of the animation together if they have the same value. In this example both the initial and final keyframes (0% and 100%) have the same background color so we can chain them together using their values in a comma separated list… we could omit the 0% value in our keyframes since the color is initially defined in the selector but I’d rather be redundant than confused.

/* defines the animation keyframes*/
@keyframes pulse {
  0%, 100% {
      background-color: rebeccapurple;
  }  
  50% {
    background-color: #FF4136;
  }
}

How many keyframes do we need?

Thanks to Rachel Nabors for answering questions I had about this.

This is a tricky question that depends a lot on the effect that you want to accomplish. Generally you only need as many keyframes as it takes to express the animation concisely and you can omit initial and ending values (0% and 100%) when it’s safe to do so and depending on the property or properties that you’re animating; if the default value for the property is none rathern than 0 then you do want an initial value.

Compare an animation like the one below with only three steps (Codepen: http://codepen.io/caraya/full/RKWwpR/)

@keyframes move {
  25% {
    margin-left: 25px;
  }

  50% {
    margin-left: 50px;
  }

  100% {
    margin-left: 100px;
  }
}

And the same animation with 10 steps (Codepen: http://codepen.io/caraya/full/JEYKyg/)

@keyframes move {
  10% {
    marging-left: 10px;
  }

  20% {
    margin-left: 20px;
  }

  30% {
    margin-left: 30px;
  }

  40% {
    margin-left: 30px;
  }

  50% {
    margin-left: 50px;
  }

  60% {
    margin-left: 60px;
  }

  70% {
    margin-left: 70px;
  }

  80% {
    margin-left: 80px;
  }

  90% {
    margin-left: 90px;
  }

  100% {
    margin-left: 100px;
  }
}

Again the number of steps for your animation will depend on what your objective is and how smooth your animation has to be. The animation for a three pulse ripple-like effect needs less steps than a polished animation like Rachel Nabors’ Alice in Wonderland.

Step based animation may be another alternative to using (too) many keyframes.

Can we change the way an animation plays?

CSS animations have several timing functions and allows for custom bezier-curve based animations. The attribute that controls the timing function is animation-timing-function.

The predefined values are the following:

.foot {
  animation-timing-function: ease;
}

.foot {
  animation-timing-function: ease-in;
}

.foot {
  animation-timing-function: ease-out;
}

.foot {
  animation-timing-function: ease-in-out;
}

.foot {
  animation-timing-function: linear;
}

.foot {
  animation-timing-function: step-start;
}

.foot {
  animation-timing-function: step-end;
}

We can also create custom animations using custom Bezier Curves to create the type of animation you want to have. Lea Verou create a Bezier Curve Generator to make it easier to generate custom bezier animation values.

/* Function values */
.foot {
  animation-timing-function: cubic-bezier(0.1, 0.7, 1.0, 0.1);
}

To use the tool go to http://cubic-bezier.com/ and play with the curve until you’re happy with the results. Once you’re happy then copy the cubic-bezier element from the site along with the values as the value of your animation-timing-function css.

This is more complex than the predefined values but it’s also the most flexible, if you’re patient enough to play with the values you can get pretty amazing animations for your elements.

The last type of animation I want to talk about is the one I understand the least. Step based animations.

.foo {
  animation-timing-function: steps(4, start);
}

.foo {
  animation-timing-function: steps(4, end);
}

Rather than move the animation between states (for each step in the @keyframes) step based animations work by breaking the animation into a number of steps and a direction where we want the first step of our animation to happen.

the second parameter, direction, needs a little more explaining. The best explanation I found is from designmodo:

The second parameter defines the point at which the action declared in our @keyframes will occur. This value is optional and will default to “end” if left unspecified. A direction of “start” denotes a left-continuous function and our animation’s first step will be completed as soon as the animation begins. It will jump immediately to the end of the first step and stay there until the end of this step duration.

A direction of “end” denotes a right-continuous function and directs the movement to stay put until the duration of the first step is completed. Each option essentially moves the element from a different side and will produce different positioning for the same animation.

Difference btween steps start and end

I’ll update this as I learn more about steps and how to best use them.

Do we need to start the animation right away?

No, we can add delays to animations to make animations start after the page has loaded or create sequences of animations by delaying the starts of different animations to fit the needs of the project.

The animation-delay property controls the time between the element being loaded and the start of the animation sequence.

Let’s take this two divs

<div class='boxes box1'></div>
<div class='boxes box2'></div>

And the following CSS where we perform the following taks:

  • Create a class to style the boxes .boxes
  • Set up the animation characteristics for our first element, .box1
  • Set up the animation characteristics for our second element, .box2. Not that the animation for .box2 will start 2 seconds after the animation for .box1
  • We define the @keyframes for our move animation
.boxes {
  height: 100px;
  width: 100px;
  background-color: rebeccapurple;
}

.box1 {
  animation-name: move;
  animation-duration: 2s;
  animation-iteration-count: 5;
  animation-direction: alternate;
}

.box2 {
  animation-delay: 2s;
  animation-name: move;
  animation-duration: 2s;
  animation-iteration-count: 5;
  animation-direction: alternate;
}

@keyframes move {
  100% {
    margin-left: 1000px;
  }
}

If you see the example (Codepen: http://codepen.io/caraya/full/wgGdeO/) you will see that the second block starts 2 seconds after the first, everything else is identical for both boxes. I use the longhand animation syntax because I don’t trust myself not to trigger the pitfalls from the shorthand syntax.

Do we have any control over pre and post animation state?

There is a property for that 🙂

The animation-fill-mode property defines what values are applied by the animation outside the time it is executing.

That’s a mouthful. Let’s unpack what it really does and what values are available.

The attribute sets which values are applied before/after the animation. For example, you can set the last state of the animation to remain on screen, or you can set it to switch back to before when the animation began.

Possible values

none is the default value. Only time when you’d set it manually is when you’re working with Javascript to change it to or from none to a different value.

See the Pen animation-fill-mode v1 by Carlos Araya (@caraya) on CodePen.

Using forwards as the value of the animation-fill-mode property tells the browser that we want to keep the values of the last keyframe displayed after the animation has finished.

See the Pen animation-fill-mode v2 by Carlos Araya (@caraya) on CodePen.

A value of backwards, upon finishing the animation, gives the element the styles that it had before the animation began.

See the Pen animation-fill-mode v3 by Carlos Araya (@caraya) on CodePen.

In this particular example we did not define an initial value it will revert to the default values for the attributes we are animating (0px and the color assigned in the CSS declaration).

The final value we’ll look at is both. This value tells the browser to apply the effects of both forwards and backwards.

See the Pen animation-fill-mode v4 by Carlos Araya (@caraya) on CodePen.

This is another case when we don’t have a beginning value so rather than take defaults and have to figure out what the results are it just takes the ending values and use those as the values for the animation.

can we play/pause an animation?

Yes, you can but this is another property that is better used as a starting point for workig with Javascript in your animation. animation-play-state controls whether the animation is playing or not. It may also be useful when working with multiple animations that we want to play at different times when used in combinations with delays in animations.

The Pen below, based on Sara Soueidan CSS Reference Demo for animation-play-state adds a button that will toggle adding a .paused class where all we do is add animation-play-state: paused to pause the animation playback.

See the Pen animation-play-state v1 by Carlos Araya (@caraya) on CodePen.

Can we have more than one animation in a given element?

Sure can. As long as we’re mindful of the impact that CSS animations can have on overall browser performance (please don’t blame me if 10 animations bring your page performance to the ground, you’ve been warned) you can attach multiple animations to the same element.

Using our standard box div element shown below

<div class='box' id='box'></div>

And the following CSS to configure the element (size and initial colors) and the animations we want to attach to it. We set a comma separate list of values for all attributes except duration. In this case the browser will follow the spec and duplicate existing values until all the necessary values are filled.

In cases like this I prefer to use the longhand syntax. When working on the demo using shorthand I made so many mistakes that now I’m skittish and prefer the extra typing.

/* Defines the object we'll animate */
.box {
  height: 100px;
  width: 100px;
  background-color: rebeccapurple;
  animation-name: move, pulse;
  animation-duration: 5s, 2s;
  animation-iteration-count: 4, 4;
  animation-direction: alternate;
}

We ten define the keyframes as normal except that this time we define two of them rather than a single one like we’ve done so far.

@keyframes move {
  100% { 
    margin-left: 1000px;
  }
}

@keyframes pulse {
  100% {
    background-color: blue;
  }
}

That’s a lot of writing, is there a shorthand?

Yes, but use it at your own risk. For anything other than simple animations the shorthand syntax can get really confusing really quickly (at least it did for me). The pseudocode for the shorthand is:

.foo {
  animation:
    <animation-name> ||
    <animation-timing-function> || 
    <animation-delay> || 
    <animation-iteration-count> || 
    <animation-direction> || 
    <animation-fill-mode> || 
    <animation-play-state> ||     
}