Image Gallery with Popovers and AIM (Anchor-Interpolated Morph)
Image Gallery with Popovers and AIM (Anchor-Interpolated Morph) êŽë š
While I was learning about Adamâs AIM technique the other day, it occurred to me that a cool use case would be a photo gallery. See what AIM (Anchor-Interpolated Morph) can do is animate the entrance and exit of elements from other elements. As in, use those other elements as literal CSS anchors.
In this case, the âthumbnailsâ in a photo gallery could be the anchors, and weâd animate in the larger versions of those photos from those thumbnail anchors. And back out to them too.
Itâs a kind of animation that not only looks cool, but is literally helpful, reminding you where an element came from and where it went. Imagine youâre proofing some images from a magazine photo shoot one by one, it would be a helpful reminder as youâre clicking through them which one you just looked at.
Hereâs the demo:
Hereâs a video in case youâre on a browser that doesnât support this particular cocktail of technology.
HTML and CSS Setup (No JavaScript by Default)
A (rather amazing) bonus of all this is how you can make a photo-grid like this, with the âopen largerâ ability, without any JavaScript. Itâs really as simple as this:
<button popovertarget="img-1">
<img
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=700&h=400&fit=crop&auto=format"
alt="Mountain landscape"
loading="lazy"
/>
</button>
<img
popover
id="img-1"
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1800&auto=format"
alt="Mountain landscape"
loading="lazy"
/>
Thatâs a <button> that points to a popover, so the default behavior is to toggle the popover open and closed when clicked. The âpopoverâ is: the larger image! Notice I can slap the popover attribute right on the <img> that comes right after the button.
That accomplishes the functionality weâre after.
The Three States
The deal with AIM (or any animation technique youâre dealing with that has entry and exit animations) is that there are really three states.
State 1: Before Open / On Way In
Weâll need to specifically make entry styles. Like the styles before the element (the âpopoverâ in our case) enters the page. This can be done with @keyframes, but I prefer the explicitness (and interruptibility) of @starting-style.
/* The large image */
[popover] {
...
/* Before Open / On Way In Styles */
@starting-style {
}
}
But there is more to deal with here, as this along isnât going to cut it. Weâre going to need to consider our open styles first, because these starting styles need to have at least the same specificity as those, otherwise it wonât work. So letâs do those next.
State 2: Open
We can be really specific with open state styles by using :popover-open. We could change a class in JavaScript or something too, but why? This is literally what popovers are for.
/* The Large Image */
[popover] {
...
/* Open Styles */
&:popover-open {
}
/* Before Open / On Way In Styles */
@starting-style {
&:popover-open {
}
}
}
Note that Iâm putting the starting styles after the open styles. To me thatâs a smidge unintuitive, but @starting-style doesnât have any specificity on itâs own, and we need to make sure the styles âbeatâ the open styles. We can win here by using the same selector and putting them later in the source order.
State 3: After Closed / On Way Out
Interestingly, these styles are the styles just directly on the element without any special extra selectors needed. But me, I kinda like putting them into a special selector just for the explicit-ness of it.
/* The Large Image */
[popover] {
...
/* Open Styles */
&:popover-open {
}
/* Before Open Styles */
@starting-style {
&:popover-open {
}
}
/* After Closed / On Way Out Styles */
&:not(:popover-open) {
}
}
Make sure it actually animates
Big olâ caveat here! One style that changes when a popover opens and closes is that itâs display value changes from none to block. This is going to pretty much totally nuke these entry and exit styles.
There is a little trick in CSS to ensure that, even though that display value is changing, our animations still work. See, normally, the display value changes immediately, but if we specifically give it a transition-behavior: allow-discrete then it will change perfectly in time for entry and exist animations to run.
So letâs do that, and apply the actual transitions.
/* The Large Image */
[popover] {
@media (prefers-reduced-motion: no-preference) {
transition:
display 1s allow-discrete,
translate 1s;
}
/* Open Styles */
&:popover-open {
translate: 0 0;
}
/* Before Open Styles */
@starting-style {
&:popover-open {
translate: -100px 0;
}
}
/* After Closed / On Way Out Styles */
&:not(:popover-open) {
translate: 100px 0;
}
}
Note
- The transitions are applied with a
@media (prefers-reduced-motion: no-preference)media query. This just removes them. You could also just tone them down. Lots of control here. - The exit styles are different than the entry styles. Fun!
The AIM Part
Those styles above just set the stage for entry and exit styles on popovers. As written, they open a popover by sliding in from the left and sliding out to the right. But with AIM, we want to be animating from the specific coordinates of another element. Thatâs the âanchorâ part of AIM.
To do AIM, we make the entry styles based on the top/left/width/height of the anchor (a photo thumbnail) and we make the open styles whatever else we wanna do (open it larger and centered).

That looks like this:
[popover] {
...
position-anchor: --âïž-morph;
--speed: 0.5s;
@media (prefers-reduced-motion: no-preference) {
transition:
display var(--speed) allow-discrete,
height var(--speed) ease,
width var(--speed) ease,
top var(--speed) ease,
left var(--speed) ease;
}
&:popover-open {
height: auto; /* or fit-content */
max-height: 70dvb;
width: 70dvi;
left: 15dvi;
top: 15dvb;
}
@starting-style {
&:popover-open {
left: anchor(left);
top: anchor(top);
width: anchor-size(width);
height: anchor-size(height);
}
}
&:not(:popover-open) {
left: anchor(left);
top: anchor(top);
width: anchor-size(width);
height: anchor-size(height);
}
}
Note that our entry and exit styles are the same here, which is actually nice. It shows where a photo came from and where it goes back to. Hereâs that final demo again.
Note that the animation starts before the larger image is necessarily all the way loaded, which is a smidge awkward. You could remove the lazy loading, or figure out some JavaScript that will delay the action until itâs loaded or something.
And hereâs an example where an AIM exit style goes to a totally different anchor!