<header view-transition-name="buttons-header">
<button id="add" aria-label="Add List Item in Random Position" view-transition-name="add-button">+</button>
<button id="remove" aria-label="Remove List Item from Random Position" view-transition-name="remove-button">-</button>
</header>
<ul id="list">
<!-- these all need unique `view-transition-name`s because they all need to move independantly. They may move or not move depending on list position.-->
<li style="view-transition-name: item-0">Apple</li>
<li style="view-transition-name: item-1">Banana</li>
<li style="view-transition-name: item-2">Guanabana</li>
<li style="view-transition-name: item-3">Star Fruit</li>
<li style="view-transition-name: item-4">Dragonfruit</li>
</ul>
ul {
display: grid;
gap: 0.5rem;
padding: 0;
width: max-content;
}
ul > li {
background: white;
padding: 0.5rem 1rem;
border-radius: 0.3vw;
overflow: hidden;
contain: layout;
}
ul > li.incoming {
animation: 0.6s incoming both;
}
::view-transition-old(outgoing) {
animation: 1s outgoing both;
}
@keyframes outgoing {
0% {
translate: 0 0;
scale: 1;
opacity: 1;
}
100% {
translate: 100px -50px;
scale: 1.2;
opacity: 0;
}
}
@keyframes incoming {
0% {
scale: 1.6;
opacity: 0;
translate: -100px 0;
}
100% {
scale: 1;
opacity: 1;
translate: 0 0;
}
}
body {
font: 100%/1.4 system-ui, sans-serif;
background: #455a64;
height: 100vh;
margin: 0;
display: grid;
place-content: center;
}
header {
display: flex;
gap: 1rem;
}
header > button {
flex: 1;
background: #90a4ae;
color: white;
border: 0;
border-radius: 100px;
line-height: unset;
}
header > button:hover,
header > button:focus-visible {
background: #b0bec5;
}
button {
font-family: inherit;
}
import { faker } from "https://esm.sh/@faker-js/faker@7.6.0";
const list = document.querySelector("#list");
const addButton = document.querySelector("#add");
const removeButton = document.querySelector("#remove");
let startIndex = 5;
addButton.addEventListener("click", () => {
// Fallback for browsers without `startViewTransition`
if (!document.startViewTransition) {
addItem();
return;
}
// FLIP!
const transition = document.startViewTransition(() => {
addItem();
});
});
removeButton.addEventListener("click", () => {
const randomListItem = getRandomItem();
randomListItem.classList.remove("incoming");
randomListItem.style.viewTransitionName = "outgoing";
if (!document.startViewTransition) {
randomListItem.remove();
return;
}
const transition = document.startViewTransition(() => {
randomListItem.remove();
});
});
function addItem() {
const newListItem = document.createElement("li");
newListItem.innerHTML = faker.random.words();
// new list items need to have a unique `view-transition-name` because they need to move independantly, like all other list items. They may move or not move depending on list position.
newListItem.style.viewTransitionName = `item-${++startIndex}`;
newListItem.classList.add("incoming");
const numberOfListItems = list.querySelectorAll(`:scope > li`).length;
const randomPosition = getRandomInt(0, numberOfListItems);
if (randomPosition === 0) {
list.insertAdjacentElement("afterBegin", newListItem);
} else {
const randomListItem = list.querySelector(
`:scope > :nth-child(${randomPosition})`
);
randomListItem.insertAdjacentElement("afterEnd", newListItem);
}
}
function getRandomItem() {
const numberOfListItems = list.querySelectorAll(`:scope > li`).length;
const randomPosition = getRandomInt(1, numberOfListItems);
return list.querySelector(`:scope > :nth-child(${randomPosition})`);
}
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}