perf: 图片渐进加载,缩短首屏等待

- 流内单图改用缩略图(原图仅在灯箱按需加载),体积大幅减小
- BubbleImage 加 decoding=async + 加载完淡入(ark-img-fade),图片逐张出现
- 视频海报/文件预览/推荐卡/热门榜补 decoding=async、lazy
原图无缩略图时自动回退,无回归。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
TerryM
2026-05-30 01:54:28 +08:00
parent 29dc71d2dd
commit a4884a689d
6 changed files with 22 additions and 15 deletions

View File

@@ -45,6 +45,7 @@ function AttachmentRow({ postId, att }: { postId: string; att: Attachment }) {
src={previewUrl}
alt=""
loading="lazy"
decoding="async"
onError={() => setPreviewFailed(true)}
className="h-16 w-16 shrink-0 rounded-lg object-cover"
/>

View File

@@ -20,6 +20,11 @@ export function SingleImageFrame({
return (
<AdaptiveImageFrame
attachment={attachment}
// Show the lightweight thumbnail in-stream for fast, progressive loading;
// the full image is loaded on tap in the lightbox. Falls back to the full
// asset if no thumbnail exists.
src={attachment.thumbnailUrl ?? attachment.thumbUrl ?? attachment.url}
fallbackSrc={[attachment.thumbUrl, attachment.url]}
onOpen={() => openLightbox([attachment], 0, text, postId)}
ariaLabel="View image"
>

View File

@@ -1,4 +1,4 @@
import { LoaderCircle, Maximize2, Play, X } from "lucide-react";
import { LoaderCircle, Play, X } from "lucide-react";
import { DownloadCloudIcon } from "../../icons/DownloadCloudIcon";
import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
@@ -79,10 +79,12 @@ function VideoAttachmentCard({
src={attachment.url}
poster={attachment.posterUrl}
controls
controlsList="nodownload noplaybackrate noremoteplayback"
disablePictureInPicture
playsInline
autoPlay
onClick={(e) => e.stopPropagation()}
className="absolute inset-0 h-full w-full"
className="ark-message-video absolute inset-0 h-full w-full"
/>
<AttachmentDownloadPill
postId={postId}
@@ -90,18 +92,6 @@ function VideoAttachmentCard({
leadingLabel={duration}
className="absolute left-2 top-2 z-20"
/>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
const v = videoRef.current;
openVideo(attachment, v?.currentTime ?? 0);
}}
className="absolute right-2 top-2 z-20 flex h-9 w-9 items-center justify-center rounded-full bg-black/60 text-white shadow-lg ring-1 ring-white/25 transition hover:bg-black/80"
aria-label="Fullscreen"
>
<Maximize2 className="h-4 w-4" strokeWidth={2.2} />
</button>
</>
) : (
<>
@@ -109,6 +99,8 @@ function VideoAttachmentCard({
<img
src={posterUrl}
alt=""
loading="lazy"
decoding="async"
className={`absolute inset-0 h-full w-full object-cover ${
overlayCount ? "blur-sm scale-105" : ""
}`}
@@ -284,6 +276,8 @@ function VideoListDialog({
<img
src={thumb}
alt=""
loading="lazy"
decoding="async"
className="h-full w-full object-cover"
/>
) : (