<div class="media">
<p-wistia>
<template #placeholder="{ playPause, modifier }">
<picture class="media__mask" :class="modifier">
<source srcset="https://unsplash.it/300/150?random&gravity=center" media="(min-width: 768px)">
<source srcset="https://unsplash.it/267/150?random&gravity=center" media="(min-width: 480px)">
<img class="lazyload" srcset="https://unsplash.it/267/150?random&gravity=center" alt="" width="267" height="150">
</picture>
<button class="media__trigger" :class="modifier" @click="playPause">
<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>
</template>
<iframe src="https://fast.wistia.net/embed/iframe/30q7n48g4f?autoplay=true" frameborder="0" allowfullscreen tabindex="-1"></iframe>
</p-wistia>
</div>
<h5>media__trigger -keepVisible</h5>
<div class="media">
<p-wistia>
<template #placeholder="{ playPause, modifier }">
<picture class="media__mask" :class="modifier">
<source srcset="https://unsplash.it/300/150?random&gravity=center" media="(min-width: 768px)">
<source srcset="https://unsplash.it/267/150?random&gravity=center" media="(min-width: 480px)">
<img class="lazyload" srcset="https://unsplash.it/267/150?random&gravity=center" alt="" width="267" height="150">
</picture>
<button class="media__trigger -keepVisible" :class="modifier" @click="playPause">
<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>
</template>
<iframe src="https://fast.wistia.net/embed/iframe/30q7n48g4f?autoplay=true" frameborder="0" allowfullscreen tabindex="-1"></iframe>
</p-wistia>
</div>
.media {
$b: &;
background-color: rgb(from currentColor r g b / .25);
display: inline grid;
margin-bottom: 2rem;
width: 267px;
height: 150px;
aspect-ratio: 267 / 150;
overflow: hidden;
@media (min-width: 768px) {
width: 300px;
aspect-ratio: 300 / 150;
}
> * {
grid-area: 1 / 1;
position: relative;
}
/* Ensure wrapper divs from components fill the grid cell */
> div:not(.media__mask):not(.media__trigger) {
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
}
/* Ensure iframes and videos don't expand their containers */
iframe,
video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
&__mask {
margin: 0;
display: inline-block;
transition: opacity var(--root-ease-out-moderate);
line-height: 0;
vertical-align: top;
img {
display: block;
width: 100%;
}
&.-loaded {
opacity: 0;
pointer-events: none;
}
}
&__maskImg {
width: 100%;
height: 100%;
object-fit: cover;
}
&__trigger {
appearance: none;
background: #0000;
border: 0;
border-radius: max(50cqmin, 6rem);
padding: 0;
align-self: center;
justify-self: center;
color: var(--accent-color-200);
container-type: size;
position: relative;
transition: color var(-root-ease-out-fast),
opacity var(--root-ease-out-moderate);
width: min(50cqmin, 6rem);
height: min(50cqmin, 6rem);
&:hover {
color: var(--accent-color-300);
}
&.-loaded {
align-self: start;
height: min(25cqmin, 3rem);
justify-self: end;
margin: .5rem;
opacity: 0;
pointer-events: none;
transition: opacity var(--root-ease-out-fast);
width: min(25cqmin, 3rem);
&.-keepVisible {
opacity: .25;
pointer-events: auto;
}
&.-keepVisible:hover,
&:focus-visible {
opacity: 1;
}
}
}
&__icon {
--icon-stroke-color: var(--accent-color-200);
filter: drop-shadow(var(--root-box-shadow-med));
width: auto;
height: auto;
&.-circle {
padding: min(6cqmin, .75rem);
transition: padding var(--root-ease-out-fast);
&:hover {
padding: min(4cqmin, .5rem);
}
}
}
/**
* hacks to target and better style iframes from youtube/vimeo embeds from CKEditor.
*/
&:has(> iframe):not(:has(p-wistia)):not(:has(p-lazy)):not(:has(p-you-tube-playlist)) {
display: grid;
/* these divs are using a padding-bottom hack for the aspect ratio. */
div[style]:has(> iframe) {
position: relative;
> iframe {
position: absolute;
inset: 0;
}
}
}
/* Ensure p-lazy wrapper div matches image size */
&:has(p-lazy) {
> div:first-child {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
> div:first-child iframe,
> div:first-child video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
}
}
/* Ensure p-you-tube-playlist wrapper div contains the iframe */
&:has(p-you-tube-playlist) {
display: block;
height: 150px;
width: 267px;
@media (min-width: 768px) {
width: 300px;
}
}
.p-youtube-playlist-wrapper {
width: 100% !important;
height: 150px !important;
min-height: 150px !important;
position: relative !important;
overflow: visible !important;
}
.p-youtube-playlist-wrapper iframe {
position: relative !important;
width: 100% !important;
height: 150px !important;
min-height: 150px !important;
display: block !important;
top: auto !important;
left: auto !important;
max-width: none !important;
max-height: none !important;
object-fit: none !important;
}
}
<script setup>
import { computed, onMounted, ref, useTemplateRef } from 'vue'
const mediaEl = useTemplateRef('video')
const mounted = ref(false)
const loaded = ref(false)
const modifier = computed(() => loaded.value ? '-loaded' : '')
onMounted(() => mounted.value = true)
const playPause = () => {
loaded.value = true
const iframe = mediaEl.value.querySelector('iframe')
if (iframe && iframe.getAttribute('tabindex') == -1) {
iframe.removeAttribute('tabindex')
}
// Try to use Wistia API if available
if (window.Wistia && iframe) {
const wistiaEmbed = window.Wistia.api(iframe)
if (wistiaEmbed) {
wistiaEmbed.play()
}
}
}
</script>
<template>
<div ref="video"><slot name="default" /></div>
<slot name="placeholder" v-bind="{ playPause, loaded, modifier }">
<button @click="playPause">play</button>
</slot>
</template>
<style scoped>
div {
container-type: size;
}
</style>