Skip to content

Instantly share code, notes, and snippets.

@mnpenner
Last active February 5, 2022 20:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mnpenner/88f5d5d2c19b7f25bc0a4dba6c3d4a55 to your computer and use it in GitHub Desktop.
Save mnpenner/88f5d5d2c19b7f25bc0a4dba6c3d4a55 to your computer and use it in GitHub Desktop.
Smart-sized image
import {useMemo, useRef} from 'react'
import {useBoundingBox} from '../hooks/useBoundingBox'
import {sortBy} from 'lodash-es'
interface Size {
width: number
height: number
}
interface SrcItem extends Size {
url: string
}
function getAspectRatio(r: Size): number {
return r.width/r.height
}
type Props = Override<React.ComponentProps<'img'>, {
srcSet: SrcItem[]
}, 'src'>
const LazyImg: React.FC<Props> = ({srcSet,...props}) => {
const ref = useRef<HTMLImageElement>(null);
const rect = useBoundingBox(ref);
const src = useMemo(() => {
if(srcSet?.length && rect.width > 0 && rect.height > 0) {
const aspectRatio = getAspectRatio(rect)
const sources = sortBy(srcSet, [
src => Math.abs(getAspectRatio(src) - aspectRatio),
src => src.width < rect.width,
src => src.height < rect.height,
src => {
const isBigger = src.width >= rect.width && src.height >= rect.height;
return (isBigger ? 1 : -1) * src.width * src.height;
},
])
return sources[0].url
}
return undefined;
}, [srcSet,rect])
return <img ref={ref} src={src} {...props}/>
}
LazyImg.defaultProps = {
loading: 'lazy'
}
export default LazyImg;
import {useLayoutEffect, useCallback, useState, Ref, useRef, RefObject, useMemo} from 'react'
import {debounce} from 'lodash-es'
interface BoundingBox {
left: number
right: number
bottom: number
top: number
width: number
height: number
x: number;
y: number;
}
export const useBoundingBox = (ref: RefObject<Element>) => {
const [rect, setRect] = useState<BoundingBox>(getRect(ref.current))
const handleResize = useMemo(() => debounce(() => {
if (!ref.current) {
return
}
// Update client rect
setRect(getRect(ref.current))
},50,{maxWait:1000}), [ref.current])
useLayoutEffect(() => {
const element = ref.current
if (!element) {
return
}
// handleResize()
if (typeof ResizeObserver === 'function') {
const resizeObserver = new ResizeObserver(handleResize)
resizeObserver.observe(element)
return () => {
resizeObserver.disconnect()
}
} else {
// Browser support, remove freely
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}
}, [ref.current])
return rect
}
function getRect(element: Element|null): BoundingBox {
if (!element) {
return {
left: 0,
right: 0,
bottom: 0,
top: 0,
width: 0,
height: 0,
x: 0,
y: 0
}
}
return element.getBoundingClientRect()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment