Skip to content

Instantly share code, notes, and snippets.

@stanwmusic
Created December 16, 2025 08:36
Show Gist options
  • Select an option

  • Save stanwmusic/da4465dda9b625fd5851151a3c938771 to your computer and use it in GitHub Desktop.

Select an option

Save stanwmusic/da4465dda9b625fd5851151a3c938771 to your computer and use it in GitHub Desktop.
Squircles Gallery with view-transition
<div class="grid">
<div class="item" data-title="The Street">
<img src="https://picsum.photos/id/57/300/300" alt="">
</div>
<div class="item" data-title="Looking Out">
<img src="https://picsum.photos/id/447/300/300" alt="">
</div>
<div class="item" data-title="Freedom Above">
<img src="https://picsum.photos/id/315/300/300" alt="">
</div>
<div class="item" data-title="Greener Grass">
<img src="https://picsum.photos/id/89/300/300" alt="">
</div>
<div class="item" data-title="Relax">
<img src="https://picsum.photos/id/103/300/300" alt="">
</div>
<div class="item" data-title="Road to Nowwhere" data-active="true">
<img src="https://picsum.photos/id/314/300/300" alt="">
</div>
<div class="item" data-title="Spiraling">
<img src="https://picsum.photos/id/137/300/300" alt="">
</div>
<div class="item" data-title="Abandoned">
<img src="https://picsum.photos/id/478/300/300" alt="">
</div>
<div class="item" data-title="Path to Paradise">
<img src="https://picsum.photos/id/316/300/300" alt="">
</div>
</div>
<svg width="0" height="0" style="position: absolute;">
<defs>
<clipPath id="squircleClip" clipPathUnits="objectBoundingBox">
<path d="
M 0.5 0.05
C 0.8 0.05 0.95 0.2 0.95 0.5
C 0.95 0.8 0.8 0.95 0.5 0.95
C 0.2 0.95 0.05 0.8 0.05 0.5
C 0.05 0.2 0.2 0.05 0.5 0.05
Z
" />
</clipPath>
</defs>
</svg>
const grid = document.querySelector(".grid");
const items = Array.from(grid.children);
let activeItem = grid.querySelector('.item[data-active="true"]');
// swap current item with new clicked item
function swapItems(clickedItem) {
if (clickedItem === activeItem) return;
// store sibling reference for repositioning
const activeNext = activeItem.nextElementSibling;
// re-order DOM nodes: move clicked to active's position, active to clicked's position
if (clickedItem === activeNext) {
// if clicked is immediately after active, just swap positions
grid.insertBefore(clickedItem, activeItem);
} else {
grid.insertBefore(activeItem, clickedItem);
grid.insertBefore(clickedItem, activeNext);
}
// update active state
activeItem.removeAttribute("data-active");
clickedItem.setAttribute("data-active", "true");
activeItem = clickedItem;
}
function handleClick(event) {
const clickedItem = event.target.closest(".item");
if (!clickedItem || clickedItem === activeItem) return;
if (document.startViewTransition) {
document.startViewTransition(() => swapItems(clickedItem));
} else {
// no transition for browsers that don't support viewTransition
swapItems(clickedItem);
}
}
items.forEach((item, index) => {
item.style.viewTransitionName = `item-${index}`;
item.addEventListener("click", handleClick);
});
@import url(https://fonts.bunny.net/css?family=amatic-sc:400);
@layers general,demo;
@layer demo {
:root {
--grid-cols: repeat(2, 1fr);
--grid-rows: 100px;
--grid-cols-active: 1 / -1;
--grid-rows-active: 3 / 5;
--trans-swap-duration: 300ms;
--trans-image-zoom: 1000ms;
@media (700px < width) {
--grid-cols: repeat(6, 1fr);
--grid-rows: 140px;
--grid-cols-active: 3 / 5;
--grid-rows-active: 1 / 3;
}
}
.grid {
max-width: 1200px;
display: grid;
gap: 1rem;
grid-template-columns: var(--grid-cols);
grid-auto-rows: var(--grid-rows);
}
.grid:has(.item:hover) > .item:not(:hover) {
/*--img-opacity: .27;*/
}
.item {
aspect-ratio: 1;
overflow: hidden;
-webkit-clip-path: url(#squircleClip);
clip-path: url(#squircleClip);
/*border: 1px solid light-dark(rgba(0 0 0 / 0.15), rgba(255 255 255 / 0.5));*/
transition: filter var(--trans-swap-duration) ease-in-out;
filter: sepia(var(--img-filter, 0.75));
cursor: pointer;
isolation: isolate;
& > img {
width: 100%;
height: 100%;
object-fit: cover;
scale: var(--img-zoom, 1.5);
opacity: var(--img-opacity, 1);
transition: scale var(--trans-image-zoom) ease-in-out,
opacity 150ms ease-in-out;
}
&:hover {
--img-zoom: 1;
}
/* active item in center */
&[data-active="true"] {
--img-filter: 0;
grid-column: var(--grid-cols-active);
grid-row: var(--grid-rows-active);
z-index: 2;
position: relative;
&::before {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(circle at 50% 300%, rgb(0 0 0), transparent);
z-index: 1;
}
&::after {
content: attr(data-title);
position: absolute;
bottom: 0;
left: 50%;
translate: -50% -2rem;
font-family: "Amatic SC", handwriting;
font-size: 1.6rem;
color: white;
z-index: 2;
transition: translate 500ms 300ms;
transition-timing-function: linear(
0,
0.197 6.9%,
1 37.8%,
0.888 44.2%,
0.862 47.1%,
0.853 50%,
0.86 52.6%,
0.881 55.4%,
1 65.5%,
0.97 69.3%,
0.96 73%,
0.967 76.4%,
1 84.5%,
0.993 89.2%,
1
);
@starting-style {
translate: -50% 90px;
opacity: 0;
}
}
}
}
}
/* general styling not relevant for this demo */
@layer base {
* {
box-sizing: border-box;
}
:root {
color-scheme: light dark;
--bg-dark: rgb(16, 24, 40);
--bg-light: rgb(255, 237, 212);
--txt-light: rgb(10, 10, 10);
--txt-dark: rgb(245, 245, 245););
--line-light: rgba(0 0 0 / .25);
--line-dark: rgba(255 255 255 / .25);
--clr-bg: light-dark(var(--bg-light), var(--bg-dark));
--clr-txt: light-dark(var(--txt-light), var(--txt-dark));
--clr-lines: light-dark(var(--line-light), var(--line-dark));
}
body {
background-color: var(--clr-bg);
color: var(--clr-txt);
min-height: 100svh;
margin: 0;
padding: 2rem;
font-family: "Jura", sans-serif;
font-size: 1rem;
line-height: 1.5;
display: grid;
place-items: center;
gap: 2rem;
& > * {
/*outline: 1px dashed red;*/
}
}
h1 {
margin: 0;
font-size: 1.2rem;
}
/* screen-reader only */
.sr-only {
position: absolute !important;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap;
border: 0;
}
.msg-supports {
font-size: 0.8rem;
/*
@supports (animation-timeline: scroll()) {
display: none;
}
@supports (order:sibling-index()) {
display: none;
}
@supports (x: attr(x type(*))) {
display: none;
}
*/
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment