diff --git a/libs/blocks/aside/aside.js b/libs/blocks/aside/aside.js index a5c7cf8ded..7c49d2bfa2 100644 --- a/libs/blocks/aside/aside.js +++ b/libs/blocks/aside/aside.js @@ -14,7 +14,7 @@ * Aside - v5.1 */ -import { decorateBlockBg, decorateBlockText } from '../../utils/decorate.js'; +import { decorateBlockBg, decorateBlockText, applyHoverPlay } from '../../utils/decorate.js'; import { createTag } from '../../utils/utils.js'; // standard/default aside uses same text sizes as the split @@ -54,7 +54,11 @@ function decorateLayout(el) { const text = foreground.querySelector('h1, h2, h3, h4, h5, h6, p')?.closest('div'); text?.classList.add('text'); const media = foreground.querySelector(':scope > div:not([class])'); - if (!el.classList.contains('notification')) media?.classList.add('image'); + if (media && !el.classList.contains('notification')) { + media.classList.add('image'); + const video = media.querySelector('video'); + if (video) applyHoverPlay(video); + } const picture = text?.querySelector('picture'); const iconArea = picture ? (picture.closest('p') || createTag('p', null, picture)) : null; iconArea?.classList.add('icon-area'); diff --git a/libs/blocks/figure/figure.js b/libs/blocks/figure/figure.js index bf2be0c585..40db745866 100644 --- a/libs/blocks/figure/figure.js +++ b/libs/blocks/figure/figure.js @@ -21,6 +21,7 @@ export function buildFigure(blockEl) { } const video = clone.querySelector('video'); if (video) { + import('../../utils/decorate.js').then(({ applyHoverPlay }) => applyHoverPlay(video)); figEl.prepend(video); } const caption = clone.querySelector('em'); diff --git a/libs/blocks/marquee/marquee.js b/libs/blocks/marquee/marquee.js index ee1518fea1..7e844a6f6e 100644 --- a/libs/blocks/marquee/marquee.js +++ b/libs/blocks/marquee/marquee.js @@ -13,7 +13,7 @@ /* * Marquee - v6.0 */ -import { decorateButtons, getBlockSize } from '../../utils/decorate.js'; +import { decorateButtons, getBlockSize, applyHoverPlay } from '../../utils/decorate.js'; import { decorateBlockAnalytics, decorateLinkAnalytics } from '../../martech/attributes.js'; import { createTag } from '../../utils/utils.js'; @@ -135,7 +135,8 @@ export default function init(el) { if (media) { media.classList.add('media'); - + const video = media.querySelector('video'); + if (video) applyHoverPlay(video); if (media.querySelector('a[href$=".mp4"]')) { decorateVideo(media); } else { diff --git a/libs/blocks/media/media.js b/libs/blocks/media/media.js index 3244944cbd..eb012f125d 100644 --- a/libs/blocks/media/media.js +++ b/libs/blocks/media/media.js @@ -14,7 +14,7 @@ * media - consonant v5.1 */ -import { decorateBlockBg, decorateBlockText, getBlockSize, decorateTextOverrides } from '../../utils/decorate.js'; +import { decorateBlockBg, decorateBlockText, getBlockSize, decorateTextOverrides, applyHoverPlay } from '../../utils/decorate.js'; import { decorateBlockAnalytics } from '../../martech/attributes.js'; import { createTag } from '../../utils/utils.js'; @@ -49,6 +49,8 @@ export default function init(el) { if (image) image.classList.add('image'); const img = image.querySelector(':scope img'); if (header && img?.alt === '') img.alt = header.textContent; + const imageVideo = image.querySelector('video'); + if (imageVideo) applyHoverPlay(imageVideo); // lists if (row.querySelector('ul')) { diff --git a/libs/blocks/video/video.js b/libs/blocks/video/video.js index 9366d11e76..5e69d869d8 100644 --- a/libs/blocks/video/video.js +++ b/libs/blocks/video/video.js @@ -1,13 +1,29 @@ -export default function init(a) { - const { pathname, hash } = a; +import { applyHoverPlay } from '../../utils/decorate.js'; - const isAutoplay = !!(hash?.includes('autoplay')); - const isNotLooped = !!(hash?.includes('autoplay1')); +function getAttrs(hash) { + const isAutoplay = hash?.includes('autoplay'); + const isAutoplayOnce = hash?.includes('autoplay1'); + const playOnHover = hash.includes('hoverplay'); + if (isAutoplay && !isAutoplayOnce) { + return 'playsinline autoplay loop muted'; + } + if (playOnHover && isAutoplayOnce) { + return 'playsinline autoplay muted data-hoverplay'; + } + if (isAutoplayOnce) { + return 'playsinline autoplay muted'; + } + return 'playsinline controls'; +} - const attrs = isAutoplay ? `playsinline autoplay ${isNotLooped ? '' : 'loop'} muted` : 'playsinline controls'; +export default function init(a) { + const { pathname, hash } = a; + const attrs = getAttrs(hash); const video = ``; a.insertAdjacentHTML('afterend', video); + const videoElem = document.body.querySelector(`source[src=".${pathname}"]`)?.parentElement; + applyHoverPlay(videoElem); a.remove(); } diff --git a/libs/utils/decorate.js b/libs/utils/decorate.js index 7195bfee9c..41e44c89b9 100644 --- a/libs/utils/decorate.js +++ b/libs/utils/decorate.js @@ -89,10 +89,20 @@ function applyTextOverrides(el, override) { } export function decorateTextOverrides(el, options = ['-heading', '-body', '-detail']) { - const overrides = [...el.classList].filter((elClass) => options.findIndex((ovClass) => elClass.endsWith(ovClass)) >= 0); + const overrides = [...el.classList].filter( + (elClass) => options.findIndex((ovClass) => elClass.endsWith(ovClass)) >= 0, + ); if (!overrides.length) return; overrides.forEach((override) => { applyTextOverrides(el, override); el.classList.remove(override); }); } + +export function applyHoverPlay(video) { + if (video.hasAttribute('data-hoverplay') && !video.hasAttribute('data-mouseevent')) { + video.addEventListener('mouseenter', () => { video.play(); }); + video.addEventListener('mouseleave', () => { video.pause(); }); + video.setAttribute('data-mouseevent', true); + } +} diff --git a/test/blocks/video/mocks/body.html b/test/blocks/video/mocks/body.html index ce82f9f66a..9c18dfc91f 100644 --- a/test/blocks/video/mocks/body.html +++ b/test/blocks/video/mocks/body.html @@ -16,4 +16,20 @@ https://main--blog--adobecom.hlx.page/media_17927691d22fe4e1bd058e94762a224fdc57ebb7b.mp4#autoplay1 +
+ + https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb.mp4#autoplay1#_hoverplay + +
+
+ + https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb.mp4#autoplay#_hoverplay + +
+
+ + https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb.mp4#_hoverplay + +
+ diff --git a/test/blocks/video/video.test.js b/test/blocks/video/video.test.js index 1387e364d5..1fe3031293 100644 --- a/test/blocks/video/video.test.js +++ b/test/blocks/video/video.test.js @@ -41,4 +41,42 @@ describe('video uploaded using franklin bot', () => { expect(block.firstElementChild.hasAttribute('loop')).to.be.false; }); + + it('decorates video with autoplay, no loop and hover play', async () => { + const block = document.querySelector('.video.no-loop.hoverplay'); + const a = block.querySelector('a'); + const { href } = a; + a.textContent = href; + block.append(a); + + init(a); + + expect(block.firstElementChild.hasAttribute('loop')).to.be.false; + expect(block.firstElementChild.hasAttribute('data-hoverplay')).to.be.true; + }); + + it('no hoverplay attribute added when with autoplay on loop', async () => { + const block = document.querySelector('.video.autoplay.playonhover'); + const a = block.querySelector('a'); + const { href } = a; + a.textContent = href; + block.append(a); + + init(a); + + expect(block.firstElementChild.hasAttribute('loop')).to.be.true; + expect(block.firstElementChild.hasAttribute('data-hoverplay')).to.be.false; + }); + + it('no hoverplay attribute added when only hoverplay is added to url', async () => { + const block = document.querySelector('.video.hoveronly'); + const a = block.querySelector('a'); + const { href } = a; + a.textContent = href; + block.append(a); + + init(a); + + expect(block.firstElementChild.hasAttribute('data-hoverplay')).to.be.false; + }); });