Animation Methods for Including and Eradicating Objects From a Stack

No Comments

Animating components with CSS can both be fairly straightforward or fairly tough relying on what you are attempting to do. Altering the background coloration of a button if you hover over it? Straightforward. Animating the place and dimension of a component in a performant method that additionally impacts the place of different components? Tough! That’s precisely what we’ll get into right here on this article.

A typical instance is eradicating an merchandise from a stack of things. The objects stacked on prime have to fall downwards to account for the area of an merchandise faraway from the underside of the stack. That’s how issues behave in actual life, and customers could anticipate this type of life-like movement on a web site. When it doesn’t occur, it’s potential the person is confused or momentarily disorientated. You anticipate one thing to behave a method primarily based on life expertise and get one thing fully totally different, and customers might have further time to course of the unrealistic motion.

Here’s a demonstration of a UI for including objects (click on the button) or eradicating objects (click on the merchandise).

CodePen Embed Fallback

You would paper over the poor UI barely by including a “fade out” animation or one thing, however the consequence gained’t be that nice, because the listing will will abruptly collapse and trigger those self same cognitive points.

Making use of CSS-only animations to a dynamic DOM occasion (including model new components and totally eradicating components) is extraordinarily difficult work. We’re going to face this drawback head-on and go over three very several types of animations that deal with this, all engaging in the identical aim of serving to customers perceive adjustments to an inventory of things. By the point we’re executed, you’ll be armed to make use of these animations, or construct your personal primarily based on the ideas.

We may also contact upon accessibility and the way elaborate HTML layouts can nonetheless retain some compatibility with accessibility units with the assistance of ARIA attributes.

The Slide-Down Opacity Animation

A really fashionable method (and my private favourite) is when newly-added components fade-and-float into place vertically relying on the place they will find yourself. This additionally means the listing must “open up” a spot (additionally animated) to make room for it. If a component is leaving the listing, the spot it took up must contract.

As a result of we now have so many alternative issues happening on the similar time, we have to change our DOM construction to wrap every .list-item in a container class appropriately titled .list-container. That is completely important as a way to get our animation to work.

<ul class=”listing”>
<li class=”list-container”>
<div class=”list-item”>Listing Merchandise</div>
</li>
<li class=”list-container”>
<div class=”list-item”>Listing Merchandise</div>
</li>
<li class=”list-container”>
<div class=”list-item”>Listing Merchandise</div>
</li>
<li class=”list-container”>
<div class=”list-item”>Listing Merchandise</div>
</li>
</ul>

<button class=”add-btn”>Add New Merchandise</button>

Now, the styling for that is unorthodox as a result of, as a way to get our animation impact to work in a while, we have to type our listing in a really particular method that will get the job executed on the expense of sacrificing some customary CSS practices.

.listing {
list-style: none;
}
.list-container {
cursor: pointer;
font-size: 3.5rem;
peak: 0;
list-style: none;
place: relative;
text-align: heart;
width: 300px;
}
.list-container:not(:first-child) {
margin-top: 10px;
}
.list-container .list-item {
background-color: #D3D3D3;
left: 0;
padding: 2rem 0;
place: absolute;
prime: 0;
transition: all 0.6s ease-out;
width: 100%;
}
.add-btn {
background-color: clear;
border: 1px stable black;
cursor: pointer;
font-size: 2.5rem;
margin-top: 10px;
padding: 2rem 0;
text-align: heart;
width: 300px;
}

How you can deal with spacing

First, we’re utilizing margin-top to create vertical area between the weather within the stack. There’s no margin on the underside in order that the opposite listing objects can fill the hole created by eradicating an inventory merchandise. That method, it nonetheless has margin on the underside despite the fact that we now have set the container peak to zero. That further area is created between the listing merchandise that was instantly under the deleted listing merchandise. And that very same listing merchandise ought to transfer up in response to the deleted listing merchandise’s container having zero peak. And since this further area expands the vertical hole between the listing objects additional then we wish it to. In order that’s why we use margin-top — to forestall that from taking place.

