Fast Video Banners that Work on Every Device

Fast Video Banners that Work on Every Device

We've all been there: you feature an amazing video on your landing page to engage your visitors. You open the page... and nothing. Just a blank rectangle staring back at you while the video loads. Or worse, a tiny, pixelated poster image stretched across your large 4K monitor.

Video loading can be frustratingly slow, especially on mobile connections. But even when videos are loading quickly, the experience often feels broken because users see poorly scaled poster images that bear little resemblance to the crisp video that eventually loads.

The poster attribute: A partial solution Jump to heading

The HTML5 <video> element's poster attribute was designed to solve this exact problem. It displays a preview image while the video loads, giving users something meaningful to look at instead of a blank void:

<video controls poster="hero-video-poster.jpg">
<source src="hero-video-desktop.mp4" type="video/mp4" media="(min-width: 750px)">
<source src="hero-video-mobile.mp4" type="video/mp4" media="(max-width: 749px)">
</video>

This works great: users see a relevant image while the video loads, the experience feels faster, and everyone's happy. But cross-device testing reveals a significant limitation.

The responsive poster problem Jump to heading

There's a fundamental problem with this approach. While the <video> element supports multiple sources for different viewport sizes (enabling responsive video delivery), the poster attribute doesn't. It only accepts a single image URL. This becomes especially problematic when serving different video orientations (such as landscape videos for desktop and portrait videos for mobile) or when the video content itself varies between devices.

The result? A guaranteed mismatch somewhere in your responsive design.

The poster problem breaks down like this:

  • Desktop poster + mobile video: Your mobile users see a stretched, distorted poster that doesn't match the portrait video they're about to watch
  • Mobile poster + desktop video: Your desktop users get a tiny, pixelated poster when scaled up to fill their widescreen

The picture + video solution Jump to heading

We can use a responsive <picture> element positioned behind the video with CSS to create the illusion of responsive poster images.

The technique works like this:

  1. Create a <picture> element with responsive sources for different viewport sizes
  2. Position the <video> element on top with a higher z-index
  3. When the video loads and starts playing, it naturally covers the picture
  4. Users see the perfect poster image for their device while the video loads

Here's an example of how the HTML would look:

<div class="video-wrapper">
<picture class="video-poster">
<!-- Mobile source -->
<source
srcset="poster-mobile-416.jpg 416w,
poster-mobile-600.jpg 600w,
poster-mobile-800.jpg 800w,
poster-mobile-1200.jpg 1200w"

media="(max-width: 749px)"
sizes="100vw">

<!-- Desktop source -->
<source
srcset="poster-desktop-832.jpg 832w,
poster-desktop-1200.jpg 1200w,
poster-desktop-1600.jpg 1600w,
poster-desktop-1920.jpg 1920w"

media="(min-width: 750px)"
sizes="100vw">

<img
src="poster-desktop-1920.jpg"
alt="Video preview"
fetchpriority="high"
loading="eager">

</picture>

<video muted autoplay loop playsinline preload="metadata" class="responsive-video">
<!-- Mobile source -->
<source src="video-mobile.mp4" type="video/mp4" media="(max-width: 749px)">
<!-- Desktop source -->
<source src="video-desktop.mp4" type="video/mp4" media="(min-width: 750px)">
Your browser does not support the video tag.
</video>
</div>

This example assumes the element is positioned above the fold and likely serves as the page's Largest Contentful Paint (LCP) element. Therefore, the <img> element includes fetchpriority="high" and loading="eager" attributes to prioritize loading and ensure the fastest possible delivery to users.

Shopify implementation with Liquid and CSS Jump to heading

Now let's get into the Shopify-specific implementation. Shopify makes this surprisingly straightforward thanks to a built-in feature that many developers overlook.

The magic of preview_image Jump to heading

When you upload a video to Shopify, it automatically generates a preview_image from the first frame. This property is available on all media types in Shopify, including videos. This means you don't need to manually create or upload poster images - Shopify does the heavy lifting for you.

You can access it like this:

{{ video.preview_image | image_url: width: 1000 }}

