Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MWPW-147468 - Delayed Activation of Blade Animations on Ps Product Page #2487

Merged
merged 8 commits into from
Jul 8, 2024
3 changes: 2 additions & 1 deletion libs/blocks/video/video.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createIntersectionObserver, getConfig } from '../../utils/utils.js';
import { applyHoverPlay, getVideoAttrs } from '../../utils/decorate.js';
import { applyHoverPlay, getVideoAttrs, applyInViewPortPlay } from '../../utils/decorate.js';

const ROOT_MARGIN = 1000;

Expand All @@ -23,6 +23,7 @@ const loadVideo = (a) => {
a.insertAdjacentHTML('afterend', video);
const videoElem = document.body.querySelector(`source[src="${videoPath}"]`)?.parentElement;
applyHoverPlay(videoElem);
applyInViewPortPlay(videoElem);
a.remove();
};

Expand Down
38 changes: 36 additions & 2 deletions libs/utils/decorate.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,14 @@ export function getVideoAttrs(hash, dataset) {
const isAutoplay = hash?.includes('autoplay');
const isAutoplayOnce = hash?.includes('autoplay1');
const playOnHover = hash?.includes('hoverplay');
const playInViewport = hash?.includes('viewportplay');
const poster = dataset?.videoPoster ? `poster='${dataset.videoPoster}'` : '';
const globalAttrs = `playsinline ${poster}`;
const autoPlayAttrs = 'autoplay muted';
const playInViewportAttrs = playInViewport ? 'data-play-viewport' : '';

if (isAutoplay && !isAutoplayOnce) {
return `${globalAttrs} ${autoPlayAttrs} loop`;
return `${globalAttrs} ${autoPlayAttrs} loop ${playInViewportAttrs}`;
}
if (playOnHover && isAutoplayOnce) {
return `${globalAttrs} ${autoPlayAttrs} data-hoverplay`;
Expand All @@ -188,7 +190,7 @@ export function getVideoAttrs(hash, dataset) {
return `${globalAttrs} muted data-hoverplay`;
}
if (isAutoplayOnce) {
return `${globalAttrs} ${autoPlayAttrs}`;
return `${globalAttrs} ${autoPlayAttrs} ${playInViewportAttrs}`;
}
return `${globalAttrs} controls`;
}
Expand Down Expand Up @@ -225,3 +227,35 @@ export function handleObjectFit(bgRow) {
setObjectFitAndPos(text, pic, r, ['fill', 'contain', 'cover', 'none', 'scale-down']);
});
}

export function getVideoIntersectionObserver() {
if (!window?.videoIntersectionObs) {
window.videoIntersectionObs = new window.IntersectionObserver((entries) => {
entries.forEach((entry) => {
const { intersectionRatio, target: video } = entry;
const isHaveLoopAttr = video.getAttributeNames().includes('loop');
const { playedOnce = false } = video.dataset;
const isPlaying = video.currentTime > 0 && !video.paused && !video.ended
&& video.readyState > video.HAVE_CURRENT_DATA;

if (intersectionRatio <= 0.8) {
video.pause();
} else if ((isHaveLoopAttr || !playedOnce) && !isPlaying) {
video.play();
}
});
}, { threshold: [0.8] });
}
return window.videoIntersectionObs;
}

export function applyInViewPortPlay(video) {
if (!video) return;
if (video.hasAttribute('data-play-viewport')) {
const observer = getVideoIntersectionObserver();
video.addEventListener('ended', () => {
video.dataset.playedOnce = true;
});
observer.observe(video);
}
}
49 changes: 49 additions & 0 deletions test/blocks/video/mocks/body.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
<style>
video {
width: 100%;
}
</style>
<main>
<div class="video no-lazy">
<a href="https://main--blog--adobecom.hlx.page/media_17927691d22fe4e1bd058e94762a224fdc57ebb7b.mp4">
Expand Down Expand Up @@ -37,4 +42,48 @@
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb.mp4#_hoverplay
</a>
</div>

<div class="video autoplay viewportplay ">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb1.mp4#autoplay#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb1.mp4#autoplay#viewportplay
</a>
</div>

<div class="video autoplay viewportplay scrolled-80">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb8.mp4#autoplay#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb8.mp4#autoplay#viewportplay
</a>
</div>
<div class="video autoplay1 viewportplay">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb2.mp4#autoplay1#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb2.mp4#autoplay1#viewportplay
</a>
</div>

<div class="video autoplay1 viewportplay ended">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb8.mp4#autoplay1#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb8.mp4#autoplay1#viewportplay
</a>
</div>

