Skip to content

Commit bb2dcdd

Browse files
committed
fix: 修复画廊图片动画黑边问题
1 parent 60c91c4 commit bb2dcdd

1 file changed

Lines changed: 58 additions & 6 deletions

File tree

apps/web/src/app/(build-in)/gallery/@modal/(.)photo/[slug]/PhotoModalClient.tsx

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ function formatGpsSpeed(speed: number, ref?: string): string {
7777
return `${speed.toFixed(1)} ${unit}`;
7878
}
7979

80+
function getExifOrientation(exif: unknown): number | undefined {
81+
if (!exif || typeof exif !== "object" || Array.isArray(exif)) {
82+
return undefined;
83+
}
84+
85+
const orientation = (exif as { orientation?: unknown }).orientation;
86+
return typeof orientation === "number" ? orientation : undefined;
87+
}
88+
8089
interface PhotoWithMedia {
8190
id: number;
8291
slug: string;
@@ -126,6 +135,10 @@ export default function PhotoModalClient({
126135
const [isOpen, setIsOpen] = useState(true);
127136
const [isClosing, setIsClosing] = useState(false);
128137
const [hdImageLoaded, setHdImageLoaded] = useState(false);
138+
const [loadedImageDimensions, setLoadedImageDimensions] = useState<{
139+
width: number;
140+
height: number;
141+
} | null>(null);
129142
const [openAnimationComplete, setOpenAnimationComplete] = useState(false);
130143
const [mounted, setMounted] = useState(false);
131144

@@ -144,6 +157,41 @@ export default function PhotoModalClient({
144157
const thumbnailUrl = useGalleryLightboxStore((s) => s.thumbnailUrl);
145158
const clearStore = useGalleryLightboxStore((s) => s.clear);
146159

160+
const metadataImageDimensions = useMemo(() => {
161+
const width = photo.media.width || 800;
162+
const height = photo.media.height || 600;
163+
const orientation = getExifOrientation(photo.media.exif);
164+
165+
// EXIF 5-8 表示图片需要旋转 90 度,数据库中的宽高仍可能是旋转前的值。
166+
if (orientation !== undefined && orientation >= 5 && orientation <= 8) {
167+
return { width: height, height: width };
168+
}
169+
170+
return { width, height };
171+
}, [photo.media.exif, photo.media.height, photo.media.width]);
172+
173+
const handleHdImageLoad = useCallback(
174+
(event: React.SyntheticEvent<HTMLImageElement>) => {
175+
const { naturalWidth, naturalHeight } = event.currentTarget;
176+
177+
if (naturalWidth > 0 && naturalHeight > 0) {
178+
setLoadedImageDimensions((previous) => {
179+
if (
180+
previous?.width === naturalWidth &&
181+
previous.height === naturalHeight
182+
) {
183+
return previous;
184+
}
185+
186+
return { width: naturalWidth, height: naturalHeight };
187+
});
188+
}
189+
190+
setHdImageLoaded(true);
191+
},
192+
[],
193+
);
194+
147195
// 隐藏原图
148196
const hideOriginalImage = useCallback(() => {
149197
if (openedPhotoId !== null) {
@@ -386,8 +434,8 @@ export default function PhotoModalClient({
386434
}
387435

388436
// 图片原始尺寸
389-
const naturalWidth = photo.media.width || 800;
390-
const naturalHeight = photo.media.height || 600;
437+
const { width: naturalWidth, height: naturalHeight } =
438+
loadedImageDimensions || metadataImageDimensions;
391439

392440
// 计算适配的尺寸
393441
const widthRatio = maxImageWidth / naturalWidth;
@@ -446,11 +494,15 @@ export default function PhotoModalClient({
446494
containerX,
447495
containerY,
448496
};
449-
}, [photo.media.width, photo.media.height, sourceRect, isMobile]);
497+
}, [loadedImageDimensions, metadataImageDimensions, sourceRect, isMobile]);
450498

451499
// 如果没有 geometry,不渲染
452500
if (!geometry) return null;
453501

502+
// 过渡期间需要填满源卡片,避免宽高比变化时出现黑边;静止后再完整显示原图。
503+
const modalImageObjectFit =
504+
openAnimationComplete && !isClosing ? "object-contain" : "object-cover";
505+
454506
// 上传者信息
455507
const uploader = photo.media.user;
456508

@@ -970,7 +1022,7 @@ export default function PhotoModalClient({
9701022
initial={{ opacity: 1 }}
9711023
exit={{ opacity: 0 }}
9721024
transition={{ duration: 0.3 }}
973-
className="absolute inset-0 w-full h-full object-cover z-[1]"
1025+
className={`absolute inset-0 w-full h-full ${modalImageObjectFit} z-[1]`}
9741026
draggable={false}
9751027
onLoad={handleThumbnailLoad}
9761028
/>
@@ -983,10 +1035,10 @@ export default function PhotoModalClient({
9831035
fill
9841036
sizes="(max-width: 768px) 100vw, 80vw"
9851037
priority
986-
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 z-[2] ${
1038+
className={`absolute inset-0 w-full h-full ${modalImageObjectFit} transition-opacity duration-300 z-[2] ${
9871039
hdImageLoaded ? "opacity-100" : "opacity-0"
9881040
}`}
989-
onLoad={() => setHdImageLoaded(true)}
1041+
onLoad={handleHdImageLoad}
9901042
data-lightbox
9911043
/>
9921044
</motion.div>

0 commit comments

Comments
 (0)