However we solely do that if the merchandise container in query isn’t the primary one within the listing. That’s we used :not(:first-child) — it targets all the containers besides the very first one (an enabling selector). We do that as a result of we don’t need the very first listing merchandise to be pushed down from the highest fringe of the listing. We solely need this to occur to each subsequent merchandise thereafter as an alternative as a result of they’re positioned instantly under one other listing merchandise whereas the primary one isn’t.

Now, that is unlikely to make full sense as a result of we’re not setting any components to zero peak in the intervening time. However we’ll in a while, and as a way to get the vertical spacing between the listing components appropriate, we have to set the margin like we do.

A notice about positioning

One thing else that’s value stating is the truth that the .list-item components nested inside the mum or dad .list-container components are set to have a place of absolute, which means that they’re positioned exterior of the DOM and in relation to their relatively-positioned .list-container components. We do that in order that we will get the .list-item factor to drift upwards when eliminated, and on the similar time, get the opposite .list-item components to maneuver and fill the hole that eradicating this .list-item factor has left. When this occurs, the .list-container factor, which isn’t positioned absolute and is due to this fact affected by the DOM, collapses its peak permitting the opposite .list-container components to fill its place, and the .list-item factor — which is positioned with absolute — floats upwards, however doesn’t have an effect on the construction of the listing because it isn’t affected by the DOM.

Dealing with peak

Sadly, we haven’t but executed sufficient to get a correct listing the place the person list-items are stacked one after the other on prime of one another. As a substitute, all we can see in the intervening time is only a single .list-item that represents all the listing objects piled on prime of one another in the very same place. That’s as a result of, though the .list-item components could have some peak through their padding property, their mum or dad components don’t, however have a peak of zero as an alternative. Because of this we don’t have something within the DOM that’s really separating these components out from one another as a result of as a way to try this, we would want our .list-item containers to have some peak as a result of, in contrast to their little one factor, they’re affected by the DOM.

To get the peak of our listing containers to completely match the peak of their little one components, we have to use JavaScript. So, we retailer all of our listing objects inside a variable. Then, we create a operate that is named instantly as quickly because the script is loaded.

This turns into the operate that handles the peak of the listing container components:

const listItems = doc.querySelectorAll(‘.list-item’);

operate calculateHeightOfListContainer(){
};

calculateHeightOfListContainer();

The very first thing that we do is extract the very first .list-item factor from the listing. We will do that as a result of they’re all the identical dimension, so it doesn’t matter which one we use. As soon as we now have entry to it, we retailer its peak, in pixels, through the factor’s clientHeight property. After this, we create a brand new <type> factor that’s prepended to the doc’s physique instantly after in order that we will instantly create a CSS class that includes the peak worth we simply extracted. And with this <type> factor safely within the DOM, we write a brand new .list-container class with types that robotically have precedence over the types declared within the exterior stylesheet since these types come from an precise <type> tag. That provides the .list-container courses the identical peak as their .list-item kids.

const listItems = doc.querySelectorAll(‘.list-item’);

operate calculateHeightOfListContainer() {
const firstListItem = listItems[0];
let heightOfListItem = firstListItem.clientHeight;
const styleTag = doc.createElement(‘type’);
doc.physique.prepend(styleTag);
styleTag.innerHTML = `.list-container{
peak: ${heightOfListItem}px;
}`;
};

calculateHeightOfListContainer();

Exhibiting and Hiding

Proper now, our listing seems a bit drab — the identical because the what we noticed within the first instance, simply with none of the addition or elimination logic, and styled in a very totally different approach to the listing constructed from <ul> and <li> tags listing that have been utilized in that opening instance.

We’re going to do one thing now that will appear inexplicable in the intervening time and modify our .list-container and .list-item courses. We’re additionally creating further styling for each of those courses that can solely be added to them if a brand new class, .present, is used along side each of those courses individually.

The aim we’re doing that is to create two states for each the .list-container and the .list-item components. One state is with out the .present courses on each of those components, and this state represents the weather as they’re animated out from the listing. The opposite state accommodates the .present class added to each of those components. It represents the desired .list-item as firmly instantiated and visual within the listing.

In only a bit, we’ll swap between these two states by including/eradicating the .present class from each the mum or dad and the container of a particular .list-item. We’ll mixed that with a CSS transition between these two states.

