Skip to content
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

Explore view transitions in Twenty Fifteen for post title and featured image specifically #8131

Draft
wants to merge 7 commits into
base: trunk
Choose a base branch
from
3 changes: 3 additions & 0 deletions src/wp-content/themes/twentyfifteen/css/view-transitions.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@view-transition {
navigation: auto;
}
15 changes: 15 additions & 0 deletions src/wp-content/themes/twentyfifteen/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,21 @@ function twentyfifteen_scripts() {
'collapse' => '<span class="screen-reader-text">' . __( 'collapse child menu', 'twentyfifteen' ) . '</span>',
)
);

wp_enqueue_style(
'twentyfifteen-view-transitions',
get_template_directory_uri() . '/css/view-transitions.css',
array(),
'20250115'
);

// This script must be loaded prior to rendering, i.e. not in the footer and not deferred or async.
wp_enqueue_script(
'twentyfifteen-view-transitions',
get_template_directory_uri() . '/js/view-transitions.js',
array(),
'20250115'
);
}
add_action( 'wp_enqueue_scripts', 'twentyfifteen_scripts' );

Expand Down
91 changes: 91 additions & 0 deletions src/wp-content/themes/twentyfifteen/js/view-transitions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
if ( !! window.navigation && 'CSSViewTransitionRule' in window ) {
const getParentElement = ( element, selector ) => {
element = element.parentElement;
while ( element && element !== document ) {
if ( ! selector || element.matches( selector ) ) {
return element;
}
}
return null;
};
Copy link

Choose a reason for hiding this comment

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

@felixarntz: I’ve narrowed the problem you are having down to this getClosestParent function: it gets stuck in a loop because element remains the same while inside loop.

The fix:

Suggested change
const getParentElement = ( element, selector ) => {
element = element.parentElement;
while ( element && element !== document ) {
if ( ! selector || element.matches( selector ) ) {
return element;
}
}
return null;
};
const getParentElement = ( element, selector ) => {
element = element.parentElement;
while ( element && element !== document ) {
if ( ! selector || element.matches( selector ) ) {
return element;
}
element = element.parentElement;
}
return null;
};

Furthermore I think you could replace the functionality entirely with Element.closest()

Copy link
Member Author

Choose a reason for hiding this comment

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

Yikes - of course it had to be an oversight like this 🤦

Thanks! Great point on using closest, I've updated this in ab9b25b - works like a charm now!


const setTemporaryViewTransitionNames = async ( entries, vtPromise ) => {
for ( const [ element, name ] of entries ) {
if ( ! element ) {
continue;
}
element.style.viewTransitionName = name;
}

await vtPromise;

for ( const [ element, name ] of entries ) {
if ( ! element ) {
continue;
}
element.style.viewTransitionName = '';
}
};

window.addEventListener( 'pageswap', ( e ) => {
if ( e.viewTransition ) {
if ( document.body.classList.contains( 'single' ) ) {
const article = document.querySelectorAll( 'article.post' );
if ( article.length !== 1 ) {
return;
}

setTemporaryViewTransitionNames( [
[ article[ 0 ].querySelector( '.entry-title' ), 'post-title' ],
[ article[ 0 ].querySelector( '.post-thumbnail' ), 'post-thumbnail' ],
], e.viewTransition.finished );
} else if ( document.body.classList.contains( 'home' ) || document.body.classList.contains( 'archive' ) ) {
const articleLink = document.querySelector( 'article.post a[href="' + e.activation.entry.url + '"]' );
if ( ! articleLink ) {
return;
}
Comment on lines +33 to +36
Copy link
Member Author

Choose a reason for hiding this comment

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

@bramus I debugged the issue further and got as far as figuring out that it, for some reason, comes down to this:

  • When you click on the link to an article without a featured image from the home page, the articleLink variable here will be empty - at least this is how it shows when I add a console.log( articleLink ); below.
  • This causes the early return to trigger.

I still don't understand why that's happening, for two reasons:

  1. If I manually run this exact document.querySelector( ... ) call in the console using the URL of that post in question, I do get a result, which is the a element in the post title. That's what I would expect. But for some reason, during the view transition it returns nothing.
  2. The early return here is triggered if articleLink is empty. But that shouldn't cause the browser to infinitely get stuck on loading the page right? Or is there any "cleanup" to do if a view transition is "aborted" with an early return like this?


const article = getParentElement( articleLink, 'article.post' );
Copy link

Choose a reason for hiding this comment

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

I believe this code throws when there is no article.

Suggested change
const article = getParentElement( articleLink, 'article.post' );
const article = getParentElement( articleLink, 'article.post' );
if ( ! article ) return;

Copy link
Member Author

Choose a reason for hiding this comment

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

Great catch!

That said, this is not what caused the problem, it wasn't actually happening. Still a good safeguard to have in any case.


setTemporaryViewTransitionNames( [
[ article.querySelector( '.entry-title' ), 'post-title' ],
[ article.querySelector( '.post-thumbnail' ), 'post-thumbnail' ],
], e.viewTransition.finished );
}
}
} );

window.addEventListener( 'pagereveal', ( e ) => {
if ( ! window.navigation.activation.from ) {
return;
}

if ( e.viewTransition ) {
if ( document.body.classList.contains( 'single' ) ) {
const article = document.querySelectorAll( 'article.post' );
if ( article.length !== 1 ) {
return;
}

setTemporaryViewTransitionNames( [
[ article[ 0 ].querySelector( '.entry-title' ), 'post-title' ],
[ article[ 0 ].querySelector( '.post-thumbnail' ), 'post-thumbnail' ],
], e.viewTransition.ready );
} else if ( document.body.classList.contains( 'home' ) || document.body.classList.contains( 'archive' ) ) {
const articleLink = document.querySelector( 'article.post a[href="' + window.navigation.activation.from.url + '"]' );
if ( ! articleLink ) {
return;
}

const article = getParentElement( articleLink, 'article.post' );

setTemporaryViewTransitionNames( [
[ article.querySelector( '.entry-title' ), 'post-title' ],
[ article.querySelector( '.post-thumbnail' ), 'post-thumbnail' ],
], e.viewTransition.ready );
}
}
} );
Copy link
Member Author

Choose a reason for hiding this comment

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

@noamr @bramus Maybe you can help me out here: With this current logic, whenever I click on a link to open a WordPress post that does not have a featured image (post-thumbnail) assigned, my browser gets stuck and seems to endlessly load the page.

Maybe it's just a simple logic flaw, but I'm not sure what's missing. My setTemporaryViewTransitionNames implementation has a condition to skip any elements that are null (via ! element), so I would think such a scenario should be covered. But maybe something else is wrong.

} else {
window.console.warn( 'View transitions not loaded as the browser is lacking support.' );
}
Loading