mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 09:42:07 +08:00
FIX: Improvements to animated image pausing (#12839)
This commit is contained in:
parent
aa9a8d1041
commit
548c044809
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -154,6 +154,7 @@ module SvgSprite
|
|||
"moon",
|
||||
"paint-brush",
|
||||
"paper-plane",
|
||||
"pause",
|
||||
"pencil-alt",
|
||||
"play",
|
||||
"plug",
|
||||
|
|
Loading…
Reference in New Issue
Block a user