In my project I have been using jQuery slideUp()
to slide up an element in a 200 item list when the user clicks a button. But, as everyone knows, animating CSS height requires a reflow, making the animation jerky. This animation is an integral part of the application and I am willing to go to extensive work to make it work AND work smoothly.
I have decided that CSS transform
is the way to make it work smoothly because of the fact that it is handled on the gpu and on some modern browsers, it is even off the main thread and heavy JS work won't affect the transform. (I do have heavy JS work).
I am looking for a clever solution with CSS transition: transform
to replicate jQuery slideUp
, which just animates the height
property. Below was my attempt, but it seems scale
and translate
do not sync as expected.
$("button").on("click", function() {
$(".collapsible").addClass("collapsed")
setTimeout(function() {
$(".collapsible").removeClass("collapsed")
}, 5000);
});
.list-item {
width: 400px;
height: 100px;
background-color: blue;
margin-top: 10px;
overflow-y: hidden;
}
.collapsible.collapsed .content {
transition: transform 3s linear;
transform: scale(1, 1) translate(0, -100%);
}
.collapsible.collapsed {
transition: transform 3s linear;
transform: translate(0, -50%) scale(1, 0);
}
.collapsible.collapsed ~ .list-item {
transition: transform 3s linear;
transform: translate(0, -100%);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
<button>Collapse</button>
<div class="list-item collapsible">
<div class="content">
Just an example <br>
Just an example <br>
Just an example <br>
Just an example <br>
Just an example <br>
</div>
</div>
<div class="list-item">
</div>
<div class="list-item">
</div>
I played with the values some and got it closer by changing the content
transform to transform: scale(1, 3) translate(0, -50%);
.
It seems I am so close to achieving, but never quite succeeding. Is there any cut and dried trick out there for this?
Requirements:
With a few nights of sleep, I landed on a solution that works precisely as I needed. I had to utilize three layers of divs
and make sure the inner two are set to height: 100%;
. I then do away with scale()
and use transform()
to slide the middle layer up.
This meets my requirements:
Transform()
which should run on the GPU up to 60fpsScale()
SlideUp()
$("button").on("click", function() {
const height = $(".collapsible").css("height");
$(":root").css("--height", height);
$(".collapsible").addClass("collapsed");
setTimeout(function() {
$(".collapsible").removeClass("collapsed");
}, 4000);
});
:root {
--height: unset;
}
.outer-container {
width: 400px;
height: fit-content;
background-color: transparent;
margin-top: 10px;
overflow-y: hidden;
}
.inner-container {
height: 100%;
background-color: blue;
overflow: hidden;
}
.content {
height: 100%;
}
.collapsible.collapsed .inner-container {
transition: transform 3s linear;
transform: translate(0, -100%);
}
.collapsible.collapsed .content {
transition: transform 3s linear;
transform: translate(0, 100%);
}
.collapsible.collapsed ~ .outer-container {
transition: transform 3s linear;
transform: translate(0, calc((var(--height) * -1) - 10px));
}
.collapsible .inner-container {
transition: transform .5s ease-in-out;
transform: translate(0, 0);
}
.collapsible .content {
transition: transform .5s ease-in-out;
transform: translate(0, 0);
}
.collapsible ~ .outer-container {
transition: transform .5s ease-in-out;
transform: translate(0, 0);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
<button>Collapse</button>
<div class="outer-container">
<div class="inner-container">
<div class="content">
Just an example <br>
Just an example <br>
Just an example <br>
Just an example <br>
</div>
</div>
</div>
<div class="outer-container collapsible">
<div class="inner-container">
<div class="content">
Just an example <br>
Just an example <br>
Just an example <br>
Just an example <br>
Just an example <br>
</div>
</div>
</div>
<div class="outer-container">
<div class="inner-container">
<div class="content">
Just an example <br>
Just an example <br>
Just an example <br>
</div>
</div>
</div>
<div class="outer-container">
<div class="inner-container">
<div class="content">
Just an example <br>
Just an example <br>
Just an example <br>
Just an example <br>
</div>
</div>
</div>
Note: for my particular use case, I made the following div slide up far enough to cover the margin. I believe this is how a jQuery slideUp()
function works.
EDIT: Updated snippet so it works with items of different heights by using a css variable and setting it with JS.