Collapse animation with requestAnimationFrame()
Tags: javascript, html, css
Published: 2021-06-17 07:12:59

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.

```javascript
window.requestAnimationFrame(callback);
```

 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

```javascript
 let animationId;

 animationId = requestAnimationFrame(callback);
 cancelAnimationFrame(animationId);
```

## Let's code
Create a project with html, css and js file. 

### HTML file
```html
<!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>
```
### JS description
Create a constant for saving duration value.
```javascript 
const duration = 200;
```
Create a flag of the component state, this flag shows if our component is "collapsed" / "expanded".

```javascript 
let isCollapsed = true;
```
Create a variable for selecting element.
 
```javascript 
const el = document.querySelector(".collapse");
```
Create a variables for button and button's text.

```javascript 
const btn = document.querySelector(".btn-collapse");
const btnTextEl = document.createElement('span');
```
Then we create a function for toggling button text. This function will return string "collapsed" / "expanded" that depends on collapsed state.

```javascript 
const getBtnText = () => (collapsed ? "collapsed" : "expanded");
```
Set text and class name to the button.
```javascript
btnTextEl.innerHTML = getBtnText(isCollapsed);
btn.appendChild(btnTextEl);
toggleClass(btn, "collapsed", isCollapsed);
```
Creating a function for toggling class name of the button element.
```javascript
function toggleClass(element, className, с) {
  if (с) {
    element.classList.add(className);
  } else {
    element.classList.remove(className);
  }
}
```
Create a function for toggling collapsed value.
```javascript
function toggleCollapsed(v) {
  return !v
}
```
Lets create function which will be called for changing height on slideDown animation.
```javascript
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`;
}
```
The same function we create for changing height of our element on SlideUp event.
```javascript
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";
}
```
And now we are creating the function for sliding down element.
```javascript
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";
    }
  });
}
```
We need to create a function for hiding content and it will be called slideUp.
```javascript
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 = "";
    }
  });
}
```

 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.
```javascript
function showHide(element, c) {
  toggleClass(element, "collapsed", c);

  if (c) {
    slideUp();
  } else {
    slideDown();
  }
}
```

And  adding click event on the button where we will execute toggleCollapsed, toggleClass and showHide functions.
```javascript
btn.addEventListener("click", (e) => {
  e.preventDefault();
  isCollapsed = toggleCollapsed(isCollapsed);
  btnTextEl.innerHTML = getBtnText(isCollapsed);

  toggleClass(e.target, "collapsed", isCollapsed);
  showHide(e.target, isCollapsed);
});
```
#### Adding the style
There is a css style for collapse element.
```css
.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);
}
```
#### All together
{% codesandbox sharp-sara-k5ldw %}

#### Github repo 

{% github GennadiyK/collapse-requestAnimationFrame %}

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

Thank you. :rocket: :rocket: :rocket: