Free Web Design Code & Scripts

Add/Remove Cards with View Transition in Vanilla JS

Add/Remove Cards With View Transition in Vanilla JS
Code Snippet:Add/Remove Cards with View Transitions (using view-transition-class )
Author: Bramus
Published: 5 months ago
Last Updated: 5 months ago
Downloads: 164
License: MIT
Edit Code online: View on CodePen
Read More

This tutorial will guide you through the process of how to Add/remove Cards With View Transition In Vanilla Js. Leveraging the View Transition API, you can create smooth and engaging animations when adding or removing elements from your web page, enhancing the user experience with visual continuity. This approach is particularly useful for dynamic content updates, providing a polished and modern feel to your web applications.

Setting Up the HTML Structure

First, we need to structure our HTML to include a button for adding new cards, a template for the card elements, and a container to hold the cards. Additionally, we’ll include a warning message for browsers that don’t support view transitions.

<button class="add-btn">
		<span class="sr-only">Add</span>
	</button>

	<template id="card">
		<li class="card">
		<button class="delete-btn">
		  <span class="sr-only">Delete</span>
		</button>
	  </li>
	</template>

	<ul class="cards">
	  <li class="card" style="view-transition-name: card-1; background-color: tan;">
		<button class="delete-btn">
		  <span class="sr-only">Delete</span>
		</button>
	  </li>
	  <li class="card" style="view-transition-name: card-2; background-color: khaki;">
		<button class="delete-btn">
		  <span class="sr-only">Delete</span>
		</button>
	  </li>
	  <li class="card" style="view-transition-name: card-3; background-color: thistle;">
		<button class="delete-btn">
		  <span class="sr-only">Delete</span>
		</button>
	  </li>
	  <li class="card" style="view-transition-name: card-4; background-color: wheat;">
		<button class="delete-btn">
		  <span class="sr-only">Delete</span>
		</button>
	  </li>
	</ul>

	<div class="warning">
		<p>Your browser does not support <code>view-transtion-class: &lt;custom-ident&gt;+</code>. As a result, the existing cards will not bounce upon inserting/deleting a card.</p>
	</div>

	<footer>
		<p>Icons from <a href="https://www.iconfinder.com/iconsets/ionicons-outline-vol-1">Ionicons Outline Vol.1</a>, licensed under the <a href="https://opensource.org/license/MIT">MIT license</a>.</p>
	</footer>
    <script  src="./script.js"></script>

Styling with CSS

Next, we will style the HTML elements created in the previous step. CSS will enable view transitions and the bounce effect, we will also create the design for cards and their respective buttons.

@layer view-transitions {
	/* Don’t capture the root, allowing pointer interaction while cards are animating */
	@layer no-root {
		:root {
			view-transition-name: none;
		}
		::view-transition {
			pointer-events: none;
		}
	}
	
	/* Cards, in general, should use a bounce effect when moving to their new position */
	@layer reorder-cards {
		@supports (view-transition-class: card) {
			.warning {
				display: none;
			}
			
			:root {
				--bounce-easing: linear(
					0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141 13.6%, 0.25, 0.391, 0.563, 0.765,
					1, 0.891 40.9%, 0.848, 0.813, 0.785, 0.766, 0.754, 0.75, 0.754, 0.766, 0.785,
					0.813, 0.848, 0.891 68.2%, 1 72.7%, 0.973, 0.953, 0.941, 0.938, 0.941, 0.953,
					0.973, 1, 0.988, 0.984, 0.988, 1
				);
			}

			.card {
				view-transition-class: card;
			}
			
			/* Without view-transition-class you had to write a selector that targets all cards … and that selector needed updating whenever you added/removed a card */
			::view-transition-group(*.card) {
				animation-timing-function: var(--bounce-easing);
				animation-duration: 0.5s;
			}
		}
	}

	/* Newly added cards should animate-in */
	@layer add-card {
		@keyframes animate-in {
			0% {
				opacity: 0;
				translate: 0 -200px;
			}
			100% {
				opacity: 1;
				translate: 0 0;
			}
		}

		::view-transition-new(targeted-card):only-child {
			animation: animate-in ease-in 0.25s forwards;
		}
	}

	/* Cards that get removed should animate-out */
	@layer remove-card {
		@keyframes animate-out {
			0% {
				opacity: 1;
				translate: 0 0;
			}
			100% {
				opacity: 0;
				translate: 0 -200px;
			}
		}

		::view-transition-old(targeted-card):only-child {
			animation: animate-out ease-out 0.5s forwards;
		}
	}

}

