DEV Community

loading...
Cover image for Collapse animation with requestAnimationFrame()

Collapse animation with requestAnimationFrame()

Gena Kainovskiy
I'm an experienced frontend developer.
ใƒป5 min read

Hi, in this article I will show you how to do collapse animation with requestAnimationFrame.

What is requestAnimationFrame()

It is a window method and it tells the browser that you are going to do an animation.

window.requestAnimationFrame(callback);
Enter fullscreen mode Exit fullscreen mode

It takes a callback function and this callback will be executed when it is the time to update animation and browser has enough resources to do a repaint.

  • requestAnimationFrame stops working in the background browser tabs for improving performance and battery time working.

  • requestAnimationFrame(callback) returns ID and this ID saves a callback and can be used for canceling animation

 let animationId;

 animationId = requestAnimationFrame(callback);
 cancelAnimationFrame(animationId);
Enter fullscreen mode Exit fullscreen mode

Let's code

Create a project with html, css and js file.

HTML file

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./style.css">
</head>
<body>
    <button type="button" class="btn-collapse"><i class="icon"> 
      &#9650;</i>
    </button>
    <div class="collapse">
      Lorem Ipsum is simply dummy text of the printing and typesetting industry.
      Lorem Ipsum has been the industry's standard dummy text ever since the
      1500s, when an unknown printer took a galley of type and scrambled it to
      make a type specimen book. It has survived not only five centuries, but
      also the leap into electronic typesetting, remaining essentially
      unchanged. It was popularised in the 1960s with the release of Letraset
      sheets containing Lorem Ipsum passages, and more recently with desktop
      publishing software like Aldus PageMaker including versions of Lorem
      Ipsum.
    </div>
    <p>
      next line
    </p>
    <script src="./animation.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

JS description

Create a constant for saving duration value.

const duration = 200;
Enter fullscreen mode Exit fullscreen mode

Create a flag of the component state, this flag shows if our component is "collapsed" / "expanded".

let isCollapsed = true;
Enter fullscreen mode Exit fullscreen mode

Create a variable for selecting element.

const el = document.querySelector(".collapse");
Enter fullscreen mode Exit fullscreen mode

Create a variables for button and button's text.

const btn = document.querySelector(".btn-collapse");
const btnTextEl = document.createElement('span');
Enter fullscreen mode Exit fullscreen mode

Then we create a function for toggling button text. This function will return string "collapsed" / "expanded" that depends on collapsed state.

const getBtnText = () => (collapsed ? "collapsed" : "expanded");
Enter fullscreen mode Exit fullscreen mode

Set text and class name to the button.

btnTextEl.innerHTML = getBtnText(isCollapsed);
btn.appendChild(btnTextEl);
toggleClass(btn, "collapsed", isCollapsed);
Enter fullscreen mode Exit fullscreen mode

Creating a function for toggling class name of the button element.

function toggleClass(element, className, ั) {
  if (ั) {
    element.classList.add(className);
  } else {
    element.classList.remove(className);
  }
}
Enter fullscreen mode Exit fullscreen mode

Create a function for toggling collapsed value.

function toggleCollapsed(v) {
  return !v
}
Enter fullscreen mode Exit fullscreen mode

Lets create function which will be called for changing height on slideDown animation.

function incrementHeight(el, progress) {
  /**
   * As arguments this function takes el (there is our 
   * collapse element) and 
   * progress (there is a count that we will get when we run 
   * slideDown animation) 
   */

  /**
   * We set to the height the value that will be increased 
   * from 0 to the scrollHeight of our element.
   */

  /**
   * We set this height to the style attribute of our element 
   * and as a result our element will be expanded.
   */
  el.style.height = `${progress * el.scrollHeight}px`;
}
Enter fullscreen mode Exit fullscreen mode

The same function we create for changing height of our element on SlideUp event.

function decrementHeight(el, progress) {
  /**
   * In this case we also override the height of the element 
   * but we need to hide element, 
   * so we subtract from the height value that was calculated 
   * progress * el.scrollHeight 
   *  */  
  height = el.scrollHeight - progress * el.scrollHeight;
  el.style.height = `${el.scrollHeight - progress * el.scrollHeight}px`;
  el.style.overflow = "hidden";
}
Enter fullscreen mode Exit fullscreen mode

