New version of the boat you haven’t used – share element animations across pages

Original link: https://ssshooter.com/2022-07-12-shared-element-transitions/

  • This article mainly refers to GitHub shared-element-transitions
  • Can’t access GitHub but can use Gitee mirror
  • Shared element animation usage requirements, after version 101, enable chrome://flags/#document-transition
  • The transition interface is not necessarily stable and may change

Effect example

The video comes from GitHub and cannot be played

Principle Analysis

The method of sharing element animation across pages is not actually manipulating DOM movement, but first saving the page or element to be moved as an image , and then adding animation to the image to achieve cross-page “shared element” animation. The enhanced version will be updated in the future: computed style + image, which can control CSS styles more finely instead of just manipulating one image. It is precisely because the screenshot is moved that the entire screen will not respond to any click events during the transition, which reduces the bugs caused by extreme operations to a certain extent.

It is worth noting that, based on the principle of cross-page transition, we can speculate that when the animation is executed, the target page has actually been rendered (there will be a target image), so the use of cross-page transition animation has a disadvantage, that is, there are certain delay.

 < top-layer > < container(root) > < image-wrapper(root) > < outgoing-image(root) /> < incoming-image(root) /> </ image-wrapper(root) > </ container(root) > </ top-layer >

It is conceivable that during the transition process, there will be such a group of elements on the top layer of the screen covering the original elements, so you cannot click on the elements on the original screen. By default, the transition between outgoing-image and incoming-image is a fade animation. We can control the animation of these two images through the following CSS selectors:

 container(root) - ::page-transition-container(root) image-wrapper(root) - ::page-transition-image-wrapper(root) outgoing-image(root) - ::page-transition-outgoing-image(root) incoming-image(root) - ::page-transition-incoming-image(root)

For example to extend the animation to 5 seconds:

 ::page-transition-outgoing-image(root), ::page-transition-incoming-image(root) { animation-duration : 5s ; }

Another example is to change the fade in and out to other animations:

 @keyframes slide-to-left { to { transform : translateX ( -100% ) ; } } @keyframes slide-from-right { from { transform : translateX ( 100% ) ; } } ::page-transition-outgoing-image(root) { animation : 500ms ease-out both slide-to-left ; } ::page-transition-incoming-image(root) { animation : 500ms ease-out both slide-from-right ; }

Multi-element participation in transition

The above code just adds a transition animation to the entire page (root), but a more common requirement is definitely a transition to an element. To do this we need to designate an element as the transition element:

 .site-header { page-transition-tag : side-header ; /* Paint containment is required */ contain : paint ; }

PS break-inside: avoid; and contain: paint; are important attributes to achieve cross-page transitions

Once set, the “image” structure for the transition will look like this:

 < top-layer > < container(root) > < image-wrapper(root) > < outgoing-image(root) /> < incoming-image(root) /> </ image-wrapper(root) > </ container(root) > < container(site-header) > < image-wrapper(site-header) > < outgoing-image(site-header) /> < incoming-image(site-header) /> </ image-wrapper(site-header) > </ container(site-header) > </ top-layer >

The operation of the transition animation is the same as the root, just replace the root with the corresponding tag: ::page-transition-outgoing-image(site-header)

Call the transition API

The transition cannot be achieved just by configuring CSS, you also need to call document.createDocumentTransition to tell the browser to perform the transition:

 async function spaNavigate ( data ) { // Fallback if ( ! document . createDocumentTransition ) { await updateTheDOMSomehow ( data ) return } // With a transition const transition = document . createDocumentTransition ( ) await transition . start ( async ( ) => { // Once this callback has called, the browser has captured the page similar to a screenshot. // This screenshot is now being displayed rather than the real DOM. // Any animated content on the page (eg CSS animations, videos, GIFs) will now appear frozen. await updateTheDOMSomehow ( ) // The DOM has now updated, but the user is still looking at the captured state. // Once this async function returns, the transition will begin. } ) // The transition is now complete, and the captured state is removed to reveal // the real DOM underneath. }

The process steps are as follows:

  • When the callback of start runs, the browser has obtained the current screenshot, and the entire page is covered by the screenshot, so from the user’s point of view, the entire screen is static and unresponsive
  • Operate the DOM in the callback, because the current screen is still a screenshot, so even if the DOM operation is completed, the user still sees the old screen
  • After the callback runs, get a new screenshot of the page, and the transition officially begins
  • After await start , the transition animation is completed, the screenshot used for the transition is removed, and the new page can be operated

You can also temporarily adjust the transition mode by modifying el.style.pageTransitionTag :

 async function animate ( direction ) { // Fallback if ( ! document . createDocumentTransition ) { await mutate ( ) return } // With a transition const transition = document . createDocumentTransition ( ) document . querySelector ( '#title' ) . style . pageTransitionTag = 'site-title-' + direction await transition . start ( ( ) => mutate ( ) ) document . querySelector ( '#title' ) . style . pageTransitionTag = '' console . log ( 'Transition complete!' ) }

Multi-page transition

The above operations are limited to single-page applications. Although the goal of this API is to enable multi-page applications to perform shared element transitions, the browser does not support it for the time being. You can only preview them through code. The basic concepts remain unchanged. The key point of cross-page is Manipulate elements before the page is hidden and the new page is shown:

 document . addEventListener ( "pagehide" , ( event ) => { if ( ! event . transition ) return ; document . getElementById ( "foo" ) . style . pageTransitionTag = "foo" ; event . transition . setData ( {} ) ; } ) ;
 document . addEventListener ( 'beforepageshow' , async ( event ) => { if ( ! event . transition ) return document . querySelector ( '.header' ) . style . pageTransitionTag = 'header' await event . transition . ready // The pseudo-elements are now accessible and can be animated: document . documentElement . animate ( keyframes , { ... animationOptions , pseudoElement : '::page-transition-container(header)' , } ) } )

come and try

After a preliminary understanding of the cross-page transition interface and CSS configuration, you should be able to understand this simple example! Everyone, open chrome://flags/#document-transition and try to understand the shared element animation more deeply by modifying this example!

https://codepen.io/ssshooter/pen/GRxjGXJ

This article is reprinted from: https://ssshooter.com/2022-07-12-shared-element-transitions/
This site is for inclusion only, and the copyright belongs to the original author.

Leave a Comment