Image Zoom Viewer
Create a component that allows users to hover over an image to view a magnified version of the part their cursor is over.
Solution
Explanation
HTML Structure
You have a NextJS
Image
tag and a container to control it's size.
NextJS Image tag
By using the built in framework tag instead of an img
tag, you get lots of benefits built in.
Handling image loading and source sets is handled automatically. However, there are set properties you are required to provide. Note on the tag the fill
property.
Handling image size
NextJS
will automatically add width and height of 100% so you need to control size and responsiveness with a container.
Looking at the .container
styles, you can see the full width is taken and we need to provide a height.
Also the element has to have a position: relative
so that the Image
that has position: absolute
knows which parent to position too. Otherwise it could use the body
tag.
Creating the 'Zoom' effect
The principle behind this code is adding a zoom when the element is hovered over.
To achieve this we track the pointer position when the pointer is over the container. Note on the container div two functions: onMouseMove
uses handleCursor
and onMouseLeave
will reset the tracked position with handleReset
.
handleCursor
getBoundingClientRect
returns positions of the offsets of the left and top part of the window.
event.clientX
is providing the horizontal position of the mouse. To get it's real position, we need to remove the container's left offset from the window as well as the border width. The same is done in the vertical direction.
The position state
is updated, and in turn used to update the position of the origin
(where the centre of the object is) which aligns with the position of the mouse.
Code
import { MouseEvent, useMemo, useRef, useState } from "react";
import Image from "next/image";
import styles from "./Solution.module.css";
const Solution = () => {
const containerRef = useRef<HTMLDivElement>(null);
const initialPosition = useMemo(() => ({ x: "0", y: "0" }), []);
const [position, setPosition] = useState(initialPosition);
const handleCursor = (event: MouseEvent<HTMLDivElement>) => {
if (containerRef.current) {
// find offset of container in window
const { left, top } = containerRef.current.getBoundingClientRect();
// remove offset and border width on one side
const x = event.clientX - left - 2;
const y = event.clientY - top - 2;
setPosition({ x: `${x}`, y: `${y}` });
}
};
const handleReset = () => {
setPosition(initialPosition);
};
return (
<div
ref={containerRef}
onMouseMove={handleCursor}
onMouseLeave={handleReset}
className={styles.container}
>
<Image
style={{
transformOrigin: `${position.x}px ${position.y}px`,
objectFit: "cover",
}}
className={styles.image}
fill={true}
priority={true}
src="https://images.unsplash.com/photo-1498050108023-c5249f4df085?q=80&w=1472&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
alt="laptop on desk"
/>
</div>
);
};
export default Solution;
Styling
.container {
position: relative;
overflow: hidden;
width: 100%;
min-height: 350px;
border: 2px solid black;
}
.image {
/* absolute is added by next for Image */
scale: 1;
transition: 0.5s;
}
.container:hover .image {
scale: 2.5;
}