Make WordPress Core

Changeset 64691


Ignore:
Timestamp:
10/23/2026 10:24:18 PM (5 hours ago)
Author:
westonruter
Message:

Administration: Refine the admin menu view transitions.

The admin menu's accordion animation sometimes degraded to a plain cross-fade, because a cross-document view transition snapshots the incoming page at its first render, which can happen before the admin menu has finished parsing. A render-blocking expect link now holds the first render until #wpcontent, the element right after the admin menu, is parsed, so the snapshot always has the complete menu without waiting for the rest of the page. The link is only printed once the View Transitions style has been printed, since an expect target that never appears blocks rendering until the whole document is parsed.

The animation itself is also refined. Each menu item's snapshot is clipped to its animating box, so a submenu wipes open or closed in step with the items around it instead of overlapping them. The Collapse Menu button moves with the menu instead of cross-fading. An open flyout fades out while moving with its menu item, instead of being cut off by that clip.

The style is now enqueued from wp-admin/admin-header.php instead of on the admin_enqueue_scripts action, which iframe_header() also fires, so screens without the admin menu no longer load it. As a result, unhooking wp_enqueue_view_transitions_admin_css() no longer turns off admin View Transitions. Dequeuing the wp-view-transitions-admin style does, and it works in 7.0 as well.

Developed in ​https://github.com/WordPress/wordpress-develop/pull/11912.
Follow-up to r61491.

Props westonruter, b1ink0, zieladam, mukesh27, dhrupo, audrasjb, joedolson, huzaifaalmesbah, jasonsa19, ozgursar, nikunj8866, darshitrajyaguru97.
See #64470.
Fixes #65032.

