Adding view transitions to traintimes.org.uk

What are view transitions?

I’ve followed the addition of View Transitions to browsers, with blog posts like Jeremy’s, and have been wanting to try this out somewhere. This is the actual cutting edge, not whatever AI gimmick of the week there is (see Ed Zitron).

Note if you’re using Chrome 125 or above, you need to use the new
@view-transition { navigation: auto; }
in your CSS rather than a meta element in your HTML as many tutorials still have.

View transitions are a way for the browser to animate the view between two pages – originally added for single page applications using JavaScript, but now for two entirely separate page views. By default, page views will cross-fade, and named transitions (see below) will morph from their old to their new locations. Google have some nice documentation and examples. So I read things like that, and Dave’s tutorial and had something up and running with the default cross-fade.

What to implement?

What I decided I wanted as my first was to have the Earlier/Later buttons on traintimes.org.uk slide the results up/down, like you were scrolling through a longer list. I’m not sure this was the easiest thing to begin with, but seems like something that should be possible.

My first attempt worked okay, but in order to get it working I ended up passing the required transition in the URL, in order for the destination page to know which thing and direction to animate. This didn’t seem ideal, and so I tried again but storing the direction in the document somewhere.

My CSS currently looks like this, with added explanatory comments:

/* Activate view transitions on all pages */
@view-transition { navigation: auto; }

/* Wrapper divs of the outward and inward results lists,
 * named transitions as this is what we want to animate */
.results-out { view-transition-name: results-out; }
.results-in { view-transition-name: results-in; }

/* Define some animations - a fade in/out, and some slides, in and out, both up and down */
@keyframes fade-in { from { opacity: 0; } }
@keyframes fade-out { to { opacity: 0; } }
@keyframes slideoutup { to { transform: translateY(-30px); } }
@keyframes slideinup { from { transform: translateY(30px); } }
@keyframes slideoutdown { to { transform: translateY(30px); } }
@keyframes slideindown { from { transform: translateY(-30px); } }

/* Define the default animation behaviour (taken from a Google tutorial I now can't find) */
::view-transition-old(*), ::view-transition-new(*) {
    animation-duration: 90ms, 300ms;
    animation-timing-function: cubic-bezier(0.4, 0, 1, 1), cubic-bezier(0.4, 0, 0.2, 1);
    animation-fill-mode: both;
}

/* Now define all the possible transitions, depending upon which Earlier/Later has been clicked.
 * For example, if someone has clicked Later on the outward results, we want .results-out to
 * fade out upwards, and then the new ones to fade in sliding in upwards */
html:active-view-transition-type(out-earlier) {
    &::view-transition-old(results-out) { animation-name: fade-out, slideoutdown; }
    &::view-transition-new(results-out) { animation-name: fade-in, slideindown; }
}
html:active-view-transition-type(out-later) {
    &::view-transition-old(results-out) { animation-name: fade-out, slideoutup; }
    &::view-transition-new(results-out) { animation-name: fade-in, slideinup; }
}
html:active-view-transition-type(in-earlier) {
    &::view-transition-old(results-in) { animation-name: fade-out, slideoutdown; }
    &::view-transition-new(results-in) { animation-name: fade-in, slideindown; }
}
html:active-view-transition-type(in-later) {
    &::view-transition-old(results-in) { animation-name: fade-out, slideoutup; }
    &::view-transition-new(results-in) { animation-name: fade-in, slideinup; }
}

/* Overriding the animation when clicking back, I talk about this more below
.back-nav::view-transition-old(*), .back-nav::view-transition-new(*) { animation: none !important; }
*/
And a bit of JavaScript to try and store/work out which way to animate. The examples I have found all have ways to tell what to do from the URL (e.g. you have gone to a profile page, your pagination number has gone up or down) which I do not have, the URL tells me nothing about where I have come from. So I store information based upon what was clicked, for the new page to then look at:
var direction_clicked;
/* Detect clicks on Earlier/Later links */
document.getElementsByClassName('js-store-type').addEventListener('click', function() {
    direction_clicked = this.dataset.type;
});

/* When we are about to leave, if it's an actual navigation somewhere else, store the clicked direction */
window.addEventListener("pageswap", event => {
    console.log('pageswap');
    if (!event.viewTransition) {
        return;
    }
    if (event.activation.navigationType === "push") {
        sessionStorage.setItem('transition', direction_clicked);
        console.log('direction_clicked', direction_clicked);
    }
});

/* When we have arrived, look at any stored information and use it as the active transition type */
window.addEventListener("pagereveal", event => {
    console.log('pagereveal');
    if (!event.viewTransition) {
        return;
    }

    /* See below for more on this bit */
    //const is_back = navigation.activation.navigationType === "traverse" &&
    //    navigation.activation.entry?.index === (navigation.activation.from?.index - 1);
    //document.documentElement.classList.toggle("back-nav", is_back);
    //console.log('is_back', is_back);

    var transition = sessionStorage.getItem('transition');
    console.log('transition', transition);
    event.viewTransition.types.add(transition);
});

Problems

EMF

If any of you are going to EMF this year, I will hopefully be there on the Saturday, giving a short talk on my Post Office Inquiry website and saying hi to some people I don’t see very often. Perhaps see you there!