Discover that combining the .list-item class with the .present class introduces some further types to issues. Particularly, we’re introducing the animation that we’re creating the place the listing merchandise fades downwards and into visibility when it’s added to the listing — the alternative occurs when it’s eliminated. Because the most performant approach to animate components positions is with the remodel property, that’s what we’ll use right here, making use of opacity alongside the way in which to deal with the visibility half. As a result of we already utilized a transition property on each the .list-item and the .list-container components, a transition robotically takes place at any time when we add or take away the .present class to each of those components as a result of further properties that the .present class brings, inflicting a transition at any time when we both add or take away these new properties.

.list-container {
cursor: pointer;
font-size: 3.5rem;
peak: 0;
list-style: none;
place: relative;
text-align: heart;
width: 300px;
}
.list-container.present:not(:first-child) {
margin-top: 10px;
}
.list-container .list-item {
background-color: #D3D3D3;
left: 0;
opacity: 0;
padding: 2rem 0;
place: absolute;
prime: 0;
remodel: translateY(-300px);
transition: all 0.6s ease-out;
width: 100%;
}
.list-container .list-item.present {
opacity: 1;
remodel: translateY(0);
}

In response to the .present class, we’re going again to our JavaScript file and altering our solely operate in order that the .list-container factor are solely given a peak property if the factor in query additionally has a .present class on it as effectively, Plus, we’re making use of a transition property to our customary .list-container components, and we’ll do it in a setTimeout operate. If we didn’t, then our containers would animate on the preliminary web page load when the script is loaded, and the heights are utilized the primary time, which isn’t one thing we wish to occur.

const listItems = doc.querySelectorAll(‘.list-item’);
operate calculateHeightOfListContainer(){
const firstListItem = listItems[0];
let heightOfListItem = firstListItem.clientHeight;
const styleTag = doc.createElement(‘type’);
doc.physique.prepend(styleTag);
styleTag.innerHTML = `.list-container.present {
peak: ${heightOfListItem}px;
}`;
setTimeout(operate() {
styleTag.innerHTML += `.list-container {
transition: all 0.6s ease-out;
}`;
}, 0);
};
calculateHeightOfListContainer();

Now, if we return and examine the markup in DevTools, then we must always have the ability to see that the listing has disappeared and all that’s left is the button. The listing hasn’t disappeared as a result of these components have been faraway from the DOM; it has disappeared due to the .present class which is now a required class that should be added to each the .list-item and the .list-container components to ensure that us to have the ability to view them.

The best way to get the listing again could be very easy. We add the .present class to all of our .list-container components in addition to the .list-item components contained inside. And as soon as that is executed we must always have the ability to see our pre-created listing objects again of their common place.

<ul class=”listing”>
<li class=”list-container present”>
<div class=”list-item present”>Listing Merchandise</div>
</li>
<li class=”list-container present”>
<div class=”list-item present”>Listing Merchandise</div>
</li>
<li class=”list-container present”>
<div class=”list-item present”>Listing Merchandise</div>
</li>
<li class=”list-container present”>
<div class=”list-item present”>Listing Merchandise</div>
</li>
</ul>

<button class=”add-btn”>Add New Merchandise</button>

We gained’t have the ability to work together with something but although as a result of to do this — we have to add extra to our JavaScript file.

The very first thing that we are going to do after our preliminary operate is declare references to each the button that we click on so as to add a brand new listing merchandise, and the .listing factor itself, which is the factor that wraps round each single .list-item and its container. Then we choose each single .list-container factor nested inside the mum or dad .listing factor and loop by all of them with the forEach methodology. We assign a technique on this callback, removeListItem, to the onclick occasion handler of every .list-container. By the tip of the loop, each single .list-container instantiated to the DOM on a brand new web page load calls this similar methodology at any time when they’re clicked.

As soon as that is executed, we assign a technique to the onclick occasion handler for addBtn in order that we will activate code after we click on on it. However clearly, we gained’t create that code simply but. For now, we’re merely logging one thing to the console for testing.

