Skip to content

Commit af98faa

Browse files
committed
replace canvas with css content; pause remaining things
1 parent 436bb20 commit af98faa

File tree

1 file changed

+38
-70
lines changed

1 file changed

+38
-70
lines changed

src/features/accesskit/disable_gifs.js

Lines changed: 38 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { buildStyle, postSelector } from '../../utils/interface.js';
55
import { getPreferences } from '../../utils/preferences.js';
66
import { memoize } from '../../utils/memoize.js';
77

8-
const canvasClass = 'xkit-paused-gif-placeholder';
98
const pausedPosterAttribute = 'data-paused-gif-use-poster';
9+
const pausedContentVar = '--xkit-paused-gif-content';
1010
const pausedBackgroundImageVar = '--xkit-paused-gif-background-image';
1111
const hoverContainerAttribute = 'data-paused-gif-hover-container';
1212
const labelAttribute = 'data-paused-gif-label';
@@ -16,7 +16,6 @@ const containerClass = 'xkit-paused-gif-container';
1616
let loadingMode;
1717

1818
const hovered = `:is(:hover, [${hoverContainerAttribute}]:hover *)`;
19-
const parentHovered = `:is(:hover > *, [${hoverContainerAttribute}]:hover *)`;
2019

2120
export const styleElement = buildStyle(`
2221
[${labelAttribute}]::after {
@@ -46,16 +45,17 @@ export const styleElement = buildStyle(`
4645
transform: translateY(-50%);
4746
}
4847
49-
.${canvasClass} {
50-
position: absolute;
51-
visibility: visible;
48+
[${labelAttribute}]${hovered}::after,
49+
[${pausedPosterAttribute}]:not(${hovered}) > div > ${keyToCss('knightRiderLoader')} {
50+
display: none;
51+
}
5252
53-
background-color: rgb(var(--white));
53+
${keyToCss('blogCard')} ${keyToCss('headerImage')}${keyToCss('small')}[${labelAttribute}]::after {
54+
font-size: 0.8rem;
55+
top: calc(140px - 1em - 2.2ch);
5456
}
5557
56-
.${canvasClass}${parentHovered},
57-
[${labelAttribute}]${hovered}::after,
58-
[${pausedPosterAttribute}]:not(${hovered}) > div > ${keyToCss('knightRiderLoader')} {
58+
img:is([${pausedPosterAttribute}], [style*="${pausedContentVar}"]):not(${hovered}) ~ div > ${keyToCss('knightRiderLoader')} {
5959
display: none;
6060
}
6161
${keyToCss('background')}[${labelAttribute}]::after {
@@ -73,6 +73,9 @@ ${keyToCss('background')}[${labelAttribute}]::after {
7373
display: none;
7474
}
7575
76+
img[style*="${pausedContentVar}"]:not(${hovered}) {
77+
content: var(${pausedContentVar});
78+
}
7679
[style*="${pausedBackgroundImageVar}"]:not(${hovered}) {
7780
background-image: var(${pausedBackgroundImageVar}) !important;
7881
}
@@ -95,12 +98,12 @@ const addLabel = (element, inside = false) => {
9598
};
9699

97100
/**
98-
* Fetches the selected image, tests if it is animated, and returns a canvas element with the paused
99-
* image if it is. This is expensive; avoid using this where practical. On older browsers without
100-
* ImageDecoder support, GIF images are assumed to be animated and WebP images are assumed to not be
101-
* animated.
101+
* Fetches the selected image, tests if it is animated, and returns a blob URL with the paused image
102+
* if it is. This is expensive and is a small memory leak, as the blob URL will never be revoked;
103+
* avoid using this where practical. On older browsers without ImageDecoder support, GIF images are
104+
* assumed to be animated and WebP images are assumed to not be animated.
102105
*/
103-
const createPausedCanvasIfAnimated = memoize(async sourceUrl => {
106+
const createPausedUrlIfAnimated = memoize(async sourceUrl => {
104107
const response = await fetch(sourceUrl, { headers: { Accept: 'image/webp,*/*' } });
105108
const contentType = response.headers.get('Content-Type');
106109
const canvas = document.createElement('canvas');
@@ -129,75 +132,31 @@ const createPausedCanvasIfAnimated = memoize(async sourceUrl => {
129132
canvas.height = imageBitmap.height;
130133
canvas.getContext('2d').drawImage(imageBitmap, 0, 0);
131134
}
132-
return canvas;
135+
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/webp', 1));
136+
return URL.createObjectURL(blob);
133137
});
134138

135-
/**
136-
* Fetches the selected image, tests if it is animated, and returns a blob URL with the paused image
137-
* if it is. This is expensive and is a small memory leak, as the blob URL will never be revoked;
138-
* avoid using this where practical.
139-
*/
140-
const createPausedUrlIfAnimated = memoize(async sourceUrl => {
141-
const canvas = await createPausedCanvasIfAnimated(sourceUrl);
142-
if (canvas) {
143-
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/webp', 1));
144-
return URL.createObjectURL(blob);
145-
}
146-
});
147-
148-
const pauseGif = function (gifElement) {
149-
const image = new Image();
150-
image.src = gifElement.currentSrc;
151-
image.onload = () => {
152-
if (gifElement.parentNode && gifElement.parentNode.querySelector(`.${canvasClass}`) === null) {
153-
const canvas = document.createElement('canvas');
154-
canvas.width = image.naturalWidth;
155-
canvas.height = image.naturalHeight;
156-
canvas.className = gifElement.className;
157-
canvas.classList.add(canvasClass);
158-
canvas.setAttribute('style', gifElement.getAttribute('style'));
159-
canvas.getContext('2d').drawImage(image, 0, 0);
160-
gifElement.after(canvas);
161-
addLabel(gifElement);
162-
}
163-
};
164-
};
165-
166-
const pauseWebP = async function (gifElement) {
167-
const canvas = await createPausedCanvasIfAnimated(gifElement.currentSrc);
168-
if (canvas) {
169-
canvas.className = gifElement.className;
170-
canvas.classList.add(canvasClass);
171-
canvas.setAttribute('style', gifElement.getAttribute('style'));
172-
gifElement.after(canvas);
173-
addLabel(gifElement);
174-
}
175-
};
176-
177139
const processGifs = function (gifElements) {
178140
gifElements.forEach(async gifElement => {
179141
if (gifElement.closest(`${keyToCss('avatarImage', 'subAvatarImage')}, .block-editor-writing-flow`)) return;
180-
const pausedGifElements = [...gifElement.parentNode.querySelectorAll(`.${canvasClass}`)];
181-
if (pausedGifElements.length) {
182-
gifElement.after(...pausedGifElements);
183-
return;
184-
}
185-
186142
gifElement.decoding = 'sync';
187143

188144
const posterElement = gifElement.parentElement.querySelector(keyToCss('poster'));
189145
if (posterElement) {
190146
gifElement.parentElement.setAttribute(pausedPosterAttribute, loadingMode);
191-
addLabel(posterElement);
192-
return;
193-
}
147+
} else {
148+
const sourceUrl = gifElement.currentSrc ||
149+
await new Promise(resolve => gifElement.addEventListener('load', () => resolve(gifElement.currentSrc), { once: true }));
194150

195-
const sourceUrl = gifElement.currentSrc ||
196-
await new Promise(resolve => gifElement.addEventListener('load', () => resolve(gifElement.currentSrc), { once: true }));
151+
const pausedUrl = await createPausedUrlIfAnimated(sourceUrl);
152+
if (!pausedUrl) return;
197153

198-
sourceUrl.endsWith('.webp') ? pauseWebP(gifElement) : pauseGif(gifElement);
154+
gifElement.style.setProperty(pausedContentVar, `url(${pausedUrl})`);
155+
}
156+
addLabel(gifElement);
199157

200158
gifElement.closest(keyToCss(
159+
'albumImage', // post audio element
201160
'imgLink' // trending tag: https://www.tumblr.com/explore/trending
202161
))?.setAttribute(hoverFixAttribute, '');
203162
});
@@ -262,10 +221,18 @@ export const main = async function () {
262221
${
263222
'figure' // post image/imageset; recommended blog carousel entry; blog view sidebar "more like this"; post in grid view; blog card modal post entry
264223
},
224+
${
225+
'main.labs' // labs settings header: https://www.tumblr.com/settings/labs
226+
},
265227
${keyToCss(
266228
'linkCard', // post link element
229+
'albumImage', // post audio element
230+
'messageImage', // direct message attached image
231+
'messagePost', // direct message linked post
267232
'typeaheadRow', // modal search dropdown entry
268233
'tagImage', // search page sidebar related tags, recommended tag carousel entry: https://www.tumblr.com/search/gif, https://www.tumblr.com/explore/recommended-for-you
234+
'headerBanner', // blog view header
235+
'headerImage', // modal blog card header, activity page "biggest fans" header
269236
'topPost', // activity page top post
270237
'colorfulListItemWrapper', // trending tag: https://www.tumblr.com/explore/trending
271238
'takeoverBanner' // advertisement
@@ -313,11 +280,12 @@ export const clean = async function () {
313280
wrapper.replaceWith(...wrapper.children)
314281
);
315282

316-
$(`.${canvasClass}`).remove();
317283
$(`[${labelAttribute}]`).removeAttr(labelAttribute);
318284
$(`[${pausedPosterAttribute}]`).removeAttr(pausedPosterAttribute);
319285
$(`[${hoverContainerAttribute}]`).removeAttr(hoverContainerAttribute);
320286
$(`[${hoverFixAttribute}]`).removeAttr(hoverFixAttribute);
287+
[...document.querySelectorAll(`img[style*="${pausedContentVar}"]`)]
288+
.forEach(element => element.style.removeProperty(pausedContentVar));
321289
[...document.querySelectorAll(`[style*="${pausedBackgroundImageVar}"]`)]
322290
.forEach(element => element.style.removeProperty(pausedBackgroundImageVar));
323291
};

0 commit comments

Comments
 (0)