View Transitions Snippets: Keeping track of the old and new positions of a transitioned element

~

🌟 This post is about View Transitions. If you are not familiar with the basics of it, check out this 30-min talk of mine to get up to speed.

~

With View Transitions, the ::view-transition-group() pseudos are the ones that move around on the screen and whose dimensions get adjusted as part of the View Transition. You can see this in the following visualization when hovering the browser window:

See the Pen
View Transition Pseudos Visualized (2)
by Bramus (@bramus)
on CodePen.

While the View Transition API does not immediately expose these old and new positions, you can get them by measuring the animated subject using getBoundingClientRect.

By calling getBoundingClientRect before you start the View Transition, you can get the old position and size. To get the new position and size, call getBoundingClientRect again but this time after the View Transition’s Update Callback has been done. For this you need to await the updateCallbackDone promise.

// The subject
const $box = document.querySelector('.box');

// Get old position and size
const rectBefore = $box.getBoundingClientRect();

// Start a View Transition that alters the $box in some way
const t = document.startViewTransition(() => {
  modify($box);
});
    
// Wait for the update callback to be done
await t.updateCallbackDone;

// Get the new position and size
const rectAfter = $box.getBoundingClientRect();

Once you have those boxes – measured against the viewport – you do whatever you want with them.

In the following demo I simply draw them on screen for debugging purposes.

See the Pen
Debugging View Transitions: Keeping track of the old and new position
by Bramus (@bramus)
on CodePen.

Or in this demo I replace the ::view-transition-group(box)‘s keyframes that were generated by the browser with my own FLIP keyframes. Because the new keyframes don’t include the width and height attributes, these animations can now run on the compositor.

See the Pen
Better performing View Transition Animations, attempt #2, simplified
by Bramus (@bramus)
on CodePen.

~

Another approach you think you might be able to take – but one that I will debunk in this section – is to read the old and new positions from the ::view-transition-group()s keyframes.

In theory, you should be able to extract the required numbers from the relevant ::view-transition-group()’s keyframes using the code I shared in the previous post.

// Get all animations linked to the active View Transition
const vtAnimations = document.getAnimations().filter((anim) => {
  return anim.effect.target === document.documentElement &&
      anim.effect.pseudoElement?.startsWith("::view-transition")
});

// Get the animation linked to '::view-transition-group(box)'
const boxGroupAnimation = vtAnimations.find((anim) => {
  return anim.effect.pseudoElement == '::view-transition-group(box)';
});

// Get the keyframes
const boxKeyframes = boxGroupAnimation.effect.getKeyframes();

// Extract the from and to positions
// Both values are in the format `matrix(a, b, c, d, tx, ty)`
// You need the tx and ty values
const from = boxKeyframes[0].transform;
const to = boxKeyframes[1].transform;

However, this approach turns out to be unworkable because of two reasons:

  1. The computed keyframes have bugs in both Blink and WebKit, so you can’t rely on them.

  2. The exposed offsets are relative to the “Snapshot Containing Block”, not the Viewport.

    The Snapshot Containing Block is a rectangle that is used to position the ::view-transition pseudo and its descendants.

    This rectangle includes the address bar – if any – so its origin can be different from the Layout Viewport’s origin on mobile devices.

    Illustration from the spec, showing the Snapshot Containing Block: The snapshot containing block includes the URL bar, as this can be scrolled away. The keyboard is included as this appears and disappears. The top and bottom bars are part of the OS rather than the browser, so they’re not included in the snapshot containing block.

    As a result, the tx and ty values can’t be used directly if the Snapshot Containing Block’s origin and Viewport’s origin differ.

    See the following screenshot of Chrome on Android. The box is positioned at the offset 24,24 when measured against the Viewport, but that has become 24,80 when measured against the Snapshot Containing Block.

    Screenshot taken with Chrome on Android. The red outline represents the Layout Viewport. The subject is positioned at 24px from its top edge, indicated by the red arrow. The blue outline represents the Snapshot Containing Block. The subject is is positioned at 80px from its top edge, indicated by the blue arrow.

    As there is no way to know the position of the viewport relative to the Snapshot Containing Block, you can’t always be sure if the number represents the viewport-relative position or not. It does on desktop. It does on mobile with the navigation bar retracted. But it does not on mobile with the navigation bar expanded … except in Safari because they have not implemented the Snapshot Containing Block – https://bugs.webkit.org/show_bug.cgi?id=285400.

    I have filed an issue with the CSS Working Group to normalize the exposed keyframes to hold viewport-relative coordinates: w3c/csswg-drafts#11456.

  3. ~

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.