View Transitions and an Astro Presentation Framework
06 March 2024
Updated: 17 April 2024
Well, since this is a post about building a presentation framework within your Astro site, it may be worth mentioning that you can view this page as a presentation using the below button:
The Problem
I’m kind of lazy.
I had to put together a little presentation based on something I’ve written about previously and wanted a lazy way to reuse my existing content while also making the resulting presentation available on my website
Overall, these are the requirements I had in mind:
My Requirements
Not require any additional build process
Work with Markdown or MDX so I can include it in my website easily
Have a small learning curve
Integrate flexibly with my existing content - pages should be able to be very easily converted to slides
The existing solutions just don’t work for my case
Now, it’s not that they’re not good - most of them are pretty great and have some features that I would like to use if this were some once-off throwaway presentation, but since I would like to refer back to and manage the way I want they’re not really suitable
I also didn’t want to style everything from scratch or write lots of HTML everywhere
The Solution
Build it myself. Obviously
Instead of just looking at the existing options, I instead chose to build a library/framework that would work with my existing Astro site while keeping the implementation relatively minimal and just depending on plain CSS, Javascript, MDX, and Astro to get the job done
Use Existing Tooling
HTML
CSS
Javascript (Typescript)
MDX
Facilitated by - not coupled to - Astro
Code
So, since I’m building it myself, that means code - and that’s what we’re going to look at
The API
I wanted to keep the API relatively simple. It should work with existing markdown content and allow me to delineate a slide in a way that can be easily read from the DOM so as to minimize the amount of build-time processing I need to do as well as minimize how much markup I need to write. For this purpose, I decided that I want it to fit into an MDX document like so:
The API
Hiven the above, I had a high level idea that I would need two parts to this - firstly I want to use the Slide component to give me something to latch onto in the HTML that I can manipulate, secondly I know I would need some kind of component that would control the overall presentation state, I called that Presentation
The Slide Component
The Slide component is simply a wrapper that includes content in an HTML section with a class presentation-slide which will contain the contents of a slide
The Slide Component
components/Slide.astro
The Presentation Component
The Presentation component needs to do a few different things:
The Presentation Component
Hide presentation until enabled
Allow navigation of slides
Render slide content above existing page
Manage transitions between slide pages
Firstly, we can just take a look at the HTML that we will render contains a few basic elements as well as a script tag that grabs a reference to these elements. The presentation-hidden class is used for hiding or showing the presentation when active/inactive:
Basic Elements
components/Presentation.astro
Next, we can grab the actual slide content by using the presentation-slide class we defined earlier:
Get Slides
components/Presentation.astro
Once we have the content of the slides and a variable to track which slide we are on, we can define a function that will set the slide content. This will set the innerHTML of the content element to the slide that is active. We can handle this by first defining some utilities for grabbing the next and previous slides as well as mapping a key code to the function that will resolve the next slide
Slide Utilities
components/Presentation.astro
Next, we can define what it means for us to start and end a presentation. For this example, starting a presentation will remove the presentation-hidden class from the main wrapper so we can make the presentation visible on the page as well as set the content to the current slide index (we initialized this to 0 above)
Start and End Presentation
components/Presentation.astro
We set the content to slide instead of 0 so that we can pause and continue the presentation if we wanted to
Next, hook up some event handlers so that we can have a method for controlling our presentation:
Wiring things up
components/Presentation.astro
In the above, the left and right arrows are used to navigate slides and the escape key is used to end the presentation
Next up, we need to add some CSS to make the slides pin to the root of our application above everything else so that you can actually use this:
Styling
components/Presentation.astro
And that’s pretty much it for the core implementation. One other piece of fanciness that I wanted to add was the ability to make an actual slide transition. To do this I decided to use the View Transitions API and found a few nice references on the Unecesssary View Transitions API List
In order to use this you need to have the feature enabled in your browser at the moment but it should be stable soon (I hope)
For this implementation, we will need to have different animations for the case where we are moving forwards or backwards. In order to do this, we will define some classes as part of our keyboard handler resolution that we will append to the presentation-container:
View Transitions
components/Presentation.astro
Then, we will update our event handling logic to set these classes on the contaienr
Setting the Classes
components/Presentation.astro
Then, instead just setting the content.innerHTML directly, we do it within the document.startViewTransition callback which will be what handles the state transition between the content leaving the DOM and the new content that is entering the DOM
Note that the startViewTransition API is experimental and typescript may complain, you will need to install @types/dom-view-transitions which will provide the type definition you need to use this API
Starting the Transition
components/Presentation.astro
The last thing we need to do is define the view transitions for when the content enters and exists the DOM. The transitions are defined in the style tag of our component as follows:
Firstly we need to
Animations
components/Presentation.astro
The above defines two basic animations that we will use for our transitions. We define an animation that moves an element off the screen to the right called slide-out-right and another to move it to the left called slide-out-left. These animations can also be reversed to slide content in from the right or left respectively
For the “Next” animation we need to do the following:
Slide the old content to the left
Slide the new content from the right
In the below we set that the presentation-next class defines a view-transition-name called next. Then, we define the transitions for the old and new content that applies to the next transition name as an Animation. We are referencing a slide-out-right and slide-out-left animations which we defined previously:
CSS Transitions
Below is the transition for moving to the next slide
components/Presentation.astro
view-transition-old refers to content that is being removed from the DOM, view-transition-new refers to the content that is being added to the DOM
Lastly, the implementation for the presentation-prev we can reuse the same animations for moving left or right as we defined previously, but change the directions as needed for the relevant section
components/Presentation.astro
And yes, that’s a fair amount of code. All-in it’s about 200 lines - most of which is the CSS for the transition though. Generally the implementation is pretty straightforward and should be relatively easy to tweak to match the vibe of your website without adding any dependency bloat.
As it stands right now the implementation is pretty simple but leaves a lot of space to be extended
Added since this post was written
Presenter mode with some kind of synchronized state for multiple monitors (LocalStorage?)
Progress tracking
Support for non-static components
Future Ideas
Presenter notes and preview of next slide
Make this a library so other people can use it with less copy pasta
We used some interesting CSS here and overall we can see that it’s not alays a huge amount of work to write your own implementation of something.
Additionally, for the sake of completeness - since this component is alive and ever changing within this website - you can view the current state of the code (sans commentary) below