This returns an optimized image URL of the video's first frame at your specified width. Combined with Shopify's image_url filter, you get full control over responsive image sizes without any extra work.

Building the responsive video component Jump to heading

Here's how to combine the <picture> element technique with Shopify's preview_image to create truly responsive video posters:

{%- liquid
# Assign poster images from preview_image
assign desktop_poster = section.settings.desktop_video.preview_image
assign mobile_poster = section.settings.mobile_video.preview_image

# Set fetch priority for above-the-fold sections
assign fetch_priority = 'auto'
if section.index <= 2
assign fetch_priority = 'high'
endif
-%}


<div class="video-wrapper">
{%- comment -%} Picture element acts as responsive poster - loads fast for LCP {%- endcomment -%}
<picture class="video-poster">
{%- if mobile_poster != blank -%}
<source
srcset="{{ mobile_poster | image_url: width: 352 }} 352w,
{{ mobile_poster | image_url: width: 832 }} 832w,
{{ mobile_poster | image_url: width: mobile_poster.width }} {{ mobile_poster.width }}w"

media="(max-width: 749px)"
sizes="100vw"
>

{%- endif -%}
{%- if desktop_poster != blank -%}
<source
srcset="{{ desktop_poster | image_url: width: 832 }} 832w,
{{ desktop_poster | image_url: width: 1200 }} 1200w,
{{ desktop_poster | image_url: width: 1920 }} 1920w,
{{ desktop_poster | image_url: width: desktop_poster.width }} {{ desktop_poster.width }}w"

media="(min-width: 750px)"
sizes="100vw"
>

{{ desktop_poster | image_url: width: 1920 | image_tag:
srcset: nil,
class: "video-poster-img",
fetchpriority: fetch_priority }}

{%- elsif mobile_poster != blank -%}
{{ mobile_poster | image_url: width: 832 | image_tag:
srcset: nil,
class: "video-poster-img",
fetchpriority: fetch_priority }}

{%- endif -%}
</picture>

{%- comment -%} Video element with responsive sources {%- endcomment -%}
<video
muted
autoplay
loop
playsinline
preload="metadata"
class="responsive-video"
>

{%- comment -%} Mobile video sources with media query {%- endcomment -%}
{%- if section.settings.mobile_video != blank -%}
{%- for source in section.settings.mobile_video.sources -%}
<source
src="{{ source.url }}"
type="{{ source.mime_type }}"
media="(max-width: 749px)"
>

{%- endfor -%}
{%- endif -%}

{%- comment -%} Desktop video sources with media query {%- endcomment -%}
{%- if section.settings.desktop_video != blank -%}
{%- for source in section.settings.desktop_video.sources -%}
<source
src="{{ source.url }}"
type="{{ source.mime_type }}"
media="(min-width: 750px)"
>

{%- endfor -%}
{%- endif -%}
Your browser does not support the video tag.
</video>
</div>

Here's an example of the CSS that makes the magic happen:

.video-wrapper {
position: relative;
width: 100%;
height: 100%;
}

.video-wrapper .video-poster {
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
}

.video-wrapper .video-poster-img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center center;
}

.video-wrapper .responsive-video {
position: relative;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center center;
z-index: 2;
}

Notice how the <video> element has no poster attribute. The <picture> element positioned absolutely behind it serves as our responsive poster, and each video's preview_image automatically provides the correct first frame for that orientation. The video naturally covers the picture when it starts playing.

Why this approach works so well Jump to heading

The benefit of using preview_image is that it's completely automatic:

  1. No extra uploads - The poster image is generated from your video's first frame
  2. Always matches - The poster perfectly represents what users will see when the video plays
  3. Fully optimized - Shopify's CDN serves the right, optimized image

Demo Page Jump to heading

Curious on how this implementation looks like in the wild and the impact it may have on perceived performance? Check out this demo page where you can see it in action.

Test Demo Page

Testing and measurement Jump to heading

To validate the effectiveness of this technique, follow this step-by-step testing process:

Step-by-step performance testing Jump to heading

1. Measure the baseline (before changes)

  • Open Chrome DevTools (F12)
  • Go to the Performance panel
  • Reload the page (F5)
  • Note the LCP time and CSS selector (should be something like video.hero-video)
