Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions components/album/histogram-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,11 @@ export default function HistogramChart({ imageUrl, className = '' }: Readonly<Hi

return (
<div className={cn('relative w-full h-32 group', className)}>
{loading && (
{/* Only show the loading overlay on the very first computation. On a photo
switch we keep the previous histogram canvas visible and let the spring
animation morph to the new one — flashing this backdrop-blur spinner on
every switch was the panel "flicker". */}
{loading && !histogram && (
<div className="absolute inset-0 z-10 flex items-center justify-center rounded-sm bg-overlay backdrop-blur-xl">
<div className="animate-spin text-xl text-foreground/70">⟳</div>
</div>
Expand All @@ -398,7 +402,9 @@ export default function HistogramChart({ imageUrl, className = '' }: Readonly<Hi
ref={canvasRef}
className={cn(
'h-full w-full rounded-sm ring-1 ring-white/10 backdrop-blur-xl transition-all duration-200 group-hover:ring-white/20',
loading && 'opacity-30',
// Dim only during the first load (no prior canvas). On a switch the
// previous histogram stays fully visible until the new one morphs in.
loading && !histogram && 'opacity-30',
)}
/>
)}
Expand Down
19 changes: 17 additions & 2 deletions components/album/preview-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,23 @@ export default function PreviewImage(props: Readonly<PreviewImageHandleProps>) {
return null
}, [current?.width, current?.height])

// Image URL for tone analysis and histogram
const imageUrl = current?.preview_url || current?.url || ''
// Histogram/tone source: the same display-sized variant the current slide has
// already loaded (≈1280, browser-cached → no extra fetch) instead of the raw
// full-resolution `preview_url`. preview compression is off on this deployment,
// so preview_url is the ~30MP original — pointing histogram/tone at it made
// every switch RE-FETCH + RE-DECODE a 30MP image (the panel "flicker"). The
// histogram/tone scale to ~300px, so a 1280 variant is more than enough. Falls
// back to preview_url only when the photo has no generated variants.
const histAvifOk = useAvifSupport()
const histVariantBase = configData?.variantBaseUrl ?? ''
const imageUrl = current && hasReadyVariants(current.image_key, current.ready_max_width, histVariantBase)
? makeVariantLoader({
base: histVariantBase,
imageKey: current.image_key,
readyMaxWidth: current.ready_max_width,
format: histAvifOk ? 'avif' : 'webp',
})({ src: current.image_key, width: 1280 })
: (current?.preview_url || current?.url || '')

// Debounce the histogram/tone source: those run an image-load + getImageData +
// full-pixel scan, wasteful to fire for every photo flashed past during fast
Expand Down
12 changes: 10 additions & 2 deletions components/album/progressive-image.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
'use client'

import type { ProgressiveImageProps } from '~/types/props.ts'
import { useEffect, useState, useRef, Activity } from 'react'
import { useEffect, useState, useRef, Activity, memo } from 'react'
import { createPortal } from 'react-dom'
import { useTranslations } from 'next-intl'
import { MotionImage } from '~/components/album/motion-image'
import { useBlurImageDataUrl } from '~/hooks/use-blurhash'
import { WebGLImageViewer } from '~/components/album/webgl-viewer'

// Memoized so the high-res XHR's loadingProgress ticks (which re-render
// ProgressiveImage many times during a multi-MB load) don't re-render the WebGL
// viewer — its props (src/dimensions) are stable until the image actually loads,
// so without this the viewer re-rendered on every progress tick = the zoom
// "flicker during loading". Only mount/unmount (the #510 destroy lifecycle) and
// a real src change re-touch it.
const MemoWebGLImageViewer = memo(WebGLImageViewer)
import type { WebGLImageViewerRef } from '~/components/album/webgl-viewer'
import { isWebGLSupported } from '~/lib/utils/webgl'
import { hasReadyVariants, makeVariantLoader } from '~/lib/image/loader'
Expand Down Expand Up @@ -289,7 +297,7 @@ export default function ProgressiveImage(

{/* WebGL 图片查看器 */}
<div className="w-full h-full">
<WebGLImageViewer
<MemoWebGLImageViewer
ref={webglViewerRef}
src={highResImageUrl}
width={props.width}
Expand Down
Loading