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
contain
andcover
fill modes - smooth transitions on prop changes
Dependencies
react-use
for observe/measure hookmotion
for 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 }