Skip to content

Add view transitions theme support in abstracted way with sensible defaults #8370

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 35 commits into
base: trunk
Choose a base branch
from

Conversation

felixarntz
Copy link
Member

@felixarntz felixarntz commented Feb 20, 2025

This PR explores adding cross-document view transitions to WordPress Core. These enable smooth transitions between URL navigations and thus can improve user experience.

Learn more about cross-document view transitions

For browser support, see https://caniuse.com/mdn-css_at-rules_view-transition

High-level approach

  • In principle, how view transitions should be applied heavily depends on the layout and markup of each site. WordPress themes are rather unpredictable in that regard - for the most part.
  • This PR focuses on identifying aspects where themes have things in common. Specifically, a smooth transition between certain elements of a post (e.g. navigating between the singular post URL and the same post within a blog or archive URL) seems achievable.
    • For classic themes, the post is almost always wrapped in an article.post element or, sometimes on the singular post URL it's not wrapped specifically, but this we can detect via body.single selector.
    • For block themes, it's equally straightforward, as there a post is almost always wrapped in a .wp-block-post.post element.
    • We can identify between which post the navigation happens by comparing the URL that is being navigated to or from with the specific .post element on the page that contains an a link pointing to that same URL. That way we can achieve a nice effect where relevant post elements (in this PR so far the post title and featured image) shift to their location within the other URL.
  • For block themes, this is overall by far the most straightforward, since their markup is far more predictable, so it should be reasonable to enable view transitions for block themes by default.

What's supported?

API

  • This PR opts in every block theme and very classic default theme to using cross-document view transitions.
  • Any theme can opt in by calling add_theme_support( 'view-transitions' ).
  • Optionally, themes that opt in can provide configuration arguments to fine-tune the default behavior by providing an array of arguments in the call: See https://github.com/WordPress/wordpress-develop/pull/8370/files#diff-b9f1810ad43acfa11ba58a9f21eb0ee3a8063c80aa155de9b210323252534716R2938 for the supported arguments.
  • For now, none of the opted in themes provides custom arguments. This is so that we can first focus on testing and validating the most suitable default behavior before getting into fine-tuning.

Transition behavior

  • Overall, the entire content on any URL will fade over to the next one. This is the very basic foundation of view transitions and, while maybe quite nice, not very useful on its own. It's the more specific transitions that make it visually appealing (see below).
  • When navigating between an archive view URL and a single post URL within that archive, the following elements will morph between the two URLs (e.g. transitioning between their locations and transforming in size accordingly):
    • the overall page header
    • the overall page content
    • the specific post title being navigated to/from
    • the specific post thumbnail / featured image (if present) being navigated to/from
    • the specific post content (if present) being navigated to/from
  • If pretty permalinks are enabled:
    • When navigating between different pages of the same archive view, the overall page content will slide to the left if moving to a higher page, or to the right if moving to a lower page, while the rest of the page remains in place.
    • When navigating between different pages of a multi-page post (rarely used, can be achieved via "Page Break" block), the specific post content will slide to the left if moving to a higher page, or to the right if moving to a lower page, while the rest of the page remains in place.
    • When navigating from a post to the previous or next post, the overall page content will slide to the left if moving to a newer post, or to the right if moving to an older post, while the rest of the page remains in place. This only works if the permalinks are configured to include the date (e.g. /%year%/%monthnum%/%day%/%postname%/).

Testing

  1. Use a compatible browser (see https://caniuse.com/mdn-css_at-rules_view-transition).
  2. Simply apply the PR to your codebase, or use WordPress Playground.
  3. Create a few posts with non-empty titles and any content, most of them with a featured image. 3-4 posts should be enough for testing.
  4. Try using any of the WordPress default themes, navigating between blog/archive views and specific posts on them to see the view transitions in effect.

Next steps

I tested all default themes with how they behave, and it works for almost all of them:

  • 2025: Works out of the box.
  • 2024: Works out of the box.
  • 2023: Works out of the box.
  • 2022: Works out of the box.
  • 2021: Works out of the box.
  • 2020: Works out of the box.
  • 2019: Requires custom configuration due to special handling of full-screen featured images.
  • 2017: Requires custom configuration due to special handling of full-screen featured images.
  • 2016: Works out of the box.
  • 2015: Works out of the box.
  • 2014: Works out of the box.
  • 2013: Works out of the box.
  • 2012: Works out of the box.
  • 2011: Works as well as it can, because featured images aren't even displayed in blog or archive views.

Obviously, more at-scale in research and testing is needed, to see how other themes (especially classic themes) handle those areas in terms of markup and whether they would also work out of the box or would require customizations. But this is a start :)

