Skip to content

Commit 9dc110a

Browse files
committed
fix: placeholder images not displaying on safari
1 parent 45d0d36 commit 9dc110a

3 files changed

Lines changed: 26 additions & 25 deletions

File tree

src/web/EnrichedText.css

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -304,17 +304,7 @@
304304
}
305305

306306
.et-view img.error {
307-
/* hide the browser's broken-image alt text;
308-
the ::before overlay covers the native broken-image icon. */
309-
font-size: 0;
310-
visibility: hidden;
311-
}
312-
313-
.et-view img.error::before {
314-
content: '';
315-
position: absolute;
316-
inset: 0;
317-
visibility: visible;
307+
content: linear-gradient(transparent, transparent);
318308
background-color: currentColor;
319309
-webkit-mask: var(--et-broken-image-glyph) no-repeat center / contain;
320310
mask: var(--et-broken-image-glyph) no-repeat center / contain;

src/web/EnrichedText.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ export const EnrichedText = memo(
6464
[textStyle, themingStyle, cssVars]
6565
);
6666

67-
useImageErrorFallback(containerRef, finalHtml);
6867
usePressInteractions(containerRef);
68+
useImageErrorFallback(containerRef);
6969

7070
return (
7171
<>

src/web/useImageErrorFallback.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,39 @@ import { useEffect, type RefObject } from 'react';
44
* Flag images that fail to load so CSS can swap in a broken-image placeholder.
55
*/
66
export const useImageErrorFallback = (
7-
containerRef: RefObject<HTMLElement | null>,
8-
html: string
7+
containerRef: RefObject<HTMLElement | null>
98
) => {
9+
// listen for errors
1010
useEffect(() => {
1111
const container = containerRef.current;
1212
if (!container) return;
1313

14-
const images = container.querySelectorAll<HTMLImageElement>('img');
15-
const cleanups: (() => void)[] = [];
14+
const handleImageError = (e: Event) => {
15+
const target = e.target as HTMLElement;
16+
if (target && target.tagName && target.tagName.toLowerCase() === 'img') {
17+
target.classList.add('error');
18+
}
19+
};
1620

17-
images.forEach((img) => {
18-
const handleImageError = () => img.classList.add('error');
21+
container.addEventListener('error', handleImageError, true);
1922

20-
img.addEventListener('error', handleImageError);
21-
cleanups.push(() => img.removeEventListener('error', handleImageError));
23+
return () => {
24+
container.removeEventListener('error', handleImageError, true);
25+
};
26+
}, [containerRef]);
2227

23-
// Catch images that already failed before the listener attached
28+
// handle the cached unloaded images that will not emit the error event
29+
useEffect(() => {
30+
const container = containerRef.current;
31+
if (!container) return;
32+
33+
const images =
34+
container.querySelectorAll<HTMLImageElement>('img:not(.error)');
35+
36+
images.forEach((img) => {
2437
if (img.complete && img.naturalHeight === 0) {
25-
handleImageError();
38+
img.classList.add('error');
2639
}
2740
});
28-
29-
return () => cleanups.forEach((cleanup) => cleanup());
30-
}, [containerRef, html]);
41+
});
3142
};

0 commit comments

Comments
 (0)