Collapsing Page Effect

Let your site fall to pieces

Create an animation just like the oneafter logging out of Bonboarding

Fun with the Logout Animation

The other day I was working on my startup, and wanted to spice things up a bit so I created a collapsing page animation for the logout functionality. Nothing fancy, some CSS transition animation. But when I posted it on Twitter, it got viral, especially after it was retweeted by Smashing Magazine.

I was totally mind-blown by the engagement, and all the positive feedback (this was my first viral content). Many of the people asked me to share the code, but instead of just publishing it on github (which I did, and you can access it as a NPM package here - available both for React or plain JavaScript) I decided to write a brief article about it.

The Not-So-Complicated Code

As a start, I wanted body's all child elements to collapse, and also all div's. I didn't want to put animation on all elements (eg. headers, links, buttons etc) because I felt it would make the animation too fractured.

js
const elements = document.querySelectorAll('body > *, body div');

To make sure that the page doesn't get scrolled, I set the position to fixed. I also disabled pointer events, so no clicks or other events get triggered during the animation:

js
document.body.style.overflow = 'hidden';
document.body.style.pointerEvents = 'none';

Finally, before dealing with the actual, I had to measure the total height of the page (to know, how much should the items "fall" to ensure that all items will be out of the screen at the end):

js
const body = document.body;
const html = document.documentElement;

const height = Math.max(
  body.scrollHeight,
  body.offsetHeight,
  html.clientHeight,
  html.scrollHeight,
  html.offsetHeight,
);

So the animation is actually super simple: just loop through the selected elements and generate some semi-random values, then add them as CSS attributes:

js
[...elements].forEach(element => {
  const delay = Math.random() * 3000); // wait between 0 and 3 seconds
  const speed = Math.random() * 3000 + 2000; // speed between 2 and 5 seconds
  const rotate = Math.random() * 30 - 15; // rotate with max 15 degrees to either direction
  const moveX = Math.random() * 160 - 80; // move with 80px to either direction

  element.style.transition = `transform ${speed}ms ease-out`;
  element.style.transitionDelay = `${delay}ms`;
  element.style.transform = `translateY(${height * 1.5}px) translateX(${moveX}px) rotate(${rotate}deg)`;
});

This loop just goes through every element and assigns random values for them.

All of the elements will be transitioned downward with the height of the screen, therefore even the ones at the top of your page will end up out of the screen at the end.

Finally, I wanted to keep one item that stayed on the screen behind the collapsing page:

There are a few important things with it:

  • it should be a child of the body, so it's parent element is not collapsing
  • it should have fixed position
  • to achieve the effect that it's in the background behind everything else, you can adjust the z-index

And then just ignore it and it's children elements in the forEach loop:

js
// Identify the logout screen that should stay in place
const logoutEl = document.querySelector('#logout-screen');

// Function that tells if an element is a
// descendant (children, grandchildren etc) of another element
const isDescendant = (parent, child) => {
  let node = child.parentNode;
  while (node !== null) {
    if (node === parent) {
      return true;
    }
    node = node.parentNode;
  }
  return false;
};

// And the updated forEach loop:
[...elements].forEach(element => {
  if (element === logoutEl || isDescendant(logoutEl, element)) {
    element.style.pointerEvents = 'all'; // this element should detect clicks
    return; // don't continue adding the animation
  }

  // ... add the animation for the other items
});

This is the basic logic, it's quite simple and all animations are handled by CSS transitions.

Here's the final code:

js
function collapsePage() {
  const elements = document.querySelectorAll('body > *, body div');
  const logoutEl = document.querySelector('#logout-screen');

  const body = document.body;
  const html = document.documentElement;

  const height = Math.max(
    body.scrollHeight,
    body.offsetHeight,
    html.clientHeight,
    html.scrollHeight,
    html.offsetHeight,
  );

  document.body.style.overflow = 'hidden';
  document.body.style.pointerEvents = 'none';

  const isDescendant = (parent, child) => {
    let node = child.parentNode;
    while (node !== null) {
      if (node === parent) {
        return true;
      }
      node = node.parentNode;
    }
    return false;
  };

  [...elements].forEach(element => {
    if (element === logoutEl || isDescendant(logoutEl, element)) {
      element.style.pointerEvents = 'all';
      return;
    }

    element.style.pointerEvents = 'none';

    const delay = Math.random() * 3000; // wait between 0 and 3 seconds
    const speed = Math.random() * 3000 + 2000; // speed between 2 and 5 seconds
    const rotate = Math.random() * 30 - 15; // rotate with max 10 degrees
    const moveX = Math.random() * 160 - 80; // move with 50px to either direction

    element.style.transition = `transform ${speed}ms ease-out`;
    element.style.transitionDelay = `${delay}ms`;
    element.style.transform = `translateY(${height *
      1.5}px) translateX(${moveX}px) rotate(${rotate}deg)`;
  });
}

Things to Consider

After the animation is done, all your elements will still be available in the DOM, just transitioned out of the screen. It is not a problem if you will navigate to another page after, but it might cause unexpected behavior if you use some libraries that handle the navigation for you (eg. react-router-dom).

To solve this issue, I added a reset function to the component, that is triggered on unmounting.

Information
You can grab the whole code as an NPM package - it can be used both as a React component or as a standalone JavaScript function.

While this animation can bring some unexpected delight to your users, be careful with it. Don't overuse, as the animation takes a few seconds each time. I recommend only using it for logouts, or when the user deletes something in your web-app (eg. a large project, or even the user's profile).

Ready to boost your user adoption?
Get in touch or create your free account!
Get Started Free