Trac ticket:


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

Copy link

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

const isMainSlide = transitionType === 'forwards' || transitionType === 'backwards';
let foundMainElement = false;
return [
...Object.entries( config.globalTransitionNames || {} ).map( ( [ selector, name ] ) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some inline comments above each of the return elements would be nice, took a bit to understand what the code was doing exactly

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added docs in 220d1b8

];
};

const setTemporaryViewTransitionNames = async ( entries, vtPromise ) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function also clears the transition names after the transitions complete, right? inline doc would be nice here as well :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added docs for all the functions in 220d1b8

@adamsilverstein
Copy link
Member

❤️ Amazing work @felixarntz 🎉 I love how you managed to create such a simple way for themes to opt in to View Transitions, and haw you handled the complex bits automatically (especially figuring out if the transition is moving "forwards" or "backwards"). Also, I completely forgot about page breaks, neat that it already supports them.

Overall this looks really good, my main feedback would to request a bit more inline documentation, especially for the obtuse JavaScript bits. It might also be nice to capture how these transitions look across a subset of core themes and include brief screencasts of them in action, or maybe some Playground blueprints that were preset to use them including sample content.

This also looks like something we might be able to leverage eventually for the Query Loop Block, eg navigating from a post list loop to a single post. Have you already considered how that could work?

Copy link
Member

@westonruter westonruter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested with Twenty Twenty-One and it felt magical.

Screen.recording.2025-03-03.12.57.25.webm

Comment on lines 87 to 88
let oldPageMatches = oldPathname.match( /\/page\/(\d+)\/?$/ );
let newPageMatches = newPathname.match( /\/page\/(\d+)\/?$/ );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically the /page/ can be overridden by setting it to something else in WP_Rewrite::$pagination_base. So ideally this would be exported from PHP as part of the config.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related, I was thinking last week about whether we should even use the URLs for this or rely on classes on body. Almost all relevant WordPress query parameters (including those for pagination) have body classes we could use. That would make the implementation more flexible and also work on sites without pretty permalinks.

Part of the JS in this PR already used body classes, so maybe we should use that approach more holistically?

@felixarntz
Copy link
Member Author

@adamsilverstein

Overall this looks really good, my main feedback would to request a bit more inline documentation, especially for the obtuse JavaScript bits.

Fair point, I'll add more soon. As I'm still experimenting at this point, I didn't want to invest too much time documenting just yet, but eventually there should definitely be better docs.

It might also be nice to capture how these transitions look across a subset of core themes and include brief screencasts of them in action, or maybe some Playground blueprints that were preset to use them including sample content.

Great idea! Let's work on this once the PR is in a place where we think the feature set is worth moving forward with.

@westonruter
Copy link
Member

While I'm not a designer, I think it's unfortunate that the index template in Twenty Twenty-Five puts the post title after the featured image:

Screen.recording.2025-03-12.14.49.49.webm

Whereas on the singular template, the index template has the post title before the featured image. If the index template were made consistent, this would make the transition better IMO:

Screen.recording.2025-03-12.14.50.42.webm

@felixarntz
Copy link
Member Author

260b377 is a first stab at allowing different default animations. It implements a gentle wipe animation that can be triggered from different directions.

To test, clone the code locally and set the default-animation theme support argument to one of:

  • wipe-from-right
  • wipe-from-left
  • wipe-from-top
  • wipe-from-bottom

This animation looks best if all default global-transition-names are disabled, so ideally empty that array before taking a look.

@felixarntz
Copy link
Member Author

e4798d3 introduces a PHP infrastructure for easier handling of view transition animations and allowing to register additional ones.

This is still a work in progress, here's some context on the overall idea:

  • Mental model: Separate the concept of animation from the concept of transition:
    • There are different kinds of view transitions: There's the default one that is used on almost every navigation, but then there are also other ones like a chronological navigation (older or newer post/s) or pagination-related navigation (paged post). Those are the transitions.
    • Any of these transitions can use a distinct animation. Maybe you'll want all transitions to use the same animations, or maybe you'll want chronological transitions to use some kind of slide left/right animation. Or maybe you'll want chronological transitions to use a soft wipe left/right animation rather than a hard swipe left/right. In other words, it needs to be possible for the various transitions to individually specify which animation they should use.
      • By abstracting animations into their own class, it also makes it easier to define animation behavior that goes beyond just CSS to load. For example, the wipe transition that was previously implemented makes hardly any sense to be used in combination with the header and main content having their own view transition names. So now this can be encapsulated in the animation definition itself.
  • Expressing animations as instances of a class makes the configuration and handling a lot easier to manage. Additionally, animations lend themselves perfectly to third-party extensions, so a registry is included as well. There could be a plugin that registers a collection of view transition animations that any theme can then use. Or a theme could register its own custom animation and then use it.
  • Per the above, what still needs to be implemented is allowing to specify animations for different kinds of transitions, instead of weirdly specific theme support args like chronological-slide-in-out. At the moment it's only possible to choose the animation for the default transition.

