This code Vanilla JS code snippet helps you to create functionality to add/remove cards with view transition. When a delete button is clicked, the corresponding card smoothly disappears from view. Similarly, when adding a new card, it smoothly transitions into view with a random background color. These transitions enhance the user experience when managing cards on a webpage.
You can use this code on any webpage to add or remove items dynamically. It provides smooth transitions, enhancing visual appeal and user experience. With Vanilla JavaScript, it’s lightweight and doesn’t require additional libraries.
How to Add/Remove Cards With View Transition in Vanilla JS
1. Start by setting up your HTML structure. Create elements for the cards and buttons. Ensure each card has a delete button associated with it.
<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>You browser does not support <code>view-transtion-class: <custom-ident>+</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>
2. Next, define CSS styles for your cards, buttons, and any transitions you want to apply. You can customize the appearance according to your design preferences.
@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; } } /* 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; } } } /* Etc. */ @layer base { body { display: grid; height: 90dvh; place-items: center; padding: 2rem 0; font-family: system-ui, sans-serif; } .cards { padding: 0; display: flex; justify-content: center; width: 100%; gap: 2rem; padding: 1rem; flex-wrap: wrap; } .card { width: 100%; aspect-ratio: 2/3; display: block; position: relative; border-radius: 1rem; max-width: 220px; min-width: 100px; 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 { box-sizing: border-box; padding: 1em; margin: 1em 0; border: 1px solid #ccc; background: rgba(255 255 205 / 0.8); } .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; } }
3. Now, write JavaScript logic to handle adding and removing cards with view transitions. Use event listeners to detect clicks on the add and delete buttons.
document.querySelector(&singleQuote;.cards&singleQuote;).addEventListener(&singleQuote;click&singleQuote;, e => { if (e.target.classList.contains(&singleQuote;delete-btn&singleQuote;)) { if (!document.startViewTransition) { e.target.parentElement.remove(); return; } e.target.parentElement.style.viewTransitionName = &singleQuote;targeted-card&singleQuote;; document.startViewTransition(() => { e.target.parentElement.remove(); }); } }) document.querySelector(&singleQuote;.add-btn&singleQuote;).addEventListener(&singleQuote;click&singleQuote;, async (e) => { const template = document.getElementById(&singleQuote;card&singleQuote;); const $newCard = template.content.cloneNode(true); if (!document.startViewTransition) { document.querySelector(&singleQuote;.cards&singleQuote;).appendChild($newCard); return; } $newCard.firstElementChild.style.viewTransitionName = &singleQuote;targeted-card&singleQuote;; $newCard.firstElementChild.style.backgroundColor = `#${ Math.floor(Math.random()*16777215).toString(16)}`; const transition = document.startViewTransition(() => { document.querySelector(&singleQuote;.cards&singleQuote;).appendChild($newCard); }); await transition.finished; const rand = window.performance.now().toString().replace(&singleQuote;.&singleQuote;, &singleQuote;_&singleQuote;) + Math.floor(Math.random() * 1000); document.querySelector(&singleQuote;.cards .card:last-child&singleQuote;).style.viewTransitionName = `card-${rand}`; });
That’s all! hopefully, you have successfully created functionality to add/remove cards on your website. If you have any questions or suggestions, feel free to comment below.
Similar Code Snippets:
I code and create web elements for amazing people around the world. I like work with new people. New people new Experiences.
I truly enjoy what I’m doing, which makes me more passionate about web development and coding. I am always ready to do challenging tasks whether it is about creating a custom CMS from scratch or customizing an existing system.