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);
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);
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">
▲</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.
const duration = 200;
Create a flag of the component state, this flag shows if our component is "collapsed" / "expanded".
let isCollapsed = true;
Create a variable for selecting element.
const el = document.querySelector(".collapse");
Create a variables for button and button's text.
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.
const getBtnText = () => (collapsed ? "collapsed" : "expanded");
Set text and class name to the button.
btnTextEl.innerHTML = getBtnText(isCollapsed);
btn.appendChild(btnTextEl);
toggleClass(btn, "collapsed", isCollapsed);
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);
}
}
Create a function for toggling collapsed value.
function toggleCollapsed(v) {
return !v
}
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`;
}
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";
}
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";
}
});
}
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 = "";
}
});
}
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();
}
}
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);
});
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);
}
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. 🚀 🚀 🚀
Top comments (1)
Good Article. I will try it.