/* Etc. */
@layer base {
	* {
		box-sizing: border-box;
	}

	body {
		display: grid;
		height: 90dvh;
		place-items: center;
		padding: 2rem 0;
		font-family: system-ui, sans-serif;
		width: 100%;
	}

	.cards {
		padding: 0;
		display: flex;
		justify-content: center;
		width: 100%;
		gap: 2rem;
		padding: 1rem 2rem;
		overflow-y: auto;
		overscroll-behavior: contain;
/* 		flex-wrap: wrap; */
	}

	.card {
		width: 100%;
		aspect-ratio: 2/3;
		display: block;
		position: relative;
		border-radius: 1rem;
		max-width: 10vw;
		min-width: 50px;

		background-color: grey;
	}

	.delete-btn {
		--icon: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiB3aWR0aD0iNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjx0aXRsZS8+PHBhdGggZD0iTTExMiwxMTJsMjAsMzIwYy45NSwxOC40OSwxNC40LDMyLDMyLDMySDM0OGMxNy42NywwLDMwLjg3LTEzLjUxLDMyLTMybDIwLTMyMCIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLXdpZHRoOjMycHgiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiMwMDA7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjMycHgiIHgxPSI4MCIgeDI9IjQzMiIgeTE9IjExMiIgeTI9IjExMiIvPjxwYXRoIGQ9Ik0xOTIsMTEyVjcyaDBhMjMuOTMsMjMuOTMsMCwwLDEsMjQtMjRoODBhMjMuOTMsMjMuOTMsMCwwLDEsMjQsMjRoMHY0MCIgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLXdpZHRoOjMycHgiLz48bGluZSBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MzJweCIgeDE9IjI1NiIgeDI9IjI1NiIgeTE9IjE3NiIgeTI9IjQwMCIvPjxsaW5lIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDA7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS13aWR0aDozMnB4IiB4MT0iMTg0IiB4Mj0iMTkyIiB5MT0iMTc2IiB5Mj0iNDAwIi8+PGxpbmUgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLXdpZHRoOjMycHgiIHgxPSIzMjgiIHgyPSIzMjAiIHkxPSIxNzYiIHkyPSI0MDAiLz48L3N2Zz4=);
		position: absolute;
		bottom: -0.75rem;
		right: -0.75rem;
		width: 3rem;
		height: 3rem;
		padding: 0.5rem;
		border: 4px solid;
		border-radius: 100%;
		background: aliceblue var(--icon) no-repeat 50% 50% / 70%;
		color: white;
		cursor: pointer;

		&:hover {
			background-color: orangered;
		}
	}

	.add-btn {
		--icon: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiB3aWR0aD0iNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjx0aXRsZS8+PGxpbmUgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLXdpZHRoOjMycHgiIHgxPSIyNTYiIHgyPSIyNTYiIHkxPSIxMTIiIHkyPSI0MDAiLz48bGluZSBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MzJweCIgeDE9IjQwMCIgeDI9IjExMiIgeTE9IjI1NiIgeTI9IjI1NiIvPjwvc3ZnPg==);
		width: 3rem;
		height: 3rem;
		padding: 0.5rem;
		border: 4px solid;
		border-radius: 100%;
		background: aliceblue var(--icon) no-repeat 50% 50% / 70%;
		color: white;
		cursor: pointer;

		&:hover {
			background-color: cornflowerblue;
		}
	}

	.sr-only {
		border: 0;
		clip: rect(1px, 1px, 1px, 1px);
		clip-path: inset(50%);
		height: 1px;
		margin: -1px;
		overflow: hidden;
		padding: 0;
		position: absolute;
		width: 1px;
		white-space: nowrap;
	}

	footer {
		text-align: center;
		font-style: italic;
		line-height: 1.42;
	}
}

@layer warning {
	.warning {
		padding: 1em;
		margin: 1em 0;
		border: 1px solid #ccc;
		background: rgba(255 255 205 / 0.8);
		text-align: center;
	}

	.warning > :first-child {
		margin-top: 0;
	}

	.warning > :last-child {
		margin-bottom: 0;
	}

	.warning a {
		color: blue;
	}
	.warning--info {
		border: 1px solid #123456;
		background: rgb(205 230 255 / 0.8);
	}
	.warning--alarm {
		border: 1px solid red;
		background: #ff000010;
	}
}

Adding JavaScript Functionality

Now, let’s implement the JavaScript code to handle adding and removing cards with view transitions. This code will attach event listeners to the “Add” and “Delete” buttons, utilizing the View Transition API to create smooth animations.

document.querySelector('.cards').addEventListener('click', e => {
	if (e.target.classList.contains('delete-btn')) {

		if (!document.startViewTransition) {
			e.target.parentElement.remove();
			return;
		}

		e.target.parentElement.style.viewTransitionName = 'targeted-card';
		document.startViewTransition(() => {
			e.target.parentElement.remove();
		});
	}
})

document.querySelector('.add-btn').addEventListener('click', async (e) => {
	const template = document.getElementById('card');

	const $newCard = template.content.cloneNode(true);
	$newCard.firstElementChild.style.backgroundColor = `#${ Math.floor(Math.random()*16777215).toString(16)}`;

	if (!document.startViewTransition) {
		document.querySelector('.cards').appendChild($newCard);
		return;
	}

	$newCard.firstElementChild.style.viewTransitionName = 'targeted-card';
	const transition = document.startViewTransition(() => {
		document.querySelector('.cards').appendChild($newCard);
	});

	await transition.ready;

	const rand = window.performance.now().toString().replace('.', '_') + Math.floor(Math.random() * 1000);
	document.querySelector('.cards .card:last-child').style.viewTransitionName = `card-${rand}`;

});

Including Header Assets

For the code to work you might need some header assets for complete functionality

<meta name="viewport" content="width=device-width, initial-scale=1">

Including Footer Assets

Also for complete functionality you can add assets like js files in the footer.

Congratulations! You have successfully implemented Add/remove Cards With View Transition In Vanilla Js. This will give a better user experience when adding or removing content.

Loading... ...

Loading preview...

Device: Desktop
Dimensions: 1200x800
Lines: 0 Characters: 0 Ln 1, Ch 1

Leave a Comment

About W3Frontend

W3Frontend provides free, open-source web design code and scripts to help developers and designers build faster. Every snippet is reviewed before publishing for quality. Learn more.