tl;dr: Rik discovers the joy of presenting his poems to the world in textual, audio and video formats
This is the last in a series of blog posts detailing my journey to rebuild my poetry website - The RikVerse - using Svelte.js, Page.js and Tailwind CSS. Other posts in this series:
- Introduction to the project
- Setting up the shiny
- The client is not the server - 404 errors
- Building the blog reader
- Fun with Cookies!
- The book store bits
- Sharing is Caring - social media share buttons
- … And finally: the poems!
The code developed in this blog post can be found on GitHub.
Where were we?
In the previous post I developed the functionality required to share my site’s pages onto Facebook and Twitter.
Here’s a quick reminder of my goals for this site rebuild:
- Index of all the poems available to read
- Tag filtering functionality on the poems index page
- Easy for the user to access a poem’s associated media files
- The landing page should display a randomly selected poem
[Done] Simpler, more minimal design; better fonts[Done] Navigation needs to be massively simplified[Done] Avoid 404 Page Not Found errors at all costs![Done] Add in poetry-and-writing-related blog posts[Done] Get rid of the database![Done] Keep the donate button; make it More Fun To Use[Done] Add in cookie consent (meeting UK ICO guidance for explicit opt-in)[Done] Make each publication page that book’s keystone page[Done] Let people read the books in-site[Done] Add social media share buttons to each page[Done] Fix the metadata
Building the poem index and poem display components
The main purpose of the RikVerse is to share my poems with the world. To do this, I need to display each poem on its own page. I also need to list all the poems in an index page with links to each poem.
… Which is pretty much the same as the work I’ve already done when I built a blog reader for the site. So I won’t be spending much time here detailing that work.
Instead, I’ll concentrate on the differences between the blog reader and the poem display functionality, as this is much more interesting stuff:
I’ve written many more poems than I have blog posts, so I need a way for the user to filter the poems index page using tags.
All of my completed poems are released into the wild with a Creative Commons licence (see this RikVerse blog post for more information) - the practical implication is that I need to display the correct Creative Commons licence alongside each poem, including its index page listing.
Some of the poems have audio clips of me reading them; others have video clips of me performing them. A few poems have both audio and video clips. So I need to build a mechanism for playing audio and/or video clips when a user requests it, but hide away the media players when not in use.
Finally, I have a requirement to display a randomly selected poem on various pages - specifically:
- The landing page - as a welcome gift;
- The error page - as an apology; and
- The privacy and security page - to congratulate the user on finding, and reading, it.
Just as for the blog posts, I’ve divided each poem’s data into two parts:
The bit you read - the “copy” - each poem has its own
.html
file containing the copy, which I save to the./public/poemCopy/
folder for easy fetching; andThe various bits of data surrounding the poem, such as title, publication date, tags, etc - the “meta” - all of which gets stored as an object in an array in the
./src/data/poemData.mjs
Javascript module file.
The objects themselves have the following attributes:
1 | { |
A poem’s “copy” is a normal html
fragment which gets fetched and inserted when the poem displays. Because it’s just html
, I can include Tailwind CSS classes in the markup.
This is where Tailwind really comes into its own! Formatting a poem to display correctly on a web page can be one of the most frustrating jobs known to developers - Tailwind classes solve a lot of the problems!
Markup like this …
1 | <p>His fortune lies in heaps<br /> |
… Displays like this:
Filtering the Poems Index page using tags
Code for the PoemsIndex component can be found in the ./src/pages/PoemsIndex.svelte
file. All the functionality for splitting poems into “completed” and “draft” sections, and for filtering poems based on tags associated with each poem, happens here:
1 | <script> |
Unlike other page components, the PoemsIndex component needs to keep track of its state - whether the user is currently filtering the listing on one of the #tag values.
A Svelte store is perfect for this work: I create and export the store in ./src/handleMetadata.js
. When a user filters the list, then leaves the page to read a poem, the page remembers the selected filter tag for whenever the user returns to the list.
Display the correct Creative Commons licence alongside each poem
The screenshots above both show Creative Commons licence icons displayed at the end of the poem, and as part of each poem’s listing in the index. This is two distinctive uses, driven by the same underlying function.
I use two CC licences across the RikVerse. The first, quite restrictive licence is for poems that I consider to be completed less than 15 years ago. For completed poems older than 15 years I apply a much less stringent licence.
The function for determining a poem’s licence is nothing special. I keep it in the ./src/utilities.js
file:
1 | // Creative Commons licence checker |
I could have saved myself some coding effort by including the Moment.js library in the build. But the RikVerse site’s requirements for date/time functionality is so trivial that I decided to do it myself, which saves the user a few extra kilobytes of Javascript download time.
… Every microsecond counts - as they say!
Displaying the poems in their full glory
The last few pages I need to build are the pages which display a poem. There’s four of these pages:
./src/pages/Home.svelte
- the page used as the site’s landing page./src/pages/Privacy.svelte
- the privacy and security notice page./src/pages/ErrorPage.svelte
- the SPA’s 404 Page Not Found error page./src/pages/Poem.svelte
- the page called by PoemIndex listing links
Home.svelte
is the only page which doesn’t keep its metadata in the ./src/data/pageData.mjs
file. This is because its path is /
- which matches the path of ./public/index.html
. The absolute last thing I want to do is accidentally overwrite the RikVerse site’s keystone file when I run my build toolchain functionality!
Other than that, the pages are very similar. They differ only in their mechanisms to discover which poem they should display, and in copy specific to that page.
The code for Poem.svelte
is representative of them all:
1 | <script> |
All four components rely on another component - ./src/components/PoemCard.svelte
- to display the selected poem.
The poem display is the most complex display on the RikVerse website. Not only does it need to display the text of the poem, it also needs to include any image, audio and/or video files associated with the poem.
For this reason, PoemCard.svelte
relies on a final set of components to do the work for it. The only work it performs itself is to fetch the poem’s html
copy:
1 | <script> |
The PoemImage
and PoemLicence
Svelte components are nothing we haven’t seen before - visit the GitHub repository to see their code.
Watch and/or Listen to Rik reading his poems
The PoemNavigation.svelte
component is where the last of the RikVerse magic happens. The component’s job is to coordinate the activities of the <Video>
and <Audio>
HTML5 elements, and to make sure that only one of those elements gets displayed at any one time.
Because much of the functionality relies on Svelte state to operate correctly, I created a ./src/handleMedia.js
Javascript module to keep track of the media elements. This module exports ten objects and functions:
1 | import { writable } from 'svelte/store'; |
The functionality specific to the <audio>
and <video>
elements gets handled in dedicated components - AudioPlayer.svelte
and VideoPlayer.svelte
.
The job of these components is to define their media element, set a handle for the element each time it is created, and (gracefully) show and hide the element when the user clicks a button to play or stop the media.
This is the VideoPlayer
code. The AudioPlayer
code differs only in small details.
1 | <script> |
The VideoPlayer
component has two jobs:
When the component is created, it has to locate the
<video>
element that gets added to the page, and put a handle to the element into thevideoController
variable (which is NOT a Svelte store) inhandleMedia.js
Then, when the Svelte
videoIsPlaying
store resolves to1
(true), theVideoPlayer
component adds avideo-active
class to the<video>
element, which triggers its reveal through a CSS transition. The element will hide itself whenvideoIsPlaying
resolves to0
(false).
So we have a VideoPlayer
component, and an AudioPlayer
component, and some functionality to keep and manipulate the state required for these components in the handleMedia.js
Javascript module.
These three parts need to be bought together. That’s the job of our final component - PoemNavigator.svelte
:
1 | <script> |
The navigation consists of between one and three buttons - depending on whether or not the poem has associated video and/or audio media.
The back button is always present; when the user clicks on this button the site navigates itself back to the Poems Index page.
The video button and audio button only appear when the poem has a video or audio file to play; when the user clicks on either of these buttons,
PoemNavigator
will start playing the associated media - first stopping any other media that is playing. Or alternatively it will stop the media playing.
… Yeah. It all sounds a bit complicated. But compared to payment gateways it ain’t complicated at all!
Wrap up
I have very much enjoyed this whole learning experience. The new shiny I’ve learned about - Svelte, Tailwind, Page.js - have solved far more problems than they’ve created for me.
Svelte site builder has been a revelation. This is how building a site with components should be!
- It’s very easy to learn
- Coding a component using (mostly) standard HTML, CSS and Javascript makes life so much easier
- The state mechanisms it introduces make sense, and are (relatively) simple to use
- I’ve barely scratched the surface of what this framework can do - for instance: mount/destroy; events; binding; tweens and animation; etc. I’ll investigate these things in my next personal project.
Tailwind CSS is a joy to use - mainly because it doesn’t have much in the way of opinions:
- The utility-first approach suits my programming style
- Setup was a little tricky, but once done the integration worked perfectly
- It’s very customizable - if I can manage to customize it for fonts, anybody can!
- It has a whole plugin ecosystem I’ve not yet investigated - the basics were enough for me
- (The latest version now supports grids and transforms - yay!)
PostCSS pre-processing - I learned nothing about this shiny; after I set it up (alongside Tailwind) I never had to think about it again.
Page.js routing - what can I say? It works for me!
- The routing code I borrowed from Jack Whiting was enough for my needs
- Which means there’s an awful lot more that this library has to offer that I haven’t bothered learning about (yet)
And, to cap it all
… I now have a fantastic new website for my poems and books! Here’s some before-and-after comparisons for new visits to each site’s landing page
Comparison of the two sites
Comparison Old site New site
------------------------------------------------------------------------------
Design Cluttered, unfocussed Clean, purposeful
UX navigation Obtuse, unfriendly Simple, Accessible
Responsiveness Fixed width, ugly Fully responsive
Cookies Ignores user wishes Meets current requirements
Security No details offered Explains data/cookie usage
Uses jQuery/UI Yes No
Uses Google Fonts Yes No
Backend Apache/PHP/MySql None beyond serving files
Networking:
Requests sent 34 requests 13 requests
Transfer load 362 KB 267 KB
Resources sent 613 KB 465 KB
DOMContentLoaded 412 ms 288 ms
Load completes in 1090 ms 511 ms
Finished in 1110 ms 604 ms
If you’ve enjoyed this series of blog posts, or found them useful - good. That makes me happy!
And if you spot any serious errors in my code, or can suggest better ways to code up various functionalities … please let me know. I want the RikVerse to be the best personal poetry website in the world - and I’m always up for new learning opportunities!