mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-14 15:09:22 +00:00
171 lines
3.9 KiB
JavaScript
171 lines
3.9 KiB
JavaScript
import { siteConfig } from '@/lib/config'
|
||
import Head from 'next/head'
|
||
import { useEffect, useRef, useState } from 'react'
|
||
|
||
/**
|
||
* 图片懒加载
|
||
* @param {*} param0
|
||
* @returns
|
||
*/
|
||
export default function LazyImage({
|
||
priority,
|
||
id,
|
||
src,
|
||
alt,
|
||
placeholderSrc,
|
||
className,
|
||
width,
|
||
height,
|
||
title,
|
||
onLoad,
|
||
onClick,
|
||
style
|
||
}) {
|
||
const maxWidth = siteConfig('IMAGE_COMPRESS_WIDTH')
|
||
const defaultPlaceholderSrc = siteConfig('IMG_LAZY_LOAD_PLACEHOLDER')
|
||
const imageRef = useRef(null)
|
||
const [currentSrc, setCurrentSrc] = useState(
|
||
placeholderSrc || defaultPlaceholderSrc
|
||
)
|
||
|
||
/**
|
||
* 占位图加载成功
|
||
*/
|
||
const handleThumbnailLoaded = () => {
|
||
if (typeof onLoad === 'function') {
|
||
// onLoad() // 触发传递的onLoad回调函数
|
||
}
|
||
}
|
||
// 原图加载完成
|
||
const handleImageLoaded = img => {
|
||
if (typeof onLoad === 'function') {
|
||
onLoad() // 触发传递的onLoad回调函数
|
||
}
|
||
}
|
||
/**
|
||
* 图片加载失败回调
|
||
*/
|
||
const handleImageError = () => {
|
||
if (imageRef.current) {
|
||
// 尝试加载 placeholderSrc,如果失败则加载 defaultPlaceholderSrc
|
||
if (imageRef.current.src !== placeholderSrc && placeholderSrc) {
|
||
imageRef.current.src = placeholderSrc
|
||
} else {
|
||
imageRef.current.src = defaultPlaceholderSrc
|
||
}
|
||
}
|
||
}
|
||
|
||
useEffect(() => {
|
||
const adjustedImageSrc =
|
||
adjustImgSize(src, maxWidth) || defaultPlaceholderSrc
|
||
|
||
// 加载原图
|
||
const img = new Image()
|
||
img.src = adjustedImageSrc
|
||
img.onload = () => {
|
||
setCurrentSrc(adjustedImageSrc)
|
||
handleImageLoaded(adjustedImageSrc)
|
||
}
|
||
img.onerror = handleImageError
|
||
|
||
const observer = new IntersectionObserver(
|
||
entries => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
const lazyImage = entry.target
|
||
lazyImage.src = adjustedImageSrc
|
||
observer.unobserve(lazyImage)
|
||
}
|
||
})
|
||
},
|
||
{ rootMargin: '50px 0px' } // Adjust the rootMargin as needed to trigger the loading earlier or later
|
||
)
|
||
|
||
if (imageRef.current) {
|
||
observer.observe(imageRef.current)
|
||
}
|
||
|
||
return () => {
|
||
if (imageRef.current) {
|
||
observer.unobserve(imageRef.current)
|
||
}
|
||
}
|
||
}, [src, maxWidth])
|
||
|
||
// 动态添加width、height和className属性,仅在它们为有效值时添加
|
||
const imgProps = {
|
||
ref: imageRef,
|
||
src: currentSrc,
|
||
alt: alt,
|
||
onLoad: handleThumbnailLoaded, // 缩略图加载完成
|
||
onError: handleImageError // 添加onError处理函数
|
||
}
|
||
|
||
if (id) {
|
||
imgProps.id = id
|
||
}
|
||
|
||
if (title) {
|
||
imgProps.title = title
|
||
}
|
||
|
||
if (width && width !== 'auto') {
|
||
imgProps.width = width
|
||
}
|
||
|
||
if (height && height !== 'auto') {
|
||
imgProps.height = height
|
||
}
|
||
if (className) {
|
||
imgProps.className = className
|
||
}
|
||
if (style) {
|
||
imgProps.style = style
|
||
}
|
||
if (onClick) {
|
||
imgProps.onClick = onClick
|
||
}
|
||
return (
|
||
<>
|
||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||
<img {...imgProps} />
|
||
{/* 预加载 */}
|
||
{priority && (
|
||
<Head>
|
||
<link rel='preload' as='image' href={adjustImgSize(src, maxWidth)} />
|
||
</Head>
|
||
)}
|
||
</>
|
||
)
|
||
}
|
||
|
||
/**
|
||
* 根据窗口尺寸决定压缩图片宽度
|
||
* @param {*} src
|
||
* @param {*} maxWidth
|
||
* @returns
|
||
*/
|
||
const adjustImgSize = (src, maxWidth) => {
|
||
if (!src) {
|
||
return null
|
||
}
|
||
const screenWidth =
|
||
(typeof window !== 'undefined' && window?.screen?.width) || maxWidth
|
||
|
||
// 屏幕尺寸大于默认图片尺寸,没必要再压缩
|
||
if (screenWidth > maxWidth) {
|
||
return src
|
||
}
|
||
|
||
// 正则表达式,用于匹配 URL 中的 width 参数
|
||
const widthRegex = /width=\d+/
|
||
// 正则表达式,用于匹配 URL 中的 w 参数
|
||
const wRegex = /w=\d+/
|
||
|
||
// 使用正则表达式替换 width/w 参数
|
||
return src
|
||
.replace(widthRegex, `width=${screenWidth}`)
|
||
.replace(wRegex, `w=${screenWidth}`)
|
||
}
|