Poster images allow you to display an enticing image behind the video player’s play button before users dive into the video itself. HTML has the use case covered, providing a poster
attribute for the <video>
element. There’s one hiccup; it allows for only one URL. This means either:
- devices with narrow screens download a heavy high-resolution image, increasing load times and wasting users’ bandwidth
- or those with wide screens get a poor resolution one that’ll look terrible.
Both a poor experience for users.
With a little CSS, we can make things look much nicer and load the appropriate image for our user’s device. If we upload an image with ActiveStorage, we can then use that to generate the CSS automatically.
Responsive poster image with CSS
This StackOverflow answer describes a nice workaround to take control of how the poster image is rendered with CSS:
- use a transparent GIF for the poster image, preventing the browser from picking a frame of the video, while still allowing to use
preload="auto"
to start loading the video as soon as possible - set the poster image as the video’s background image.
<video class="responsive-background" poster="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"> <!-- Video sources --> </video>
Once CSS is responsible for the poster’s rendering, we’re in a good place as we have access to media queries to pick a different background based on the screen size.
.responsive-background { background-image: url('tiny-poster.jpeg'); background-position: center; background-size: cover; } @media (min-width: 75rem) { .responsive-background { background-image: url('large-poster.jpeg'); } }
Modern browsers will only download the appropriate background. Narrow screens will only download the tiny poster and wider ones, only the large poster. And as a bonus, we now have more control over the poster’s positioning, as we can use the various background-...
properties to adjust how it displays.
Generating the CSS for an image in ActiveStorage
For poster images uploaded by users, this CSS needs to be generated by the server. A little helper can neatly encapsulate this. It’ll turn an ActiveStorage attachment into a set of CSS rules, based on a given list of breakpoints and the selector we want to use for attaching the styles.
module ResponsiveBackgroundHelper # Generates the CSS necessary to create a responsive background image # # @param attachment - The ActiveStorage attachment with the image # @param selector [String] - The selector the styles will be attached with # @param breakpoints [Array<Number>] - The list of breakpoints. Default based on https://www.freecodecamp.org/news/the-100-correct-way-to-do-css-breakpoints-88d6a5ba1862/ # @return [String] The CSS string with the media queries that define the responsive background def responsive_background_style(attachment, selector: '.responsive-background', breakpoints: [400,600, 900,1200, 1800]) # Create a list of "queries" object that will simplify the CSS rendering # Start with the smallest image without breakpoint queries = [{url: polymorphic_url(attachment.variant(resize_to_limit: [breakpoints[0],breakpoints[0]]))}] # Gather the URLs for each breakpoints, picking the image one step higher breakpoints.each_with_index do |breakpoint, index| if breakpoints[index+1].present? url = polymorphic_url(attachment.variant(resize_to_limit: [breakpoints[index+1],breakpoints[index+1]])) else url = polymorphic_url(attachment) end queries += [{breakpoint: breakpoint, url: url }] end # Generate the CSS based on the "queries" css = ''; queries.each do |query| if (query.fetch(:breakpoint, nil)) # Use 'rem' to not leave behind users # who need a different default font size # and have set their browser up accordingly css += " @media (min-width: #{query[:breakpoint] / 16}rem) { " end css += "#{selector} { background-image: url('#{query[:url]}'); }" if (query.fetch(:breakpoint, nil)) css += " }" end end css.html_safe end end
Inside templates, the helper can then be used to create an inline <style>
tag (possibly lifted into the header with content_for
, but that’s up to you).
:style #{responsive_background_style(@video.poster)} %video.responsive-background(...)
The helper is not limited to backgrounds for video poster too. It can be used for any background image coming from an ActiveStorage upload.
I’m sure a similar API would allow grabbing the URLs from PaperClip if that’s what your project uses. And if you feel adventurous, the breakpoints could be customized image by image, as not all images get the same weight loss for the same size reduction. But those are both very separate issues from rendering video posters responsively from ActiveStorage.