diff --git a/cigaradvisor/blocks/video/video.css b/cigaradvisor/blocks/video/video.css new file mode 100644 index 00000000..c5f1d524 --- /dev/null +++ b/cigaradvisor/blocks/video/video.css @@ -0,0 +1,79 @@ +.video { + width: unset; + text-align: center; + max-width: 1020px; + margin: auto; +} + +.video.lazy-loading { + /* reserve an approximate space to avoid extensive layout shifts */ + aspect-ratio: 16 / 9; +} + +.video > div { + display: flex; + justify-content: center; +} + +.video video { + max-width: 100%; +} + +.video video[data-loading] { + /* reserve an approximate space to avoid extensive layout shifts */ + width: 100%; + aspect-ratio: 16 / 9; +} + +.video .video-placeholder { + width: 100%; + aspect-ratio: 16 / 9; + position: relative; +} + +.video .video-placeholder > * { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + inset: 0; +} + +.video .video-placeholder picture img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.video .video-placeholder-play button { + box-sizing: border-box; + position: relative; + display: block; + transform: scale(3); + width: 22px; + height: 22px; + border: 2px solid; + border-radius: 20px; + padding: 0; +} + +.video .video-placeholder-play button::before { + content: ""; + display: block; + box-sizing: border-box; + position: absolute; + width: 0; + height: 10px; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: 6px solid; + top: 4px; + left: 7px; +} + +@media print, screen and (max-width: 960px) { + .video { + max-width: 780px; + margin: auto 20px; + } +} diff --git a/cigaradvisor/blocks/video/video.js b/cigaradvisor/blocks/video/video.js new file mode 100644 index 00000000..19712695 --- /dev/null +++ b/cigaradvisor/blocks/video/video.js @@ -0,0 +1,94 @@ +/* +* Video Block +* Show a video referenced by a link +* https://www.hlx.live/developer/block-collection/video +*/ + +function embedYoutube(url, autoplay) { + const usp = new URLSearchParams(url.search); + const suffix = autoplay ? '&muted=1&autoplay=1' : ''; + let vid = usp.get('v') ? encodeURIComponent(usp.get('v')) : ''; + const embed = url.pathname; + if (url.origin.includes('youtu.be')) { + [, vid] = url.pathname.split('/'); + } + return `
+ +
`; +} + +function embedVimeo(url, autoplay) { + const [, video] = url.pathname.split('/'); + const suffix = autoplay ? '?muted=1&autoplay=1' : ''; + return `
+ +
`; +} + +function getVideoElement(source, autoplay) { + const video = document.createElement('video'); + video.setAttribute('controls', ''); + video.dataset.loading = 'true'; + video.addEventListener('loadedmetadata', () => delete video.dataset.loading); + if (autoplay) video.setAttribute('autoplay', ''); + + const sourceEl = document.createElement('source'); + sourceEl.setAttribute('src', source); + sourceEl.setAttribute('type', `video/${source.split('.').pop()}`); + video.append(sourceEl); + + return video; +} + +const loadVideoEmbed = (block, link, autoplay) => { + if (block.dataset.embedIsLoaded) { + return; + } + const url = new URL(link); + + const isYoutube = link.includes('youtube') || link.includes('youtu.be'); + const isVimeo = link.includes('vimeo'); + const isMp4 = link.includes('.mp4'); + + if (isYoutube) { + block.innerHTML = embedYoutube(url, autoplay); + } else if (isVimeo) { + block.innerHTML = embedVimeo(url, autoplay); + } else if (isMp4) { + block.textContent = ''; + block.append(getVideoElement(link, autoplay)); + } + + block.dataset.embedIsLoaded = true; +}; + +export default async function decorate(block) { + const placeholder = block.querySelector('picture'); + const link = block.querySelector('a').href; + block.textContent = ''; + + if (placeholder) { + const wrapper = document.createElement('div'); + wrapper.className = 'video-placeholder'; + wrapper.innerHTML = '
'; + wrapper.prepend(placeholder); + wrapper.addEventListener('click', () => { + loadVideoEmbed(block, link, true); + }); + block.append(wrapper); + } else { + block.classList.add('lazy-loading'); + const observer = new IntersectionObserver((entries) => { + if (entries.some((e) => e.isIntersecting)) { + observer.disconnect(); + loadVideoEmbed(block, link, false); + block.classList.remove('lazy-loading'); + } + }); + observer.observe(block); + } +}