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.
If you like the song, you can buy the whole EP on Google Play, Amazon Music or iTunes – or stream it on Spotify:
</self-promotion>
The player
The goal was to make the video player always take 100% of the browser height and be centered horizontally.
The simplified markup is: <div class="container"><iframe></iframe></div>
– the iframe containing the YouTube video.
The container has the width and height set to 100vw
and
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 #player
div, and the YouTube JavaScript iframe embed method creates the
iframe and appends it to the DOM. Please refer to the
official YouTube API Reference for more
information.
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.
The scribbles
There were two problems to solve regarding the scribbles:
1) make them appear at the right time and
2) position them properly.
1) Timing
To make them appear at the right time, I made use of YouTubes JavaScript API. In the onStateChange
event, I checked whether event.data == YT.PlayerState.PLAYING
and if so, I started a periodic timer
with setInterval(updateTime, intervalDelay)
, with updateTime
being the periodically
invoked callback, and intervalDelay
the time between each interval (I chose 25ms).
Inside the 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
that: <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.
2) Positioning
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 apng
(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:
Final Words
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.