1
1
'use client'
2
2
3
- import { forwardRef , useCallback , useMemo , useRef , useState } from 'react'
3
+ import { forwardRef , memo , useCallback , useMemo , useRef , useState } from 'react'
4
4
import clsx from 'clsx'
5
5
import { useIsomorphicLayoutEffect } from 'foxact/use-isomorphic-layout-effect'
6
6
import mediumZoom from 'medium-zoom'
7
7
import Image from 'next/image'
8
8
import { tv } from 'tailwind-variants'
9
9
import type { Zoom } from 'medium-zoom'
10
- import type { FC , ReactNode } from 'react'
10
+ import type {
11
+ AnimationEventHandler ,
12
+ DetailedHTMLProps ,
13
+ FC ,
14
+ ImgHTMLAttributes ,
15
+ ReactNode ,
16
+ } from 'react'
11
17
18
+ import { useIsMobile } from '~/atoms/hooks'
12
19
import { LazyLoad } from '~/components/common/Lazyload'
13
20
import { useIsUnMounted } from '~/hooks/common/use-is-unmounted'
14
21
import { isDev , isServerSide } from '~/lib/env'
@@ -67,7 +74,7 @@ export const ImageLazy: Component<TImageProps & BaseImageProps> = ({
67
74
const [ zoomer_ ] = useState ( ( ) => {
68
75
if ( isServerSide ) return null !
69
76
if ( zoomer ) return zoomer
70
- const zoom = mediumZoom ( undefined )
77
+ const zoom = mediumZoom ( undefined , { } )
71
78
zoomer = zoom
72
79
return zoom
73
80
} )
@@ -86,6 +93,7 @@ export const ImageLazy: Component<TImageProps & BaseImageProps> = ({
86
93
[ isUnmount ] ,
87
94
)
88
95
const imageRef = useRef < HTMLImageElement > ( null )
96
+ const isMobile = useIsMobile ( )
89
97
useIsomorphicLayoutEffect ( ( ) => {
90
98
if ( imageLoadStatus !== ImageLoadStatus . Loaded ) {
91
99
return
@@ -95,15 +103,50 @@ export const ImageLazy: Component<TImageProps & BaseImageProps> = ({
95
103
}
96
104
const $image = imageRef . current
97
105
106
+ if ( ! $image ) return
107
+ if ( isMobile ) {
108
+ $image . onclick = ( ) => {
109
+ // NOTE: document 上的 click 可以用 stopImmediatePropagation 阻止
110
+ // e.stopImmediatePropagation()
111
+ window . open ( src )
112
+ }
113
+ return ( ) => {
114
+ $image . onclick = null
115
+ }
116
+ }
117
+
98
118
if ( $image ) {
99
119
zoomer_ . attach ( $image )
100
120
101
121
return ( ) => {
102
122
zoomer_ . detach ( $image )
103
123
}
104
124
}
105
- } , [ zoom , zoomer_ , imageLoadStatus ] )
125
+ } , [ zoom , zoomer_ , imageLoadStatus , isMobile ] )
106
126
127
+ const handleOnLoad = useCallback ( ( ) => {
128
+ setImageLoadStatusSafe ( ImageLoadStatus . Loaded )
129
+ } , [ setImageLoadStatusSafe ] )
130
+ const handleError = useCallback (
131
+ ( ) => setImageLoadStatusSafe ( ImageLoadStatus . Error ) ,
132
+ [ setImageLoadStatusSafe ] ,
133
+ )
134
+ const handleOnAnimationEnd : AnimationEventHandler < HTMLImageElement > =
135
+ useCallback ( ( e ) => {
136
+ if ( ImageLoadStatus . Loaded ) {
137
+ ; ( e . target as HTMLElement ) . classList . remove (
138
+ imageStyles [ ImageLoadStatus . Loaded ] ,
139
+ )
140
+ }
141
+ } , [ ] )
142
+ const imageClassName = useMemo (
143
+ ( ) =>
144
+ styles ( {
145
+ status : imageLoadStatus ,
146
+ className : clsx ( imageStyles [ ImageLoadStatus . Loaded ] , className ) ,
147
+ } ) ,
148
+ [ className , imageLoadStatus ] ,
149
+ )
107
150
return (
108
151
< figure >
109
152
< span className = "relative flex justify-center" data-hide-print >
@@ -130,21 +173,10 @@ export const ImageLazy: Component<TImageProps & BaseImageProps> = ({
130
173
title = { title }
131
174
alt = { alt || title || '' }
132
175
ref = { imageRef }
133
- onLoad = { ( ) => {
134
- setImageLoadStatusSafe ( ImageLoadStatus . Loaded )
135
- } }
136
- onError = { ( ) => setImageLoadStatusSafe ( ImageLoadStatus . Error ) }
137
- className = { styles ( {
138
- status : imageLoadStatus ,
139
- className : clsx ( imageStyles [ ImageLoadStatus . Loaded ] , className ) ,
140
- } ) }
141
- onAnimationEnd = { ( e : Event ) => {
142
- if ( ImageLoadStatus . Loaded ) {
143
- ; ( e . target as HTMLElement ) . classList . remove (
144
- imageStyles [ ImageLoadStatus . Loaded ] ,
145
- )
146
- }
147
- } }
176
+ onLoad = { handleOnLoad }
177
+ onError = { handleError }
178
+ className = { imageClassName }
179
+ onAnimationEnd = { handleOnAnimationEnd }
148
180
/>
149
181
</ LazyLoad >
150
182
</ span >
@@ -256,21 +288,41 @@ const NoFixedPlaceholder = ({ accent }: { accent?: string }) => {
256
288
)
257
289
}
258
290
259
- const OptimizedImage : FC < any > = forwardRef ( ( { src, alt, ...rest } , ref ) => {
260
- const { height, width } = useMarkdownImageRecord ( src ! ) || rest
261
- if ( ! height || ! width ) return < img alt = { alt } src = { src } ref = { ref } { ...rest } />
262
- return (
263
- < Image
264
- alt = { alt || '' }
265
- fetchPriority = "high"
266
- priority
267
- src = { src ! }
268
- { ...rest }
269
- height = { + height }
270
- width = { + width }
271
- ref = { ref as any }
272
- />
273
- )
274
- } )
291
+ const OptimizedImage = memo (
292
+ forwardRef <
293
+ HTMLImageElement ,
294
+ DetailedHTMLProps < ImgHTMLAttributes < HTMLImageElement > , HTMLImageElement >
295
+ > ( ( { src, alt, ...rest } , ref ) => {
296
+ const { height, width } = useMarkdownImageRecord ( src ! ) || rest
297
+ const hasDim = ! ! ( height && width )
298
+
299
+ const ImageEl = (
300
+ < img data-zoom-src = { src } alt = { alt } src = { src } ref = { ref } { ...rest } />
301
+ )
302
+ return (
303
+ < >
304
+ { hasDim ? (
305
+ < >
306
+ { /* @ts -expect-error */ }
307
+ < Image
308
+ alt = { alt || '' }
309
+ fetchPriority = "high"
310
+ priority
311
+ src = { src ! }
312
+ { ...rest }
313
+ height = { + height }
314
+ width = { + width }
315
+ />
316
+ < div className = "absolute inset-0 flex justify-center opacity-0" >
317
+ { ImageEl }
318
+ </ div >
319
+ </ >
320
+ ) : (
321
+ ImageEl
322
+ ) }
323
+ </ >
324
+ )
325
+ } ) ,
326
+ )
275
327
276
328
OptimizedImage . displayName = 'OptimizedImage'
0 commit comments