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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 143 additions and 23 deletions

View File

@ -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);
},
};

View File

@ -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;
}

View File

@ -1240,15 +1240,59 @@ a.mention-group {
}
}
.paused-animated-image {
.pausable-animated-image {
position: relative;
display: block;
> canvas {
display: inline-block;
> canvas,
> .animated-image-overlay {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
> canvas {
background-color: var(--primary-very-low);
}
> .animated-image-overlay {
pointer-events: none;
text-align: right;
display: flex;
justify-content: flex-end;
align-items: flex-end;
> .d-icon {
cursor: pointer;
padding: 0.5em;
margin: 0.5em;
background-color: rgba(var(--always-black-rgb), 0.25);
color: var(--secondary-or-primary);
cursor: pointer;
display: none;
}
}
img.animated {
cursor: pointer;
}
html.no-touch
&:not(.paused-animated-image)
.animated:hover
+ .animated-image-overlay
.d-icon-pause {
display: initial;
}
&.paused-animated-image
.animated.manually-paused
+ .animated-image-overlay
.d-icon-play {
display: initial;
}
}
.paused-animated-image {
img.animated {
// need to keep the image hidden but clickable
// so the user can resume animation

View File

@ -154,6 +154,7 @@ module SvgSprite
"moon",
"paint-brush",
"paper-plane",
"pause",
"pencil-alt",
"play",
"plug",