And now we are creating the function for sliding down element.

function slideDown() {
  /**
   * First of all we need to save time when slideDown() was 
   * executed. 
   *
   */
  const start = performance.now();

  /**
   * Then we execute requestAnimationFrame with the callback 
   * function. For creating animation affect we should call 
   * this callback recursively.
   */
  requestAnimationFrame(function animate(time) {
    /**
     * Callback of requestAnimationFrame has the time 
     * argument, it is the timestamp.
     * Timestamp means the point in time when 
     * requestAnimationFrame() starts to execute callback 
     * functions.
     */
    /**
     * Create a variable for runtime. We get Runtime if 
     * we subtract start time from timestamp
     */
    const runtime = time - start;

    /**
     * Then we create a variable for progress in terms of 
     * duration. 
     * relativeProgress - is 0 then it is tart and 1 when it 
     * is 100% of animation result
     */
    const relativeProgress = runtime / duration;

    /**
     * We use Math.min() for avoiding situation when 
     * relativeProgress will be more than 1. 
     * 
     */
    const process = Math.min(relativeProgress, 1);

    /**
     * Then we create a statement for executing animate 
     * callback recursively by requestAnimationFrame
     *  It will be executed if process less than 1.
     */
    if (process < 1) {
      /**
       * In this statement we increment a height and 
       * calling requestAnimationFrame(animate).
       */
      incrementHeight(el, process);
      requestAnimationFrame(animate);
    }

    /**
     * We need to set height auto and reset overflow hidden if 
     * the process is 1. 
     * Cos it means the end of animation and if our container 
     * have a fixed height and 
     * overflow hidden we can meat an issue when the content 
     * in our element is more then height and
     * it will be cut.
     */
    if (process === 1) {
      el.style.height = "auto";
      el.style.overflow = "initial";
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

We need to create a function for hiding content and it will be called slideUp.

function slideUp() {
  /**
   * The same as in slideDown().
   */
  const start = performance.now();
  requestAnimationFrame(function animate(time) {
    const runtime = time - start;
    const relativeProgress = runtime / duration;
    const process = Math.min(relativeProgress, 1);
    if (process < 1) {
      /**
       * In this statement we decrease the element height.
       */
      decrementHeight(el, process);
      requestAnimationFrame(animate);
    }
    /**
     * And at the end of our animation we remove style 
     * attribute with height and overflow,
     * cos we have this necessary style in css file.
     */
    if (process === 1) {
      el.style.height = "";
      el.style.overflow = "";
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

So the last step with programming we create a show/hide function. In this function we will call slideUp / slideDown if collapsed value true / false.

function showHide(element, c) {
  toggleClass(element, "collapsed", c);

  if (c) {
    slideUp();
  } else {
    slideDown();
  }
}
Enter fullscreen mode Exit fullscreen mode

And adding click event on the button where we will execute toggleCollapsed, toggleClass and showHide functions.

btn.addEventListener("click", (e) => {
  e.preventDefault();
  isCollapsed = toggleCollapsed(isCollapsed);
  btnTextEl.innerHTML = getBtnText(isCollapsed);

  toggleClass(e.target, "collapsed", isCollapsed);
  showHide(e.target, isCollapsed);
});
Enter fullscreen mode Exit fullscreen mode

Adding the style

There is a css style for collapse element.

.collapse {
  height: 0;
  overflow: hidden;
}

.btn-collapse {
  background: rgb(222, 222, 222);
  padding: 1rem;
  display: block;
  text-align-last: left;
  box-shadow: none;
  border: 1px solid #ccc;
  border-radius: 3px;
  cursor: pointer;
  width: 100%;
}

.btn-collapse span {
  pointer-events: none;
}

.icon {
  display: inline-block;
  transform: rotate(180deg);
  pointer-events: none;
}

.collapsed .icon {
  transform: rotate(0deg);
}
Enter fullscreen mode Exit fullscreen mode

All together

Github repo

Collapse with requestAnimationFrame

There is an implementation of Collapse component with requestAnimationFrame.

I guess it will help you to understand better the requestAnimationFrame(). Left your comments.

Thank you. ๐Ÿš€ ๐Ÿš€ ๐Ÿš€

Discussion (1)

Collapse
ramikoff profile image
Ramil Novruzov

Good Article. I will try it.