I just released my Solo EP «Flashlights» and built a page to promote its first single of the same title. Since the page itself is a bit more than a collection of links, and interacts with the video playing in the background, I figured that it might be of interest to some people to gain insight into the build process.
So first things first: go to meno.fm/flashlights and watch the video to see the effects in action. Unfortunately it is built to be viewed for Desktop, so if you are on mobile you will only be able to see the plain video:
What you see on the page, is the video playing in the background with the text appearing at the time of the first verse, with scribbles on top of it, guiding you through the lyrics. Here and there I added small visual effects that make all lyrics shake or disappear at appropriate times.
The goal was to make the video player always take 100% of the browser height and be centered horizontally.
The simplified markup is:
– the iframe containing the YouTube video.
The container has the width and height set to
100vh respectively (viewport units), to make sure it consumes 100% of the browser space and has
overflow: hidden so the browser won't introduce scroll bars if the video sticks out.
The iframe is positioned absolutely inside the container with a height of
100vh as well.
To make sure it always has the right dimensions (16:9) I make use of a fairly new CSS function
to calculate the width:
width: calc(100vh * 1.777).
I then positioned the iframe at the top and
left: 50%, which means that the left edge of the iframe
will be at exactly 50% of the browser window. Now all that was needed, was to adjust this position so it's centered,
which means that I needed to move it exactly half of its width to the left:
margin-left: calc((-100vh * 1.777) / 2)
Sidenotes: setting the width and height actively is necessary for YouTube to get the proper dimensions, otherwise
YouTube automatically scales down the content for the available space and will not allow the video to be wider than
the actual browser size.
Also, the iframe is actually in a
iframe and appends it to the DOM. Please refer to the
official YouTube API Reference for more
Now that I had a nice player in the background I just made sure that pointer events weren’t forwarded to the YouTube player by overlaying an invisible div that captures all those events (so the YouTube UI doesn’t appear when hovering the player) and using the YouTube Player API to forward any events to pause or resume the video when the container is clicked and to implement my own timeline & seeking behaviour.
There were two problems to solve regarding the scribbles:
1) make them appear at the right time and 2) position them properly.
event, I checked whether
event.data == YT.PlayerState.PLAYING and if so, I started a periodic timer
setInterval(updateTime, intervalDelay), with
updateTime being the periodically
invoked callback, and
intervalDelay the time between each interval (I chose 25ms).
updateTime I used the
player.getCurrentTime() function to get the time of the video.
Unfortunately, the time returned by this function is not very precise (if queried in a 25ms interval, you will get the same time multiple times), so I needed to adjust the time accordingly to make sure that the scribbles appear at exactly the time I wanted them to. The easiest way to address this issue, was to simply add 25ms to the last time retrieved, as long as the time returned by
getCurrentTime() is the same.
The mundane part then, was to get all the timings of the scribbles. I did that by loading the video into a video editing
software, and write down all the time codes. I then marked up the individual phrases that I wanted to add scribbles to like
<span data-time="33.92">vultures</span>. The
highlight() function checks if one of the phrases should be
highlighted based on that
date-time attribute, and if so, it adds the
.highlight class which makes the
scribble appear and then fade out.
But how did I add the scribble, and how are the scribbles positioned? Well… that actually took me some time to get right. I wanted to find a solution that met all the following criteria:
- I didn’t want individual images to be loaded due to http overhead, but one spritemap
- the spritemap should be generated automatically – I didn’t want to copy paste images all day
- the scribbles should be perfectly positioned, accounting for different font renderings (so just using one big image over the whole paragraph and positioning the scribbles is a no-no)
- I didn’t want to fiddle with coordinates and painstakingly write down pixels for each scribble
- This one is basically a summary of the others: I wanted to be able to iterate quickly. So when I wanted to make changes to the scribbles, I didn’t want to change coordinates in my source code or have a manual process where I need to copy some images around.
To solve all those problems, I made following decisions:
- Every highlights position is measured from the center of a
<span>of a single word or phrase. So let’s say the word «Flashlight» should be highlighted, then my script adds an additional
<span>inside this word’s span, positioning it at the center.
- This allows me to create the scribbles inside Photoshop, by defining a fixed width/height rectangle for each word,
positioning the individual words I want to highlight at the center of each rectangle, and just draw over it:
In the end, I just save the scribbles without background or text as a
png(so just scribbles with a transparent background). This approach ensures minimal displacement, since even very different font rendering will render the word at pretty much the same position
- I could already use this image as a CSS spritemap, but this image is obviously a lot larger than needed, and contains a lot of unnecessary white space. So I wrote a script that goes through each rectangle, trims the white space around each scribble, generates a new optimized spritemap and generates a json file with the necessary information to be able to position each scribble at the center again (depending on how much white space has been removed on each side). You might be thinking that this step is unnecessary, because empty space in a png should be compressed properly, but unfortunately, even after png optimization, there is a significant difference in size. It will also take much less space in memory in the browser.
- I then use optipng on the spritemap to ensure that it is the smallest possible size
- Finally, inside my browser script, I use the generated JSON to properly position all scribbles, with the resulting
elements looking like this:
The whole page is open source, so feel free to go through it if you want more information: github.com/enyo/meno.fm
I hope that this post has been useful to you and that it will help you create something great.