const addBtn = doc.querySelector(‘.add-btn’);
const listing = doc.querySelector(‘.listing’);
operate removeListItem(e){
console.log(‘Deleted!’);
}
// DOCUMENT LOAD
doc.querySelectorAll(‘.listing .list-container’).forEach(operate(container) {
container.onclick = removeListItem;
});

addBtn.onclick = operate(e){
console.log(‘Add Btn’);
}

Beginning work on the onclick occasion handler for addBtn, the very first thing that we wish to do is create two new components: container and listItem. Each components characterize the .list-item factor and their respective .list-container factor, which is why we assign these actual courses to them as quickly as we create the them.

As soon as these two components are ready, we use the append methodology on the container to insert the listItem inside it as a baby, the identical as how these components which are already within the listing are formatted. With the listItem efficiently appended as a baby to the container, we will transfer the container factor together with its little one listItem factor to the DOM with the insertBefore methodology. We do that as a result of we wish new objects to look on the backside of the listing however earlier than the addBtn, which wants to remain on the very backside of the listing. So, through the use of the parentNode attribute of addBtn to focus on its mum or dad, listing, we’re saying that we wish to insert the factor as a baby of listing, and the kid that we’re inserting (container) shall be inserted earlier than the kid that’s already on the DOM and that we now have focused with the second argument of the insertBefore methodology, addBtn.

Lastly, with the .list-item and its container efficiently added to the DOM, we will set the container’s onclick occasion handler to match the identical methodology as each different .list-item already on the DOM by default.

addBtn.onclick = operate(e){
const container = doc.createElement(‘li’);
container.classList.add(‘list-container’);
const listItem = doc.createElement(‘div’);
listItem.classList.add(‘list-item’);
listItem.innerHTML = ‘Listing Merchandise’;
container.append(listItem);
addBtn.parentNode.insertBefore(container, addBtn);
container.onclick = removeListItem;
}

If we do this out, then we gained’t have the ability to see any adjustments to our listing regardless of what number of occasions we click on the addBtn. This isn’t an error with the clicking occasion handler. Issues are working precisely how they need to be. The .list-item components (and their containers) are added to the listing within the appropriate place, it’s simply that they’re getting added with out the .present class. Consequently, they don’t have any peak to them, which is why we will’t see them and is why it seems like nothing is occurring to the listing.

To get every newly added .list-item to animate into the listing at any time when we click on on the addBtn, we have to apply the .present class to each the .list-item and its container, simply as we needed to do to view the listing objects already hard-coded into the DOM.

The issue is that we can’t simply add the .present class to those components immediately. If we did, the brand new .list-item statically pops into existence on the backside of the listing with none animation. We have to register just a few types earlier than the animation further types that override these preliminary types for a component to know what transition to make. Which means, that if we simply apply the .present class to are already in place — so no transition.

The answer is to use the .present courses in a setTimeout callback, delaying the activation of the callback by 15 milliseconds, or 1.5/one hundredth of a second. This imperceptible delay is lengthy sufficient to create a transition from the proviso state to the brand new state that’s created by including the .present class. However that delay can also be brief sufficient that we are going to by no means know that there was a delay within the first place.

addBtn.onclick = operate(e){
const container = doc.createElement(‘li’);
container.classList.add(‘list-container’);
const listItem = doc.createElement(‘div’);
listItem.classList.add(‘list-item’);
listItem.innerHTML = ‘Listing Merchandise’;
container.append(listItem);
addBtn.parentNode.insertBefore(container, addBtn);
container.onclick = removeListItem;
setTimeout(operate(){
container.classList.add(‘present’);
listItem.classList.add(‘present’);
}, 15);
}

Success! It’s now time to deal with how we take away listing objects when they’re clicked.

Eradicating listing objects shouldn’t be too laborious now as a result of we now have already gone by the tough process of including them. First, we have to make it possible for the factor we’re coping with is the .list-container factor as an alternative of the .list-item factor. As a consequence of occasion propagation, it’s seemingly that the goal that triggered this click on occasion was the .list-item factor.

