Blueprint

useZoom

Add scroll-based zoom functionality to any <img /> element.

Import


import { useZoom } from '@hover/blueprint';

Options

OptionTypeDefaultDescription
zoomnumber0.1Zoom factor applied on each scroll event (controls the speed of the zoom)
maxZoomnumber | falsefalseMaximum zoom level allowed (e.g: 5 maxes out the image at 5 times the original size)
initialZoomnumber1Initial zoom level (use in combination with initialX and initialY to zoom to specific region of the image)
initialXnumber0.5Initial horizontal offset (for use with initialZoom)
initialYnumber0.5Initial horizontal offset (for use with initialZoom)

Import the ZoomOptions type to incorporate zoom options into your component's props.


import { Image, ImageProps, ZoomOptions } from '@hover/blueprint';
interface ZoomImageProps extends ImageProps {
zoomOptions?: ZoomOptions;
}
export const ZoomImage = ({ zoomOptions, ...props }: ZoomImageProps) => {
const { props } = useZoom({ maxZoom: 10, ...zoomOptions });
return <Image cursor="zoom-in" {...props} />;
};

Usage

Simply spread the returned props object into the target image element. Note that the props object includes a ref that is required for zoom to work so be careful not to overwrite it.

live

() => {
const { props } = useZoom({ maxZoom: 10 });
return (
<Image
src="/images/kitten--2560-1440.jpg"
width="700"
cursor="zoom-in"
{...props}
/>
);
};

Resize

If it's possible that the image element's size will change, you'll need to call resize. Depending on what triggers the resize, you can use the resize event on window, a ResizeObserver like @chakra-ui/react-use-size's useSize hook, or another handler that you know will cause a resize.

Window resize

Here we're using vw units so we know a window resize will always resize the image.

live

() => {
const { props, resize } = useZoom({ maxZoom: 10 });
useEffect(() => {
window.addEventListener('resize', resize);
return () => window.removeEventListener('resize', resize);
}, [resize]);
return (
<Image
src="/images/kitten--2560-1440.jpg"
width="30vw"
cursor="zoom-in"
{...props}
/>
);
};

Breakpoint

Here we're using responsive style prop width and an effect depending on the value of the useBreakpoint hook to trigger a resize when the breakpoint changes.

live

() => {
const breakpoint = useBreakpoint();
const { props, resize } = useZoom({ maxZoom: 10 });
useEffect(resize, [breakpoint]);
return (
<Image
src="/images/kitten--2560-1440.jpg"
width={{ base: '600', tablet: '700', desktop: '750' }}
cursor="zoom-in"
{...props}
/>
);
};

Handler

Here we're resizing the image in an event handler so we can just invoke resize() in the same handler.

live

() => {
const [width, setWidth] = useState(450);
const { props, resize } = useZoom({ maxZoom: 10 });
return (
<Stack spacing="300">
<Slider
min={300}
max={600}
value={width}
onChange={value => {
setWidth(value);
resize();
}}
/>
<Image
src="/images/kitten--2560-1440.jpg"
width={`${width}px`}
cursor="zoom-in"
{...props}
/>
</Stack>
);
};

Set Zoom

The useZoom hook returns the current zoomRatio and a setZoom method. This enables consumers of the hook to build custom UI zoom controls.

live

() => {
const {
props,
state: { zoomRatio },
setZoom,
} = useZoom({ maxZoom: 2.0 });
return (
<Stack spacing="300">
<Slider
min={1.0}
max={2.0}
step={0.1}
onChange={value => setZoom(value)}
value={zoomRatio}
width="30vw"
/>
<Image
cursor="zoom-in"
src="/images/kitten--2560-1440.jpg"
{...props}
/>
</Stack>
);
};

Focus Box

The useZoom hook returns a focusBox method. The focusBox receives a bounding box parameter and zooms and centers the bounding box as much as possible while still respecting maxZoom.

The bounding box is defined using the natural dimensions of the image. The natural dimensions are adjusted as needed by the useZoom hook to account for differences between the natural dimensions and the dimensions of the image on screen.

The maxZoom option can be passed to focusBox if a lower maxZoom is desired. Passing a maxZoom higher than the maxZoom passed to useZoom is ignored.

live

() => {
const {
props,
focusBox,
setZoom,
} = useZoom({ maxZoom: 8.0 });
const focusLeftEye = {
x: 631,
y: 621,
width: 289,
height: 223,
};
return (
<Stack spacing="300">
<HStack>
<Button onClick={() => setZoom(1.0)}>
Reset
</Button>
<Button onClick={() => focusBox(focusLeftEye, { maxZoom: 2.0 } )}>
Left Eye (2.0)
</Button>
<Button onClick={() => focusBox(focusLeftEye)}>
Left Eye (8.0)
</Button>
</HStack>
<Image
cursor="zoom-in"
src="/images/kitten--2560-1440.jpg"
{...props}
/>
</Stack>
);
}