@felixarntz
Copy link
Member Author

88766c2 completes an initial implementation of the approach I outlined above in #8370 (comment).

It also adds a few more basic animations.

As of now, the following animations are available:

  • fade: This is basically the default browser behavior for view transitions, no extra CSS, just blending the two pages into each other.
  • slide: Slides out the old page contents and slides in the new page contents from the opposite direction (by default from the right).
    • You can use aliases slide-from-right, slide-from-bottom, slide-from-left, and slide-from-top to configure the directions. Alternatively you can configure concrete offset arguments for granular control.
    • You can optionally specify a target-name argument, referencing a view transition name used for one or more elements in the page to only apply the slide transition to those elements. (Realistically, this may be the more useful way to use this animation, since sliding the entire viewport in and out looks a bit, well, "cheap".)
  • swipe: This is almost the same as slide, but with the addition that the contents also receive a fade in/out effect (via opacity: 0).
    • You can use aliases swipe-from-right, swipe-from-bottom, swipe-from-left, and swipe-from-top to configure the directions. Alternatively you can configure concrete offset arguments for granular control.
    • You can optionally specify a target-name argument, referencing a view transition name used for one or more elements in the page to only apply the swipe transition to those elements. (Realistically, this may be the more useful way to use this animation, since swiping the entire viewport in and out looks a bit, well, "cheap".)
  • wipe: This is a slightly more elegant way of swipe: Instead of moving the actual viewport contents, they overlap and the new page contents are slowly blended in starting from a certain angle (by default from the right).
    • You can use aliases wipe-from-right, wipe-from-bottom, wipe-from-left, and wipe-from-top to configure the directions. Alternatively you can configure concrete offset arguments for granular control.

Since animations are registerable, it's easy to implement further ones.

For the actual view transition types, these are hard-coded as they rely on specific heuristics to be determined. The following view transition types are available:

  • default: The default which is used whenever no more specific view transition type is determined.
  • chronological-forwards: Transition type for navigating from a post or an archive to another post or archive within that same hierarchy with content that is newer.
  • chronological-backwards: Same as chronological-forwards, but in the opposite direction.
  • pagination-forwards: Transition type for navigating from a page of a multi-page post (rarely used) to a higher page.
  • pagination-backwards: Same as pagination-forwards, but in the opposite direction.

For each of these 5 transitions, you can assign one of the animations that are available. You must assign an animation for the default transition type, all others are optional. If not specified, they will use the same animation as the default transition type.

The current default is to simply use the fade animation for all transition types, which is the least special, but also the safest to look okay regardless of theme layout used.

Here are some other configurations you can play around with (either by locally updating the $defaults from the PR or by actually configuring an add_theme_support( 'view-transitions', ... ) call):

Use wipe (from right) for every transition:

array(
	'default-animation'                 => 'wipe',
	'chronological-forwards-animation'  => false,
	'chronological-backwards-animation' => false,
	'pagination-forwards-animation'     => false,
	'pagination-backwards-animation'    => false,
);

Use wipe with custom angle (from lower right corner) for every transition:

array(
	'default-animation'                 => 'wipe',
	'default-animation-args'            => array( 'angle' => 315 ),
	'chronological-forwards-animation'  => false,
	'chronological-backwards-animation' => false,
	'pagination-forwards-animation'     => false,
	'pagination-backwards-animation'    => false,
);

Use directional wipe for chronological transitions and slide just the post content for pagination transitions:

array(
	'default-animation'                   => 'fade',
	'chronological-forwards-animation'    => 'wipe-from-right',
	'chronological-backwards-animation'   => 'wipe-from-left',
	'pagination-forwards-animation'       => 'slide-from-right',
	'pagination-forwards-animation-args'  => array( 'target-name' => 'post-content' ),
	'pagination-backwards-animation'      => 'slide-from-left',
	'pagination-backwards-animation-args' => array( 'target-name' => 'post-content' ),
);

Use directional slide from different transitions (just for fun, this looks very cheesy):

array(
	'default-animation'                 => 'fade',
	'chronological-forwards-animation'  => 'slide-from-bottom',
	'chronological-backwards-animation' => 'slide-from-top',
	'pagination-forwards-animation'     => 'slide-from-right',
	'pagination-backwards-animation'    => 'slide-from-left',
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants