FIX: Improvements to animated image pausing (#12839)

This commit is contained in:
Penar Musaraj
2021-04-28 10:48:00 -04:00
committed by GitHub
parent aa9a8d1041
commit 548c044809
4 changed files with 143 additions and 23 deletions
@@ -1,35 +1,53 @@
import { iconHTML } from "discourse-common/lib/icon-library";
import { prefersReducedMotion } from "discourse/lib/utilities";
import { withPluginApi } from "discourse/lib/plugin-api";
let _gifClickHandlers = {};
function _pauseAnimation(img, opts = {}) {
let canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext("2d").drawImage(img, 0, 0, img.width, img.height);
canvas.setAttribute("aria-hidden", "true");
canvas.setAttribute("role", "presentation");
if (opts.manualPause) {
img.classList.add("manually-paused");
}
img.parentNode.classList.add("paused-animated-image");
img.parentNode.insertBefore(canvas, img);
}
function _resumeAnimation(img) {
img.previousSibling.remove();
img.parentNode.classList.remove("paused-animated-image", "manually-paused");
}
function animatedImgs() {
return document.querySelectorAll("img.animated:not(.manually-paused)");
}
export default {
name: "animated-images-pause-on-click",
initialize() {
withPluginApi("0.8.7", (api) => {
function _cleanUp() {
Object.values(_gifClickHandlers || {}).forEach((handler) =>
handler.removeEventListener("click", _handleClick)
);
Object.values(_gifClickHandlers || {}).forEach((handler) => {
handler.removeEventListener("click", _handleEvent);
handler.removeEventListener("load", _handleEvent);
});
_gifClickHandlers = {};
}
function _handleClick(event) {
function _handleEvent(event) {
const img = event.target;
if (img && !img.previousSibling) {
let canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext("2d").drawImage(img, 0, 0, img.width, img.height);
canvas.setAttribute("aria-hidden", "true");
canvas.setAttribute("role", "presentation");
img.parentNode.classList.add("paused-animated-image");
img.parentNode.insertBefore(canvas, img);
_pauseAnimation(img, { manualPause: true });
} else {
img.previousSibling.remove();
img.parentNode.classList.remove("paused-animated-image");
_resumeAnimation(img);
}
}
@@ -41,17 +59,37 @@ export default {
let images = post.querySelectorAll("img.animated");
images.forEach((img) => {
// skip for edge case of multiple animated images in same block
if (img.parentNode.querySelectorAll("img").length > 1) {
return;
}
if (_gifClickHandlers[img.src]) {
_gifClickHandlers[img.src].removeEventListener(
"click",
_handleClick
_handleEvent
);
_gifClickHandlers[img.src].removeEventListener(
"load",
_handleEvent
);
delete _gifClickHandlers[img.src];
}
_gifClickHandlers[img.src] = img;
img.addEventListener("click", _handleClick, false);
img.addEventListener("click", _handleEvent, false);
if (prefersReducedMotion()) {
img.addEventListener("load", _handleEvent, false);
}
img.parentNode.classList.add("pausable-animated-image");
const overlay = document.createElement("div");
overlay.classList.add("animated-image-overlay");
overlay.setAttribute("aria-hidden", "true");
overlay.setAttribute("role", "presentation");
overlay.innerHTML = `${iconHTML("pause")}${iconHTML("play")}`;
img.parentNode.appendChild(overlay);
});
}
@@ -61,6 +99,39 @@ export default {
});
api.cleanupStream(_cleanUp);
// paused on load when prefers-reduced-motion is active, no need for blur/focus events
if (!prefersReducedMotion()) {
window.addEventListener("blur", this.blurEvent);
window.addEventListener("focus", this.focusEvent);
}
});
},
blurEvent() {
animatedImgs().forEach((img) => {
if (
img.parentNode.querySelectorAll("img").length === 1 &&
!img.previousSibling
) {
_pauseAnimation(img);
}
});
},
focusEvent() {
animatedImgs().forEach((img) => {
if (
img.parentNode.querySelectorAll("img").length === 1 &&
img.previousSibling
) {
_resumeAnimation(img);
}
});
},
teardown() {
window.removeEventListener("blur", this.blurEvent);
window.removeEventListener("focus", this.focusEvent);
},
};
@@ -433,6 +433,10 @@ export function isiOSPWA() {
return window.matchMedia("(display-mode: standalone)").matches && caps.isIOS;
}
export function prefersReducedMotion() {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
export function isAppWebview() {
return window.ReactNativeWebView !== undefined;
}