<div class="video no-autoplay no-viewportplay">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb3.mp4#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb3.mp4#viewportplay
</a>
</div>
<div class="video no-autoplay1 no-viewportplay">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb4.mp4#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb4.mp4#viewportplay
</a>
</div>
<div class="video hoverplay no-viewportplay">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb5.mp4#viewportplay#_hoverplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb5.mp4#viewportplay#_hoverplay
</a>
</div>
<div class="video autoplay1 hoverplay no-viewportplay">
<a href="https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb6.mp4#autoplay1#_hoverplay#viewportplay">
https://main--milo--adobecom.hlx.page/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb6.mp4#autoplay1#_hoverplay#viewportplay
</a>
</div>
</main>
166 changes: 159 additions & 7 deletions test/blocks/video/video.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { readFile } from '@web/test-runner-commands';
import { expect } from '@esm-bundle/chai';
import { expect, assert } from '@esm-bundle/chai';

import sinon from 'sinon';

import { waitForElement } from '../../helpers/waitfor.js';
import { setConfig } from '../../../libs/utils/utils.js';

Expand Down Expand Up @@ -71,25 +74,174 @@ describe('video uploaded using franklin bot', () => {
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;
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = await waitForElement('.video.autoplay.playonhover video');
const video = block.querySelector('video');
expect(video.hasAttribute('loop')).to.be.true;
expect(video.hasAttribute('data-hoverplay')).to.be.false;
});

it('decorate video with hoverplay 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;
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = await waitForElement('.video.hoveronly video');
const video = block.querySelector('video');
expect(video.hasAttribute('data-hoverplay')).to.be.true;
});

it('decorate video with viewportplay only with autoplay', async () => {
const block = document.querySelector('.video.autoplay.viewportplay');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = block.querySelector('video');
expect(video.hasAttribute('data-play-viewport')).to.be.true;
});

it('play video when element reached 80% viewport', async () => {
const block = document.querySelector('.video.autoplay.viewportplay.scrolled-80');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);
const nextFrame = () => new Promise((resolve) => {
requestAnimationFrame(resolve);
});

init(a);
const video = block.querySelector('video');
const source = video.querySelector('source');
source.setAttribute('src', 'https://www.adobe.com/creativecloud/media_1167374e3354ef57f126fa78a55cbc1708ac4babd.mp4');
source.setAttribute('type', 'video/mp4');

const playSpy = sinon.spy(video, 'play');
const pauseSpy = sinon.spy(video, 'pause');

video.scrollIntoView();
await nextFrame();
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
assert.isTrue(playSpy.calledOnce);

document.body.scrollIntoView();
await nextFrame();
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
assert.isTrue(pauseSpy.calledOnce);

expect(video.hasAttribute('data-play-viewport')).to.be.true;
});

it('Don\'t play the video once it end when autoplay1 enabled', async () => {
const block = document.querySelector('.video.autoplay1.viewportplay.ended');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);
const nextFrame = () => new Promise((resolve) => {
requestAnimationFrame(resolve);
});

init(a);
const video = block.querySelector('video');
const source = video.querySelector('source');
source.setAttribute('src', 'https://www.adobe.com/creativecloud/media_1167374e3354ef57f126fa78a55cbc1708ac4babd.mp4');
source.setAttribute('type', 'video/mp4');

const playSpy = sinon.spy(video, 'play');
const pauseSpy = sinon.spy(video, 'pause');
const endedSpy = sinon.spy();
video.addEventListener('ended', endedSpy);

video.scrollIntoView();
await nextFrame();
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
assert.isTrue(playSpy.calledOnce);

document.body.scrollIntoView();
await nextFrame();
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
assert.isTrue(pauseSpy.calledOnce);

video.dispatchEvent(new Event('ended'));
await nextFrame();
await new Promise((resolve) => {
setTimeout(resolve, 100);
});

video.scrollIntoView();
await nextFrame();
await new Promise((resolve) => {
setTimeout(resolve, 100);
});

expect(playSpy.callCount).to.equal(1);
expect(video.hasAttribute('data-play-viewport')).to.be.true;
});

it('decorate video with viewportplay only with autoplay1', async () => {
const block = document.querySelector('.video.autoplay1.viewportplay');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = block.querySelector('video');
expect(video.hasAttribute('data-play-viewport')).to.be.true;
});

it('decorate video with no viewportplay with autoplay1 hoverplay', async () => {
const block = document.querySelector('.video.autoplay1.hoverplay.no-viewportplay');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = block.querySelector('video');
expect(video.hasAttribute('data-play-viewport')).to.be.false;
});

it('decorate video with no viewportplay with hoverplay', async () => {
const block = document.querySelector('.video.hoverplay.no-viewportplay');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = block.querySelector('video');
expect(video.hasAttribute('data-play-viewport')).to.be.false;
});

it('decorate video with no viewportplay no autoplay', async () => {
const block = document.querySelector('.video.no-autoplay.no-viewportplay');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = block.querySelector('video');
expect(video.hasAttribute('data-play-viewport')).to.be.false;
});

it('decorate video with no viewportplay no autoplay1', async () => {
const block = document.querySelector('.video.no-autoplay1.no-viewportplay');
const a = block.querySelector('a');
a.textContent = 'no-lazy';
block.append(a);

init(a);
const video = block.querySelector('video');
expect(video.hasAttribute('data-play-viewport')).to.be.false;
});
});
Loading