a pixel-sized component that scales to fill its dynamic-sized parent component
Features
- automatically scales (via
transform) to fill available space - centers self within parent
- supports both
containandcoverfill modes - smooth transitions on prop changes
Dependencies
react-usefor observe/measure hookmotionfor animations
import React, { useMemo } from 'react'import { useMeasure } from 'react-use'import { motion } from 'motion/react'
const PixelSizedAutoScaler = function ({ screen_pixels_horiz, screen_pixels_vert, fill_mode, color_bg_canvas, children}) {
const [ ref, { width, height } ] = useMeasure()
const pos_top = useMemo(() => { const pos_top = (height / 2) - (screen_pixels_vert / 2) return pos_top }, [ height, screen_pixels_vert ])
const pos_left = useMemo(() => { const pos_left = (width / 2) - (screen_pixels_horiz / 2) return pos_left }, [ width, screen_pixels_horiz ])
const scaleFactor = useMemo(() => {
let m_func if (fill_mode === undefined || fill_mode === 'contain') { m_func = Math.min } else if (fill_mode === 'cover') { m_func = Math.max } else { console.error(`unexpected: fill_mode=${fill_mode}`) // fallback m_func = Math.min }
return m_func( (1 / (screen_pixels_horiz / width)), (1 / (screen_pixels_vert / height)), )
}, [ height, width, screen_pixels_horiz, screen_pixels_vert, fill_mode ])
const jsx_inner = (width === 0 || height === 0) ? undefined : ( <motion.div initial={false} animate={{ top : pos_top, left : pos_left, scale: scaleFactor, }} transition={{ ease : 'linear', duration: 1/8, }} style={{ position: 'absolute', width : `${screen_pixels_horiz}px`, height : `${screen_pixels_vert}px`,
backgroundColor: color_bg_canvas, }} > {children} </motion.div> )
return ( <div ref={ref} style={{ height: '100%', width: '100%', position: 'relative', }} > {jsx_inner} </div> )
}
export { PixelSizedAutoScaler }