Since we wish to cope with the related .list-container factor as an alternative of the particular .list-item factor that triggered the occasion, we’re utilizing a while-loop to loop one ancestor upwards till the factor held in container is the .list-container factor. We all know it really works when container will get the .list-container class, which is one thing that we will uncover through the use of the accommodates methodology on the classList property of the container factor.

As soon as we now have entry to the container, we promptly take away the .present class from each the container and its .list-item as soon as we now have entry to that as effectively.

operate removeListItem(e) {
let container = e.goal;
whereas (!container.classList.accommodates(‘list-container’)) {
container = container.parentElement;
}
container.classList.take away(‘present’);
const listItem = container.querySelector(‘.list-item’);
listItem.classList.take away(‘present’);
}

And right here is the completed consequence:

CodePen Embed Fallback

Accessibility & Efficiency

Now it’s possible you’ll be tempted to only depart the mission right here as a result of each listing additions and removals ought to now be working. However you will need to take into account that this performance is simply floor degree and there are undoubtedly some contact ups that have to be made as a way to make this a whole package deal.

Initially, simply because the eliminated components have pale upwards and out of existence and the listing has contracted to fill the hole that it has left behind doesn’t imply that the eliminated factor has been faraway from the DOM. In actual fact, it hasn’t. Which is a efficiency legal responsibility as a result of it implies that we now have components within the DOM that serve no function aside from to only accumulate within the background and decelerate our software.

To unravel this, we use the ontransitionend methodology on the container factor to take away it from the DOM however solely when the transition brought on by us eradicating the .present class has completed in order that its elimination couldn’t probably interrupt our transition.

operate removeListItem(e) {
let container = e.goal;
whereas (!container.classList.accommodates(‘list-container’)) {
container = container.parentElement;
}
container.classList.take away(‘present’);
const listItem = container.querySelector(‘.list-item’);
listItem.classList.take away(‘present’);
container.ontransitionend = operate(){
container.take away();
}
}

We shouldn’t have the ability to see any distinction at this level as a result of allwe did was enhance the efficiency — no styling updates.

The opposite distinction can also be unnoticeable, however tremendous vital: compatibility. As a result of we now have used the proper <ul> and <li> tags, units should not have any drawback with appropriately deciphering what we now have created as an unordered listing.

Different concerns for this method

An issue that we do have nevertheless, is that units could have an issue with the dynamic nature of our listing, like how the listing can change its dimension and the variety of objects that it holds. A brand new listing merchandise shall be fully ignored and eliminated listing objects shall be learn as in the event that they nonetheless exist.

So, as a way to get units to re-interpret our listing at any time when the scale of it adjustments, we have to use ARIA attributes. They assist get our nonstandard HTML listing to be acknowledged as such by compatibility units. That mentioned, they don’t seem to be a assured answer right here as a result of they’re by no means pretty much as good for compatibility as a local tag. Take the <ul> tag for instance — no want to fret about that as a result of we have been ready to make use of the native unordered listing factor.

We will use the aria-live attribute to the .listing factor. The whole lot nested inside a piece of the DOM marked with aria-live turns into responsive. In different phrases, adjustments made to a component with aria-live is acknowledged, permitting them to challenge an up to date response. In our case, we wish issues extremely reactive and we try this be setting the aria stay attribute to assertive. That method, at any time when a change is detected, it’s going to accomplish that, interrupting no matter process it was presently doing on the time to instantly touch upon the change that was made.

<ul class=”listing” function=”listing” aria-live=”assertive”>

The Collapse Animation

It is a extra refined animation the place, as an alternative of listing objects floating both up or down whereas altering opacity, components as an alternative simply collapse or broaden outwards as they steadily fade in or out; in the meantime, the remainder of the listing repositions itself to the transition happening.

The cool factor concerning the listing (and maybe some remission for the verbose DOM construction we created), can be the truth that we will change the animation very simply with out interfering with the principle impact.

So, to realize this impact, we begin of by hiding overflow on our .list-container. We do that in order that when the .list-container collapses in on itself, it does so with out the kid .list-item flowing past the listing container’s boundaries because it shrinks. Aside from that, the one different factor that we have to do is take away the remodel property from the .list-item with the .present class since we don’t need the .list-item to drift upwards anymore.

