Image component lazy behavior
This commit is contained in:
1 parent
8f54454d32
commit
11ff066e6c
4 files changed
+79
-17
No files matched your search
@@ -67,9 +67,6 @@ const Page: NextPage<Props> = props => {
|
||||
srcSet={srcSet}
|
||||
style={{
|
||||
marginBottom: 'var(--size-l)',
|
||||
}}
|
||||
imgStyle={{
|
||||
width: '100%',
|
||||
borderRadius: 'var(--radius)',
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import './styles.css'
|
||||
import cx from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import { Image } from '../Image'
|
||||
|
||||
export type CardLayout = 'simple' | 'featured'
|
||||
export type CardProps = {
|
||||
@@ -19,8 +20,7 @@ export const Card: React.FC<CardProps> = props => (
|
||||
<Link href={props.href}>
|
||||
<a>
|
||||
<div className={'card-image'}>
|
||||
<img
|
||||
className={cx({ opa: false })}
|
||||
<Image
|
||||
alt={props.img.alt}
|
||||
src={props.img.src}
|
||||
srcSet={props.img.srcSet}
|
||||
|
||||
@@ -1,23 +1,73 @@
|
||||
import './styles.css'
|
||||
import { memo } from 'react'
|
||||
import { memo, useRef, useState, useEffect, useCallback } from 'react'
|
||||
|
||||
export type ImageProps = {
|
||||
style?: React.CSSProperties
|
||||
imgStyle?: React.CSSProperties
|
||||
src: string
|
||||
alt?: string
|
||||
srcSet?: string
|
||||
}
|
||||
|
||||
export const Image: React.FC<ImageProps> = memo(props => {
|
||||
const [imageSrc, setImageSrc] = useState<[string, string]>([null, null])
|
||||
const ref = useRef<HTMLImageElement>()
|
||||
const onLoad = useCallback((event) => {
|
||||
event.target.classList.add('loaded')
|
||||
event.target.classList.remove('pending')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
ref.current.classList.add('pending')
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
let observer: IntersectionObserver
|
||||
let didCancel = false
|
||||
|
||||
if (ref.current && imageSrc[0] !== props.src) {
|
||||
if (IntersectionObserver) {
|
||||
observer = new IntersectionObserver(
|
||||
entries => {
|
||||
entries.forEach(entry => {
|
||||
if (
|
||||
!didCancel &&
|
||||
(entry.intersectionRatio > 0 || entry.isIntersecting)
|
||||
) {
|
||||
setImageSrc([props.src, props.srcSet])
|
||||
observer.unobserve(ref.current)
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
threshold: 0.01,
|
||||
rootMargin: "75%",
|
||||
}
|
||||
)
|
||||
observer.observe(ref.current)
|
||||
} else {
|
||||
// Old browsers fallback
|
||||
setImageSrc([props.src, props.srcSet])
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
didCancel = true
|
||||
if (observer && observer.unobserve) {
|
||||
observer.unobserve(ref.current)
|
||||
}
|
||||
}
|
||||
}, [props.src, props.srcSet, imageSrc, ref])
|
||||
|
||||
return (
|
||||
<picture className={'image'} style={props.style}>
|
||||
<img
|
||||
style={props.imgStyle}
|
||||
alt={props.alt}
|
||||
src={props.src}
|
||||
srcSet={props.srcSet}
|
||||
/>
|
||||
</picture>
|
||||
<img
|
||||
ref={ref}
|
||||
onLoad={onLoad}
|
||||
className={'image'}
|
||||
style={props.style}
|
||||
alt={props.alt}
|
||||
src={imageSrc[0]}
|
||||
srcSet={imageSrc[1]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
@@ -1,7 +1,22 @@
|
||||
@keyframes loaded {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image img {
|
||||
display: block;
|
||||
.image.pending {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.image.loaded {
|
||||
animation: loaded 250ms ease-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
Reference in new issue
Block a user