docbrown/app/javascript/utilities/animatedImageUtils.jsx
Suzanne Aitchison 1b98b03bde
Allow users to pause gifs in a post (#16314)
* working POC

* refactors and styling

* add some comments

* css tweaks to keep sizes in step

* add cypress test

* comment added to css
2022-02-02 13:45:55 +00:00

91 lines
3 KiB
JavaScript

import { h, render } from 'preact';
import Freezeframe from 'freezeframe';
import { Icon, ButtonNew as Button } from '@crayons';
import Play from '@images/play.svg';
import Pause from '@images/pause.svg';
const toggleGifButtonPressedState = (button) => {
const currentlyPressed = button.getAttribute('aria-pressed') === 'true';
button.setAttribute('aria-pressed', !currentlyPressed);
};
/**
* Helper function that will initialize play/pause functionality for any image with the attribute data-animated="true"
*/
export const initializePausableAnimatedImages = (animatedImages = []) => {
if (animatedImages.length > 0) {
const freezeframes = [];
// Remove the surrounding links for the image, so it can be clicked to play/pause
for (const image of animatedImages) {
image.closest('a').outerHTML = image.outerHTML;
freezeframes.push(
new Freezeframe({
selector: `img[src="${image.getAttribute('src')}"]`,
responsive: false,
trigger: 'click',
}),
);
}
const handleFreezeframeReadyState = (_mutationList, observer) => {
// Check if freezeframe has finished initializing
const initializedFrames = document.querySelectorAll(
'.ff-container.ff-ready',
);
if (initializedFrames.length < freezeframes.length) {
// Not ready yet, do nothing
return;
}
// Freezeframes are ready, and we can disconnect our observer
observer.disconnect();
// Freezeframes are "paused" by default. If a user's settings allow, we immediately restart the animation.
const okWithMotion = window.matchMedia(
'(prefers-reduced-motion: no-preference)',
).matches;
freezeframes.forEach((ff) => {
if (okWithMotion) {
ff.start();
}
// Freezeframe doesn't allow gifs to be stopped by keyboard press, so we add a button to handle it
const ffWrapper = ff.items[0]['$container'];
// We want to update the button's state when the gif is paused by image/canvas click
ffWrapper.addEventListener('click', ({ currentTarget }) =>
toggleGifButtonPressedState(
currentTarget.querySelector('.gif-button'),
),
);
render(
<Button
aria-label="Pause animation playback"
aria-pressed={!okWithMotion}
className="gif-button fs-s gap-2"
onClick={() => ff.toggle()}
>
<Icon src={Play} className="gif-play" />
<Icon src={Pause} className="gif-pause" />
GIF
</Button>,
ffWrapper,
);
});
};
// Freezeframe initializes asynchronously, but unfortunately doesn't offer a callback for when it's "ready".
// This mutation observer allows us to complete some final tasks when it's complete.
const readyWatcher = new MutationObserver(handleFreezeframeReadyState);
readyWatcher.observe(document.querySelector('main'), {
subtree: true,
attributes: true,
attributeFilter: ['class'],
});
}
};