<!-- MediaBanner - Full viewport decorative media -->
<div class="mediaBanner">
<img
class="mediaBanner__content"
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4"
alt=""
/>
</div>
<!-- MediaBanner with video -->
<div class="mediaBanner">
<p-lazy>
<video class="mediaBanner__content" inert controls autoplay playsinline muted>
<source
data-src="https://www.w3schools.com/html/mov_bbb.mp4"
type="video/mp4"
/>
Your browser does not support the video tag.
</video>
<template #placeholder="{ load, modifier }">
<div class="media">
<picture class="mediaBanner__mask media__mask" :class="modifier">
<img
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4"
alt=""
/>
</picture>
<button
class="mediaBanner__trigger media__trigger -circle"
@click="load"
:class="modifier"
>
<span class="srOnly">play</span>
<svg class="media__icon icon -circle" viewBox="0 0 96 96">
<use href="/main-icons-sprite.svg#play" />
</svg>
</button>
</div>
</template>
</p-lazy>
</div>
<!-- MediaBanner with video inside container -->
<section class="section">
<div class="container">
<h2 class="h3">
With Optional Header inside a container
</h2>
<div class="mediaBanner">
<p-lazy>
<video class="mediaBanner__content" inert controls autoplay playsinline muted>
<source
data-src="https://www.w3schools.com/html/mov_bbb.mp4"
type="video/mp4"
/>
Your browser does not support the video tag.
</video>
<template #placeholder="{ load, modifier }">
<div class="media">
<picture class="mediaBanner__mask media__mask" :class="modifier">
<img
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4"
alt=""
/>
</picture>
<button
class="mediaBanner__trigger media__trigger -circle"
@click="load"
:class="modifier"
>
<span class="srOnly">play</span>
<svg class="media__icon icon -circle" viewBox="0 0 96 96">
<use href="/main-icons-sprite.svg#play" />
</svg>
</button>
</div>
</template>
</p-lazy>
</div>
</div>
</section>
<!-- MediaBanner with overlay -->
<div class="mediaBanner -withOverlay">
<img
class="mediaBanner__content"
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4"
alt=""
/>
<div class="mediaBanner__overlay"></div>
</div>
<!-- MediaBanner with text content - Center alignment (overlay added automatically) -->
<div class="mediaBanner -withOverlay">
<img
class="mediaBanner__content"
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4"
alt=""
/>
<div class="mediaBanner__overlay"></div>
<div class="mediaBanner__content -white -center">
<div class="container">
<div class="mediaBanner__eyebrow">Featured Content</div>
<h2 class="mediaBanner__title h3">Center Aligned Headline</h2>
<div class="mediaBanner__actions">
<button class="button -navy">Call to Action</button>
</div>
</div>
</div>
</div>
<!-- MediaBanner with text content - Left alignment (overlay added automatically) -->
<div class="mediaBanner -withOverlay">
<img
class="mediaBanner__content"
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4"
alt=""
/>
<div class="mediaBanner__overlay"></div>
<div class="mediaBanner__content -white -left">
<div class="container">
<div class="mediaBanner__eyebrow">Featured Content</div>
<h2 class="mediaBanner__title h3">Left Aligned Headline</h2>
<div class="mediaBanner__actions">
<button class="button -navy">Call to Action</button>
</div>
</div>
</div>
</div>
<!-- MediaBanner with text content - Right alignment (overlay added automatically) -->
<div class="mediaBanner -withOverlay">
<img
class="mediaBanner__content"
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4"
alt=""
/>
<div class="mediaBanner__overlay"></div>
<div class="mediaBanner__content -white -right">
<div class="container">
<div class="mediaBanner__eyebrow">Featured Content</div>
<h2 class="mediaBanner__title h3">Right Aligned Headline</h2>
<div class="mediaBanner__actions">
<button class="button -navy">Call to Action</button>
</div>
</div>
</div>
</div>
.mediaBanner {
position: relative;
width: 100%;
overflow: hidden;
display: grid;
grid-template-areas: "media";
--media-banner-aspect-ratio: 65 / 24;
// When inside a container, add border radius
.container & {
border-radius: var(--root-border-radius);
}
// Target p-lazy wrapper div to be in grid area
> div:first-child {
grid-area: media;
position: relative;
width: 100%;
video {
width: 100%;
height: auto;
aspect-ratio: var(--media-banner-aspect-ratio);
display: block;
}
}
.media {
grid-area: media;
display: grid;
width: 100%;
// Override section's --accent-color: #fff to use default purple for icons
--accent-color: var(--color-purple);
}
&__content {
grid-area: media;
width: 100%;
height: auto;
aspect-ratio: var(--media-banner-aspect-ratio);
object-fit: cover;
display: block;
z-index: 0;
// Content overlay styling (when used for text content)
&.-white {
grid-area: media;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
padding: 2rem;
color: white;
z-index: 2;
background: none;
object-fit: initial;
// Ensure all child elements inherit white color
* {
color: inherit;
}
// Don't override container - let it work with Pronto grid system
.container {
// Only add minimal styles that don't break the grid
width: 100%;
row-gap: 0;
}
// Text alignment modifiers
&.-center {
align-items: center;
text-align: center;
}
&.-left {
align-items: flex-start;
text-align: left;
}
&.-right {
align-items: flex-end;
text-align: right;
}
}
}
&__eyebrow {
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.5rem;
opacity: 0.9;
}
&__title {
margin-bottom: 1.5rem;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
&__actions {
margin-top: 1rem;
}
&__mask {
.media & {
grid-area: 1 / 1;
}
grid-area: media;
position: relative;
z-index: 1;
background: white;
margin: 0;
transition: opacity 0.3s ease;
border-radius: var(--root-border-radius);
img {
width: 100%;
height: auto;
aspect-ratio: var(--media-banner-aspect-ratio);
object-fit: cover;
}
&.-loaded {
opacity: 0;
pointer-events: none;
}
}
&__trigger {
// When inside media class, use media's grid system (media__trigger handles positioning)
.media & {
grid-area: 1 / 1;
}
grid-area: media;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
appearance: none;
background: transparent;
border: 0;
cursor: pointer;
padding: 0;
}
&__overlay {
grid-area: media;
background-color: rgba(0, 0, 0, 0.3);
pointer-events: none;
z-index: 1;
}
// Modifier for different overlay styles
&.-withOverlay {
.mediaBanner__overlay {
opacity: 1;
}
}
}
<script setup>
import { computed, ref, nextTick, useTemplateRef } from 'vue'
/**
* PLazy is a vue component that lazy loads its content after it's been clicked.
*
* @slot default - the content to lazyload. It will remove inert attributes, change data-src
* attributes to src attributes, and call .load() on videos.
* @slot placeholder - the content to display before it's lazyloaded. The following properties
* are exposed to this slot:
* load {function} - function to call to load the content.
* loaded {boolean} - whether the content is loaded or not.
* modifier {string} - either '-loaded' or '' based on loaded.
*/
const loaded = ref(false)
const content = useTemplateRef('content')
const modifier = computed(() => loaded.value ? '-loaded' : '')
const load = async () => {
if (loaded.value) return
await nextTick()
const promises = []
/* Change data-src attributes to src attributes */
content.value.querySelectorAll('[data-src]')
.forEach(element => element.setAttribute('src', element.dataset.src))
/* remove inert attribute */
content.value.querySelectorAll('[inert]')
.forEach(element => element.inert = false)
/* load videos */
content.value.querySelectorAll('video')
.forEach(element => {
element.load()
promises.push(new Promise(resolve => {
element.addEventListener('loadeddata', resolve, { once: true })
}))
})
/* load iframes */
content.value.querySelectorAll('iframe')
.forEach(element => {
promises.push(new Promise(resolve => {
element.addEventListener('load', resolve, { once: true })
}))
})
/* wait for all videos and iframes to load, then update loaded.value */
Promise.all(promises).then(() => loaded.value = true)
}
</script>
<template>
<div ref="content">
<slot name="default"></slot>
</div>
<slot name="placeholder" v-bind="{ load, loaded, modifier }"></slot>
</template>
<style scoped>
div {
container-type: size;
}
</style>