Location:
trunk
Files:
1 added
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/admin-header.php

    r64301 r64691  
    103103wp_enqueue_script( 'utils' );
    104104wp_enqueue_script( 'svg-painter' );
     105
     106/*
     107 * View transitions animate the admin menu, so they are only enqueued on screens that print it.
     108 * Iframe screens such as Thickbox modals do not load this header and so do not opt in.
     109 */
     110wp_enqueue_view_transitions_admin_css();
    105111
    106112$admin_body_class = preg_replace( '/[^a-z0-9_-]+/i', '-', $hook_suffix );
  • trunk/src/wp-admin/css/view-transitions.css

    r61502 r64691  
    44        }
    55
     6        /*
     7         * Give each top-level menu item its own view transition name — its id,
     8         * such as "menu-posts" — so each one is captured as an independent group
     9         * and animates from its old position and size to its new ones across a
     10         * navigation. This produces the accordion effect: the previously open
     11         * section collapses as the newly current one expands, instead of the
     12         * whole menu simply cross-fading. attr() falls back to "none" for any
     13         * item whose id is missing or is not a valid custom identifier. The
     14         * shared wp-menu-top class lets the image-pair rule below address every
     15         * menu-top group at once.
     16         */
    617        #adminmenu > .menu-top {
    718                view-transition-name: attr(id type(<custom-ident>), none);
     19                view-transition-class: wp-menu-top;
     20        }
     21
     22        /*
     23         * Name the collapse button too, so it slides into place with the menu
     24         * items instead of cross-fading as part of the page root.
     25         */
     26        #collapse-menu {
     27                view-transition-name: collapse-menu;
     28        }
     29
     30        /*
     31         * Clip each menu-top's snapshot to its animating group box. By default a
     32         * captured snapshot keeps its full intrinsic height, so the new (expanded)
     33         * submenu paints at full height from the first frame and overlaps the
     34         * items below while they are still sliding down. Clipping the image pair
     35         * to the group — whose height animates from the link row to the full
     36         * expanded height — makes the submenu wipe open, and closed in reverse,
     37         * in step with those items.
     38         */
     39        ::view-transition-image-pair(.wp-menu-top) {
     40                overflow: clip;
     41        }
     42
     43        /*
     44         * Capture an open flyout submenu — one shown on hover or focus — as its own
     45         * group, so that it fades out instead of being cut off by the clip above,
     46         * since it extends outside its menu-top box. match-element gives each
     47         * flyout a unique name, so two open at once cannot collide and abort the
     48         * transition. view-transition-group nests the flyout's group inside its
     49         * menu-top's group, so it moves along with its menu item while fading out
     50         * instead of staying behind at its old position. Since the nested group is
     51         * a sibling of the menu-top's image pair, it is not cut off by the clip.
     52         *
     53         * In the expanded menu, the current item's submenu is in the normal flow,
     54         * so it is left to the clipped menu-top. When the menu is folded, either
     55         * explicitly or automatically on narrower screens, every submenu is a
     56         * flyout, including the current one. Below 783px the menu is responsive and
     57         * its submenus are in the normal flow, so none are named there.
     58         */
     59        @media screen and (min-width: 783px) {
     60                #adminmenu > .wp-not-current-submenu.opensub > .wp-submenu,
     61                #adminmenu > .wp-not-current-submenu > a.menu-top:focus + .wp-submenu,
     62                .folded #adminmenu > .opensub > .wp-submenu,
     63                .folded #adminmenu > .menu-top > a.menu-top:focus + .wp-submenu {
     64                        view-transition-name: match-element;
     65                        view-transition-group: nearest;
     66                }
     67        }
     68
     69        @media screen and (min-width: 783px) and (max-width: 960px) {
     70                .auto-fold #adminmenu > .opensub > .wp-submenu,
     71                .auto-fold #adminmenu > .menu-top > a.menu-top:focus + .wp-submenu {
     72                        view-transition-name: match-element;
     73                        view-transition-group: nearest;
     74                }
    875        }
    976}
  • trunk/src/wp-includes/default-filters.php

    r64588 r64691  
    635635add_action( 'admin_enqueue_scripts', 'wp_common_block_scripts_and_styles' );
    636636add_action( 'admin_enqueue_scripts', 'wp_enqueue_command_palette_assets' );
    637 add_action( 'admin_enqueue_scripts', 'wp_enqueue_view_transitions_admin_css' );
     637add_action( 'admin_head', 'wp_print_view_transitions_render_blocking_link' );
    638638add_action( 'enqueue_block_assets', 'wp_enqueue_classic_theme_styles' );
    639639add_action( 'enqueue_block_assets', 'wp_enqueue_registered_block_scripts_and_styles' );
  • trunk/src/wp-includes/view-transitions.php

    r61491 r64691  
    1111 * Enqueues View Transitions CSS for the admin.
    1212 *
     13 * To disable admin View Transitions, dequeue the `wp-view-transitions-admin` style at the
     14 * {@see 'admin_enqueue_scripts'} action, rather than unhooking this function.
     15 *
    1316 * @since 7.0.0
     17 * @since 7.2.0 Called from wp-admin/admin-header.php instead of hooked to {@see 'admin_enqueue_scripts'}.
    1418 */
    1519function wp_enqueue_view_transitions_admin_css(): void {
    … …  
    2731        $affix = SCRIPT_DEBUG ? '' : '.min';
    2832        $path  = ABSPATH . "wp-admin/css/view-transitions{$affix}.css";
    29         return file_get_contents( $path );
     33        return (string) file_get_contents( $path );
    3034}
     35
     36/**
     37 * Prints a render-blocking expectation so View Transitions capture a fully parsed admin menu.
     38 *
     39 * A cross-document view transition snapshots the incoming page at its first render, which can
     40 * happen before the `<body>` has finished parsing. The snapshot would then capture a partially
     41 * built page — for example an incomplete admin menu — and the transition animates to or from
     42 * the wrong state.
     43 *
     44 * The `expect` link blocks the first render until `#wpcontent` is parsed. Rendering is unblocked
     45 * as soon as the target's start tag is parsed, not its children, so the target is the element
     46 * right after the admin menu rather than the menu itself: once `#wpcontent` starts, the whole
     47 * menu has been parsed. Targeting an element near the end of the body would also work, but it
     48 * would delay the first render by the time it takes to parse all of the page content. The media
     49 * query limits the first-render delay to when view transitions can run, matching the
     50 * `@view-transition` rule in view-transitions.css. It does not detect whether a transition is
     51 * actually in progress, so a reload or a direct visit is also held until `#wpcontent` is parsed,
     52 * but since that element starts right after the admin menu, the delay is negligible.
     53 *
     54 * Nothing is printed unless the admin View Transitions style has already been printed, since
     55 * there is otherwise no transition to wait for. That style is only enqueued from
     56 * wp-admin/admin-header.php, so this also leaves out iframe screens such as Thickbox modals,
     57 * which have no `#wpcontent`: an `expect` link whose target never appears blocks the first
     58 * render until the whole document has been parsed.
     59 *
     60 * @since 7.2.0
     61 *
     62 * @link https://html.spec.whatwg.org/multipage/links.html#link-type-expect
     63 */
     64function wp_print_view_transitions_render_blocking_link(): void {
     65        if ( ! wp_style_is( 'wp-view-transitions-admin', 'done' ) ) {
     66                return;
     67        }
     68
     69        echo '<link rel="expect" href="#wpcontent" blocking="render" media="(prefers-reduced-motion: no-preference)">' . "\n";
     70}
  • trunk/tests/phpunit/tests/view-transitions/wpEnqueueViewTransitionsAdminCss.php

    r63628 r64691  
    2727                $wp_styles = $this->original_wp_styles;
    2828                parent::tear_down();
    29         }
    30 
    31         /**
    32          * Tests that the hook for enqueuing admin view transitions CSS is set up.
    33          *
    34          * @ticket 64470
    35          */
    36         public function test_hook() {
    37                 $this->assertSame( 10, has_action( 'admin_enqueue_scripts', 'wp_enqueue_view_transitions_admin_css' ) );
    3829        }
    3930
Note: See TracChangeset for help on using the changeset viewer.