Sliding Between Views With Transform

Moving users between views, like a list-to-details flow, benefits from some motion to keep context and add polish. The details of that transition depend on the views themselves: a modal overlay sliding up is a different job than a full-screen list-and-detail handoff.

Whatever the effect, three principles apply:

  • Animate with translations, never with left, top, or other layout-triggering properties.
  • Keep durations short and the motion snappy.
  • Reconsider the design as screens grow; an effect that fits a phone can look wrong on desktop.

Build the slide with transforms

A common pattern has a list view and a details view inside a container. Tapping a list item slides the details view in while the list slides out.

View hierarchy.

Start with a container that holds both views side by side. Setting overflow: hidden keeps both inside without a horizontal scrollbar while each view slides.

The container CSS:

.container {
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;
}

The relative position lets each child sit absolutely at the top-left corner and move with transforms. That avoids the layout and paint work that using left would trigger, and keeps the math easier to follow.

.view {
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;

  /* let the browser know we plan to animate
     each view in and out */
  will-change: transform;
}

Add a transition on the transform property. A custom cubic-bezier curve gives a more polished feel:

.view {
  transition: transform 0.3s cubic-bezier(0.465, 0.183, 0.153, 0.946);
}

The offscreen view, in this case the details view, gets translated fully to the right:

.details-view {
  transform: translateX(100%);
}

Toggling classes on the views is handled with minimal JavaScript:

var container = document.querySelector('.container');
var backButton = document.querySelector('.back-button');
var listItems = document.querySelectorAll('.list-item');

/**
    * Toggles the class on the container so that
    * we choose the correct view.
    */
function onViewChange(evt) {
    container.classList.toggle('view-change');
}

// When you click a list item, bring on the details view.
for (var i = 0; i < listItems.length; i++) {
    listItems[i].addEventListener('click', onViewChange, false);
}

// And switch it back again when you click the back button
backButton.addEventListener('click', onViewChange);

The classes themselves get their own CSS declarations:

.view-change .list-view {
  transform: translateX(-100%);
}

.view-change .details-view {
  transform: translateX(0);
}

The approach extends to more than two views: keep every non-visible view translated offscreen and brought on as needed, moving the currently visible one off. The same technique works for slide-in elements like sidebar navigation; the only difference is other views don't need to move.

Adapt to larger screens

On a large screen, a side-by-side list and detail layout suggests a different choreography. Rather than remove the list when navigation happens, keep it in place and slide the details view from the right, much like a navigation panel.