There’s a new web API proposal
for transitioning shared-elements
across pages.
It’s great for making smooth page transitions,
but what if we apply it to
individual elements
with changing styles on a single page?
Over the last couple of years,
the Web Incubator Community Group
has been developing a proposal for
Shared Element Transitions.
The stated goal
is to smooth out transitions
across page-loads on the web –
helping users maintain context as they navigate.
From the explainer:
When a user navigates on the web from Page-A to Page-B,
the viewport jumps and there is a flash of white
as elements disappear only to reappear in the same place
in some in-progress state.
This sequenced, disconnected user experience is disorienting…
I haven’t been involved with
the development of this feature,
but I was excited to see it underway.
Last week, I decided to take a closer look.
In order to animate ‘shared elements’
across different pages, we need to:
Associate elements on the starting page
(like the thumbnail of a video)
with elements on the ending page
(like the video itself).
Start a ‘document transition’
from one page to the other,
which will animate each ‘shared’ element
between the two pages.
Optionally override the default transitions
used for each ‘shared’ element.
In the first draft of the feature,
JavaScript was required for all three steps.
At that point,
many of us suggested
that steps 1 and 3 in particular
feel like style concerns
that could be handled declaratively in CSS.
And earlier this year,
Jake Archibald
presented a new proposal & prototype,
which makes that possible!
We can use the page-transition-tag property in CSS
to give each element a name.
If the start and end pages both have
an element with the same name,
it becomes a shared element!
At this point,
we still need JavaScript
to create and start transition events
(we’ll get there).
We can optionally override the default transitions in CSS
by applying animation styles
to several pseudo-elements
that represent the elements-in-transition.
For more detail on the full API,
I recommend reading the
Developer Guide.
Last week
at SmashingConf SF,
Jhey Tompkins
mentioned something that caught my attention.
According to Jhey,
it’s possible to trigger
a page ‘transition’ without
ever leaving the page you’re on.
That seemed strange to me.
Is it just a bug in the prototype?
But after thinking through it more,
this makes sense.
Shared-element transitions
are designed to work
with standard web navigation
across multiple page loads,
as well as page transitions
in ‘single-page’ apps (often called SPAs).
While many SPAs have similar features built-in,
a web platform approach requires less code,
and will result in better, more consistent performance.
In either case,
the stated goal is to help with transitions
from one ‘page’ to the next –
but SPAs (by definition)
recreate the effect of a page-load
without ever leaving the page.
We might update the URL
and replace the entire contents of the page,
but from a browser perspective
there is no change from one document to another.
Since SPA transitions are supported,
and SPA navigation happens entirely in-page,
a ‘page’ in this case is just
any given state of the document.
We can capture the state of things at one moment,
define that as the starting page,
make any changes we want,
and define the results as our ending page –
then animate between them.
The result is just like a ‘FLIP’
(first, last, invert, play) animation!
If you haven’t encountered FLIP before,
Cassie Evans
is a great resource on the topic:
Cassie also gave a brilliant talk at SmashingConf
about FLIP animations in responsive design.
I’ll post that video here if it becomes available.
CSS can currently only animate
by smoothly updating the value of a property.
We can animate a change in opacity from 0 to 1,
because CSS understands all the opacity values
between 0 and 1.
Along the way,
CSS can show us an opacity of 0.001 and 0.002
and so on, up through 0.998 and eventually 1.
But there are many properties
that we can’t animate in that way,
like grid-item positions.
There are no valid grid-column-start values between 1 and 2 –
grid lines only exist as whole steps,
or what we call ‘discrete’ values,
without any other values in-between.
Since CSS has no way to represent
the infinite intermediate steps along the way,
we aren’t able to animate between them.
The same is true
if we want to change the order
of elements in grid or flexbox layout,
and have them re-arrange automatically.
What we really want to do in this case
is animate the results of those style changes.
The ‘in-between’ positions aren’t values of any CSS property,
but actual coordinates on the page –
the changes in an element from state ‘A’ to state ‘B’.
Again, we’re describing the common use-case
for a FLIP animation.
For my own talk at SmashingConf,
I created a
visualization
of
Cascade Layers
being ‘sorted’ into groups.
The visualization starts
with an explicit layer order
followed by 9 layer blocks & imports,
mixed together in random order.
Three buttons allow them
to be shown either in their original order,
sorted so last-takes-precedence,
or weighted with most powerful at the top:
Pressing a given button
changes a data-sort attribute
on the (flexbox) list of layers,
and then CSSorder and flex-direction
(both discrete values)
are used to re-order the list
in different ways.
I wanted to animate the re-ordering
to make it clear what’s happening,
but in a hurry
I didn’t want to mess with
the JavaScript libraries required.
It may also be more semantically appropriate
to do this sorting in the DOM –
and I may still do that –
but at the time I was more concerned
about the resulting stage visualization.
Either way,
sorting in CSS or in the DOM,
the shared-element transition will work.
All that matters to this API
is that something changed between two states,
and we can animate each ‘shared element’
from one state to the other.
In this case, we want to associate
each element in the initial state
with the same element in the target state.
Since the element itself isn’t going anywhere,
we can do that by giving each element
a unique page-transition-tag.
That transition-tag will be the same
both before and after the state-change,
associating the element before
with the same element after.
I used a for loop in Sass
to apply unique transition-tags on each layer:
// for each of the nine layer blocks@for$ifrom 1 through9 {// give it a transition-tag based on its nth-position
[data-layer]:nth-of-type(#{$i}){page-transition-tag: layer-#{$i};}}
That’s all we need in the CSS.
I also have a pressBtn JavaScript function
that changes an attribute on the list.
This is the function that ‘triggers’ our change:
All that’s left is telling the browser
that this attribute change
should be considered a ‘document transition’.
As far as I can tell,
with my limited research,
this seems to work:
constbtnPress=(btn)=>{// create a document transitionconst transition = document.createDocumentTransition();// start the transition,// and make our change in the callback functionawait transition.start(()=>
doc.setAttribute('data-sort', btn.dataset.set),);};
Roughly speaking,
the browser will take a picture of each tagged element
before any changes are applied by the callback function,
and then take a picture of any tag-associated elements
after the change has been applied –
and animates between those two images.
It’s worth noting that –
while we haven’t changed any content on the page,
and only a few elements are moving –
the browser does actually
consider this a transition ‘between pages’.
For the duration of the transition,
the entire page is being turned into
a set of captured images to cross-fade.
That can lead to some unexpected stretching
of the surrounding elements
if we’re not careful.
You might also notice the clip-path animation on the list itself
looks a bit janky.
The prototype doesn’t yet support
‘nesting’ shared-elements in a transition –
so the items are being pulled out of the list
(where they appear un-clipped)
for the duration of the change.
If I understand right,
that will be improved in later versions,
allowing the outer clip animation
to impact the items inside.
I’m sure there are more improvements
that we could make here –
like checking for support of the API
and providing a fallback,
or providing more detailed animation rules,
or re-arranging the DOM rather than using CSS order –
but I’ll leave all those optimizations
as an exercise for the reader.
I am not an expert
on the details of this API –
and the API itself is likely to keep changing.
At this point,
I’m just an interested developer
playing with an early prototype,
and potentially pushing it outside
the intended use-cases.
I’ll be interested in feedback on this
from the people working on the spec,
or anyone else who wants to explore.
I’ll try to provide updates here
as my understanding develops.
New CSS features are shipping at an unprecedented rate –
cascade layers, container queries, the :has() selector,
subgrid, nesting, and so much more.
It’s a good time to step back and understand
how these tools fit together in a declarative system –
a resilient cascade of styles.
There’s a new proposal for animating shared-elements across web pages. But what happens if we apply it to individual elements within a page? @TerribleMia explores the possibility of native FLIP animations in the browser: oddbird.net/2022/06/29/sha…
I find this approach interesting, but would it work if there are other elements like tooltips above the transitioning elements, or if there are other animations, videos or interactions running while the transition takes place? I’m dreaming of an element transition api 😀
This was all sparked by a demo video @jaffathecake released last month, and then an edge-case @jh3yy suggested, a visualization of Cascade Layers, and an inspiring talk on ‘impossible’ animations from @cassiecodes. Put it all together, and…
What makes something a ‘grid’, and what’s at stake?
byMiriam Suzanneon
Back in 2020, Firefox released a prototype for doing ‘masonry’ layout in CSS. Now all the browsers are eager to ship something, but there’s a hot debate about the best syntax to use.
Over the last month, the CSS Working Group has determined we can loosen containment restrictions for query containers, and agreed on a syntax for special-case support queries (like support for the gap property in a flex context, or support for align-content in a block flow context).