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).
@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
- Sometimes the pagereveal event is just... not firing? Which sort of ruins the whole thing.
It seems entirely random. Here is a little animation I made, my first click it worked fine
(and this also shows you the animation working), but my second, there was no reveal event and so
it fell back to the default cross fade. Anyone with any ideas here, do get in touch.
- Back and forward. As you might be able to work out from the above code, if you
click Earlier/Later a few times and then start clicking Back/Forward, the site
will still be reading the sessionStorage item and repeat whatever the last
Earlier/Later animation was (which is quite possibly the reverse of what you
would expect).
I have tried e.g. setting
in the pageswap, then in the pagereveal if that's present, doing something likedocument.documentElement.dataset.transition = direction_clicked;
(rather than using the sessionStorage item) to try and swap for the other direction animation. But this just stops the animation working entirely (except occasionally?!), and I have not yet worked out why, I can't see why it shouldn’t be working. But I’m also not sure that would fix it for every back/forward, perhaps only backs? If I cannot work it out, having nothing on back/forward is probably better than it animating the wrong way. But if anyone can see what I need to fix, please do let me know :)transition = document.documentElement.dataset.transition.replace('in', 'foo').replace('out', 'in').replace('foo', 'out');
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!