bramus,
@bramus@front-end.social avatar

Want to see something cool? Here it is: it’s a Scroll-Driven View Transition.

Yep, you read that right: the View Transition is controlled by you scrolling! There’s a lot to tell about this one, so let’s take a look …

Video of the thing I have built. It’s an header that expands/grows in an animated manner.

kizu,
@kizu@front-end.social avatar

@bramus Were there ever pure CSS view transitions discussed? It would be cool to be able to say “here are two different states, create a view transition between them” and then use any CSS animation via any trigger to run it.

I think I saw multiple demos, where there was JS added only as something to trigger the view transitions. If this was available purely via CSS, I think it could be a powerful way to do various effects, often potentially more performant.

bramus,
@bramus@front-end.social avatar

If you can’t wait to see the final result, check out these 2 variants on @CodePen:

(I’ve also got a draggable version, but it’s not a nice. Find the link and details further down)

bramus,
@bramus@front-end.social avatar

Before I continue though, credit where credit is due: the demo is built after this Music Playlist Interaction Concept by Ehsan Rahimi:

https://dribbble.com/shots/4650389-Music-Playlist-App-Interaction

Video of the concept built by Ehsan Rahimi

bramus,
@bramus@front-end.social avatar

You are most likely already familiar with the first part of this concept, as that one got implemented by Maxi Ferreira about a year ago and got shared A LOT (we even featured it in last year’s #GoogleIO!)

Live Demo: https://live-transitions.pages.dev/

Source Code: https://github.com/Charca/view-transitions-live

Recording of https://live-transitions.pages.dev/

bramus,
@bramus@front-end.social avatar

So I set out to implement the second leg of this concept: the playlist header shrinks as you scroll down.

The first thing I did was to create a base View Transition, without hooking it up to scroll just yet. Click the button to trigger the transition.

https://codepen.io/bramus/full/BabWXJv

Recording of the linked to demo

bramus,
@bramus@front-end.social avatar

The only thing that changes between the two states is the classList on the .card, which applies a different grid-template. View Transitions take care of moving everything around. Very powerful stuff.

(And yes, that was DevTools used to scrub through the animations there)

bramus,
@bramus@front-end.social avatar

A first attempt at adding interactivity is creating a draggable version. This uses the Pointer Events to start the View Transition if need be.

https://codepen.io/bramus/full/wvOdBJY

Recording of the linked to demo

bramus,
@bramus@front-end.social avatar

On pointerdown the View Transition starts but all its animations get paused immediately.

On pointermove the animations their currentTime get updated based on your drag distance.

On pointerup the VT either snaps back, or plays until the end.

// This one gets triggered on

bramus,
@bramus@front-end.social avatar

This main logic here is borrowed from this Gesture View Transition demo by @jaffathecake

https://simple-set-demos.glitch.me/gesture/

He’s really done all the heavy lifting here, so I owe you one Jake!

Recording of the linked to demo

bramus,
@bramus@front-end.social avatar

While nice, this drag approach quickly turns into a UX mess:

  • Mouse users need to combine drag and scroll
  • On touchy devices you need touch-action: none for the code to work, but disable scroll doing so
  • Mapping drag to scroll removes momentum scroll
bramus,
@bramus@front-end.social avatar

That said: In Jake’s demo the approach works fine as he can apply the touch-action: none on the draghandle itself.

In my demo I need to conditionally set it based on the scroll offset, drag direction, and card state … which doesn’t work as it gets eval’d before pointerdown.

bramus,
@bramus@front-end.social avatar

Looking to other solutions, I turned to what I know very well: Scroll-Driven Animations. The idea is to let all VT animations play but set their timeline to a ScrollTimeline.

That way they don’t tick forwards by time but by you scrolling.

bramus,
@bramus@front-end.social avatar

You can see the result here: https://codepen.io/bramus/full/YzgVymZ

It works, but one big issue though: once the animations reach 100% the View Transition finishes. Because of that, you only get to see the Scroll-Driven View Transition only once.

Recording of the linked to demo

bramus,
@bramus@front-end.social avatar

To make the Scroll-Driven Animation restart after it finished, I added an extra scroll listener that calls startViewTransition when in range.

bramus,
@bramus@front-end.social avatar

That also required me to check the direction you are going in: are you expanding or contracting the card? Depending on that, the VT’s animations need to be reversed.

bramus,
@bramus@front-end.social avatar

I also added scroll snapping to the code. I’ll spare you the details but this was quite difficult to get right, especially with the DOM already having changed underneath the VT running.

Essentially, I inject snapping boxes that match the expanded layout. Hacky, but it works.

https://cdn.masto.host/frontendsocial/media_attachments/files/111/800/633/819/141/564/original/9eb4927b30635507.png

bramus,
@bramus@front-end.social avatar

All combined, we get this: https://codepen.io/bramus/full/xxBdYNy

This one works fine on both touch and non-touchy devices, has momentum scrolling, snaps, … just a bit stupid that you need both a Scroll-Driven Animation and a scroll listener.

Recording of the linked to demo

bramus,
@bramus@front-end.social avatar

Not entirely satisfied with the current result that mixes SDA and scroll I explored two more paths:

  1. One that uses only a scroll listener
  2. One that uses only a Scroll-Driven Animation
bramus,
@bramus@front-end.social avatar

The version that uses a scroll listener is pretty straightforward: constantly check the offsetTop on scroll. When in range start or update the VT. When out of range, clean up the VT (if any).

It uses update logic similar as the touch version.

Demo: https://codepen.io/bramus/full/mdowgYX

bramus,
@bramus@front-end.social avatar

The version with a true Scroll-Driven Animation is a bit more complicated: it doesn’t create a ScrollTimeline that is used as the timeline for the VT animations but instead it creates a dummy scroll-driven animation on root whose progress is tracked.

bramus,
@bramus@front-end.social avatar

Using the trackProgress function from my @bramus@front-end.social/sda-utilities the progress is read. When in range start or update the VT. When out of range, clean up the VT (if any).

This logic is basically the same as with the scroll listener version.

Demo: https://codepen.io/bramus/full/BabRVLg

bramus,
@bramus@front-end.social avatar

So which one of those two scroll ones should you use? For now, use the scroll one, as it’s more performant.

Reason for this, is that trackProgress uses a requestAnimationFrame’d getComputedTiming() to constantly read the effect progress. Yikes.

bramus,
@bramus@front-end.social avatar

In the future however, I expect the ScrollTimeline version to become more performant.

Once something like CustomEffect – in which you pass a callback function that receives the progress into the animation – becomes available, that could eventually replace trackProgress.

Or maybe we’ll get native Scroll/Gesture Driven View Transitions? Who knows? 😛

bramus,
@bramus@front-end.social avatar

To summarize, the core of this all is to start a View Transition and hijack its animations. Either by pausing them and manually updating the currentTime, or by attaching them to a different type of timeline.

Again, kudos to @jaffathecake for leading the way here.

Fin.

  • All
  • Subscribed
  • Moderated
  • Favorites
  • random
  • DreamBathrooms
  • thenastyranch
  • mdbf
  • vwfavf
  • Youngstown
  • slotface
  • hgfsjryuu7
  • Durango
  • rosin
  • kavyap
  • osvaldo12
  • PowerRangers
  • InstantRegret
  • magazineikmin
  • normalnudes
  • khanakhh
  • GTA5RPClips
  • ethstaker
  • cubers
  • ngwrru68w68
  • tacticalgear
  • everett
  • tester
  • Leos
  • cisconetworking
  • modclub
  • anitta
  • provamag3
  • All magazines