Bringing pages to life with web animation

Post by Paul J Picture of Paul J
Reading time 3 mins clock

We’re using animation in a few places on, mostly subtle movements to try and inject a bit of life into the design. The key thing when we were adding these animations was to keep them subtle. If at any point they make the actual content on the site harder to read then we've failed.

On mobile, the menu button animates into a close button when it’s opened. This is fairly simple. When the menu is opened, we add an open class to the button, and use some CSS3 transforms to move things around. The relevant Sass is below, the main thing is we add a 0.3s transition on opacity and transforms, and then just move things when the open class is added.

.navbar-toggle {
  .icon-bar {
    transition: opacity 0.3s, transform 0.3s;

  &.open {
    .icon-bar-1 {
      transform: translate(0, 8px) rotate(45deg);

    .icon-bar-2 {
      opacity: 0;

    .icon-bar-3 {
      transform: translate(0, -8px) rotate(-45deg);

The transforms occur in order from left to right, even though they animate all at once, which is why we move first, and then rotate. Otherwise the movement would be relative to the new rotation.

Our fancy animated icons

Our new service icons are SVGs, which means we can target individual elements within the icon to animate, as long as the SVG is inline and not embedded with an img tag or background-image style. To make life easier we added theme implementations for them on the Drupal side, so they can easily be added and reused. Then we have an icon-development.html.twig file that contains the SVG.

* Implements hook_theme().
function example_theme($existing, $type, $theme, $path) {
  return array(
    'icon_development' => array(
      'variables' => array(),
    // ...

We’re using CSS3 keyframes for the animations themselves, which means we can easily trigger them when we scroll them into view. In our Sass a lot of the values are shared with placeholders, but I’ve tried to simplify them here, and it’s only showing the animation for one part of one of the icons.

#service-development {
  .bracket-left {
    animation-duration: 1s;
    animation-timing-function: ease;
    animation-iteration-count: 1;
    animation-fill-mode: forwards;
    animation-play-state: paused;
    animation-name: developmentBracketLeft;

  &.scrolled-to .bracket-left {
    animation-play-state: running;

@keyframes developmentBracketLeft {
  0% {
    transform: translate(-4px, 0);
  100% {
    transform: translate(0, 0);

Since all of our animations are fairly short, we had to make sure they’d only trigger once they’re visible, otherwise it’s just a waste of time. So we’re using waypoints to add a ’scrolled-to’ class when animations should trigger. Javascript is only used for these triggers, the animations themselves are pure CSS, so they benefit from hardware acceleration. We're using this approach for virtually all of our animations, from the service icons down to subtle fade in effects on things like heading banners, testimonials and client work pages.

(function ($) {
  Drupal.behaviors.magicksServiceAnimation = {
    attach: function (context, settings) {
        offset: 'bottom-in-view',
        handler: function() {

The fade in effect is a combination of 2 transitions. It moves from 0 opacity up to 1, and it moves 100px up and into it's correct position. This particular effect is all wrapped in a Sass mixin, so it can be easily reused on new elements, and only ever needs to be changed in one place.

On the blogs page we have infinite scrolling, with a rotating Ixis logo to show that new content is loading in. Not much to this one: it’s the standard Drupal Ajax progress throbber, we’ve just styled it with CSS and animated with keyframes.


Paul J

Senior Developer

Frontend Developer at Ixis. Making Drupal sites easier to use, more interactive and prettier looking.

Add new comment

Share this article

Sign up to our newsletter!

Our thoughts

Let's work together

Get in touch and find out how we can empower your organisation.
Back to top