Rendering filmstring of a test page with and without the picture + video implementation.

2. Apply the responsive poster technique

  • Implement the <picture> + <video> solution from the examples above
  • Make sure your poster images are optimized and properly sized

3. Measure the improvement (after changes)

  • Repeat step 1 with the same DevTools process
  • The LCP element should now be your poster image instead of the video
  • Compare the LCP times - you should see a significant improvement
Rendering filmstring of a test page with and without the picture + video implementation.

4. Test under realistic conditions
Avoid testing only on high-performance development machines. Simulate realistic user conditions:

  • In DevTools, go to Performance tab → NetworkSlow 4G or Fast 4G
  • In the same tab → CPU4x slowdown (simulates lower-end devices)
  • Run the same tests - the difference will be even more dramatic

Before the implementation:

Chrome DevTools screenshot of a mobile page. Network: Fast 4G. CPU: 4x slowdown. The LCP element is the video element and the LCP value is 7.77 seconds

After the implementation:

Chrome DevTools screenshot of a mobile page. Network: Fast 4G. CPU: 4x slowdown. The LCP element is the img element and the LCP value is 1.21 seconds

Visual loading experience comparison Jump to heading

The real impact of this technique becomes clear when you see it in action.

Scenario 1: No poster image
Users see a blank rectangle during video loading, a problem that becomes increasingly frustrating on slower connections.

Rendering filmstring of a test page with and without the picture + video implementation.

Scenario 2: Single poster attribute (mismatched)
The video loads with a poster, but it's stretched or pixelated because it doesn't match the viewport size.

Rendering filmstring of a test page with and without the right poster image.

The visual difference is striking. Even when comparing against a video with a basic poster attribute, our responsive approach delivers a noticeably more polished experience. Users see crisp, device-optimized visuals instead of stretched or pixelated placeholders, making the entire loading process feel more seamless and intentional.

The future of video performance: native lazy loading Jump to heading

While the responsive poster technique provides immediate benefits today, the web platform continues to evolve to better support video performance optimization. Currently, there's an active proposal in the WHATWG HTML specification to add native lazy loading support for video and audio elements through the loading attribute.

Proposed video lazy loading scdpecification Jump to heading

The proposal would extend the existing loading attribute (already supported on img and iframe elements) to video elements, with two possible values:

  • loading="eager" - Load the video immediately (current default behavior)
  • loading="lazy" - Defer loading until the video enters the viewport

When loading="lazy" is specified, the browser would delay loading video data, poster images, and autoplay playback until the element is needed. This approach could significantly reduce initial bandwidth consumption, allowing browsers to prioritize more critical resources during page load.

The responsive poster approach outlined in this article would complement native video lazy loading perfectly. Here's how they would work together:

<div class="video-wrapper">
<!-- Responsive poster with its own lazy loading -->
<picture class="video-poster">
<source srcset="..." media="(max-width: 749px)" sizes="100vw">
<source srcset="..." media="(min-width: 750px)" sizes="100vw">
<img src="..." alt="Video preview" loading="lazy">
</picture>

<!-- Video with proposed lazy loading -->
<video loading="lazy" muted autoplay loop playsinline preload="none">
<source src="video-mobile.mp4" type="video/mp4" media="(max-width: 749px)">
<source src="video-desktop.mp4" type="video/mp4" media="(min-width: 750px)">
</video>
</div>

This combination would provide several advantages:

  1. Bandwidth optimization - Videos below the fold wouldn't consume bandwidth until needed
  2. Graceful loading sequence - Lazy loaded poster images download faster than videos, ensuring users see appropriate visuals immediately when scrolling to the video.
  3. Resource prioritization - Critical above-the-fold resources load first, with below-the-fold videos and poster images deferred until actually needed.

Conclusion Jump to heading

Making responsive videos feel faster isn't just about technical tricks - it's about understanding user psychology. Users judge speed by what they see, not by what's happening in the background. By providing the right poster image for each device, you're giving users faster visual feedback that something is happening.

Read similar articles tagged...

Back to blog