mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix: hold gif thumbnails until first frame is ready
Distinguish GIF frame loading from GIF frame failure in useFetchGifFirstFrame(), so thread and catalog thumbnails no longer render animated GIFs before the still frame arrives. Keep the existing animated fallback when the first-frame fetch actually fails.
This commit is contained in:
@@ -11,7 +11,8 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.floatingEmbed, .subplebbitAvatar {
|
||||
.floatingEmbed,
|
||||
.subplebbitAvatar {
|
||||
max-width: 250px;
|
||||
max-height: 250px;
|
||||
display: inline-flex;
|
||||
@@ -53,7 +54,8 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.thumbnailSmall span, .thumbnailSmall a {
|
||||
.thumbnailSmall span,
|
||||
.thumbnailSmall a {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
word-wrap: break-word;
|
||||
@@ -81,7 +83,10 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.content img, .content video, .content iframe, .content audio {
|
||||
.content img,
|
||||
.content video,
|
||||
.content iframe,
|
||||
.content audio {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
@@ -91,6 +96,13 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gifPlaceholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.content iframe {
|
||||
border: none;
|
||||
color-scheme: light;
|
||||
@@ -99,7 +111,7 @@
|
||||
.fileInfo {
|
||||
padding-bottom: 5px;
|
||||
text-align: center;
|
||||
color: var(--post-mobile-file-info-text-color);
|
||||
color: var(--post-mobile-file-info-text-color);
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
@@ -125,11 +137,17 @@
|
||||
margin: 8px 10px 5px 5px;
|
||||
}
|
||||
|
||||
.mediaMobile img, .mediaMobile video, .mediaMobile iframe, .mediaMobile audio {
|
||||
.mediaMobile img,
|
||||
.mediaMobile video,
|
||||
.mediaMobile iframe,
|
||||
.mediaMobile audio {
|
||||
padding: 3px 0 5px 0;
|
||||
}
|
||||
|
||||
.mediaDesktopReply img, .mediaDesktopReply video, .mediaDesktopReply iframe, .mediaDesktopReply audio {
|
||||
.mediaDesktopReply img,
|
||||
.mediaDesktopReply video,
|
||||
.mediaDesktopReply iframe,
|
||||
.mediaDesktopReply audio {
|
||||
padding: 3px 20px 5px 20px;
|
||||
}
|
||||
|
||||
@@ -138,7 +156,10 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.mediaDesktopOp img, .mediaDesktopOp video, .mediaDesktopOp iframe, .mediaDesktopOp audio {
|
||||
.mediaDesktopOp img,
|
||||
.mediaDesktopOp video,
|
||||
.mediaDesktopOp iframe,
|
||||
.mediaDesktopOp audio {
|
||||
padding: 3px 20px 5px 20px;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
@@ -164,4 +185,4 @@
|
||||
.fitToScreen video {
|
||||
max-width: 100%;
|
||||
max-height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,25 +45,42 @@ const Thumbnail = ({
|
||||
|
||||
let thumbnailComponent: React.ReactNode = null;
|
||||
const iframeThumbnail = patternThumbnailUrl || thumbnail;
|
||||
const gifFrameUrl = useFetchGifFirstFrame(type === 'gif' ? url : undefined);
|
||||
const { frameUrl: gifFrameUrl, status: gifFrameStatus } = useFetchGifFirstFrame(type === 'gif' ? url : undefined);
|
||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, url);
|
||||
const gifThumbnailButtonProps =
|
||||
gifFrameStatus === 'loading'
|
||||
? {
|
||||
role: 'button' as const,
|
||||
tabIndex: 0,
|
||||
onKeyDown: (e: React.KeyboardEvent<HTMLSpanElement>) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
setShowThumbnail(false);
|
||||
}
|
||||
},
|
||||
onClick: () => setShowThumbnail(false),
|
||||
}
|
||||
: {};
|
||||
|
||||
if (type === 'gif') {
|
||||
thumbnailComponent = (
|
||||
<img
|
||||
src={gifFrameUrl || url}
|
||||
alt=''
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
setShowThumbnail(false);
|
||||
}
|
||||
}}
|
||||
onClick={() => setShowThumbnail(false)}
|
||||
/>
|
||||
);
|
||||
thumbnailComponent =
|
||||
gifFrameStatus === 'loading' ? (
|
||||
<span className={styles.gifPlaceholder} aria-label='Loading GIF thumbnail' {...gifThumbnailButtonProps} />
|
||||
) : (
|
||||
<img
|
||||
src={gifFrameUrl || url}
|
||||
alt=''
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
setShowThumbnail(false);
|
||||
}
|
||||
}}
|
||||
onClick={() => setShowThumbnail(false)}
|
||||
/>
|
||||
);
|
||||
} else if (type === 'video') {
|
||||
thumbnailComponent = thumbnail ? (
|
||||
<img src={thumbnail} alt='' />
|
||||
@@ -405,11 +422,11 @@ const CommentMedia = ({
|
||||
const isMobile = useIsMobile();
|
||||
const { thumbnailHeight, thumbnailWidth, url } = commentMediaInfo || {};
|
||||
let type = commentMediaInfo?.type;
|
||||
const gifFrameUrl = useFetchGifFirstFrame(type === 'gif' ? url : undefined);
|
||||
const { status: gifFrameStatus } = useFetchGifFirstFrame(type === 'gif' ? url : undefined);
|
||||
|
||||
if (type === 'gif' && gifFrameUrl) {
|
||||
if (type === 'gif' && gifFrameStatus === 'ready') {
|
||||
type = 'animated gif';
|
||||
} else if (type === 'gif' && !gifFrameUrl) {
|
||||
} else if (type === 'gif' && gifFrameStatus === 'failed') {
|
||||
type = 'static gif';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user