.list-container {
cursor: pointer;
font-size: 3.5rem;
peak: 0;
overflow: hidden;
list-style: none;
place: relative;
text-align: heart;
width: 300px;
}
.list-container.present:not(:first-child) {
margin-top: 10px;
}
.list-container .list-item {
background-color: #D3D3D3;
left: 0;
opacity: 0;
padding: 2rem 0;
place: absolute;
prime: 0;
transition: all 0.6s ease-out;
width: 100%;
}
.list-container .list-item.present {
opacity: 1;
}

CodePen Embed Fallback

The Facet-Slide Animation

This final animation method is strikingly totally different fromithe others in that the container animation and the .list-item animation are literally out of sync. The .list-item is sliding to the correct when it’s faraway from the listing, and sliding in from the correct when it’s added to the listing. There must be sufficient vertical room within the listing to make method for a brand new .list-item earlier than it even begins animating into the listing, and vice versa for the elimination.

As for the styling, it’s very very similar to the Slide Down Opacity animation, solely factor that the transition for the .list-item needs to be on the x-axis now as an alternative of the y-axis.

.list-container {
cursor: pointer;
font-size: 3.5rem;
peak: 0;
list-style: none;
place: relative;
text-align: heart;
width: 300px;
}
.list-container.present:not(:first-child) {
margin-top: 10px;
}
.list-container .list-item {
background-color: #D3D3D3;
left: 0;
opacity: 0;
padding: 2rem 0;
place: absolute;
prime: 0;
remodel: translateX(300px);
transition: all 0.6s ease-out;
width: 100%;
}
.list-container .list-item.present {
opacity: 1;
remodel: translateX(0);
}

As for the onclick occasion handler of the addBtn in our JavaScript, we’re utilizing a nested setTimeout methodology to delay the start of the listItem animation by 350 milliseconds after its container factor has already began transitioning.

setTimeout(operate(){
container.classList.add(‘present’);
setTimeout(operate(){
listItem.classList.add(‘present’);
}, 350);
}, 10);

Within the removeListItem operate, we take away the listing merchandise’s .present class first so it may well start transitioning instantly. The mum or dad container factor then loses its .present class, however solely 350 milliseconds after the preliminary listItem transition has already began. Then, 600 milliseconds after the container factor begins to transition (or 950 milliseconds after the listItem transition), we take away the container factor from the DOM as a result of, by this level, each the listItem and the container transitions ought to have come to an finish.

operate removeListItem(e){
let container = e.goal;
whereas(!container.classList.accommodates(‘list-container’)){
container = container.parentElement;
}
const listItem = container.querySelector(‘.list-item’);
listItem.classList.take away(‘present’);
setTimeout(operate(){
container.classList.take away(‘present’);
container.ontransitionend = operate(){
container.take away();
}
}, 350);
}

Right here is the tip consequence:

CodePen Embed Fallback

That’s a wrap!

There you may have it, three totally different strategies for animating objects which are added and faraway from a stack. I hope that with these examples you are actually assured to work in a state of affairs the place the DOM construction settles into a brand new place in response to a component that has both been added or faraway from the DOM.

As you’ll be able to see, there’s a variety of transferring elements and issues to think about. We began with that we anticipate from such a motion in the true world and thought of what occurs to a bunch of components when one in all them is up to date. It took a bit balancing to transition between the exhibiting and hiding states and which components get them at particular occasions, however we bought there. We even went as far as to verify our listing is each performant and accessible, issues that we’d undoubtedly have to deal with on an actual mission.

Anyway, I want you all the very best in your future initiatives. And that’s all from me. Over and out.

The publish Animation Methods for Including and Eradicating Objects From a Stack appeared first on CSS-Tips. You possibly can assist CSS-Tips by being an MVP Supporter.

    About Marketing Solution Australia

    We are a digital marketing company with a focus on helping our customers achieve great results across several key areas.

    Request a free quote

    We offer professional SEO services that help websites increase their organic search score drastically in order to compete for the highest rankings even when it comes to highly competitive keywords.

    Subscribe to our newsletter!

    More from our blog

    See all posts

    Leave a Comment