Hello Guys, In this tutorial, we'll build a custom Image Magnifier component using Next JS. Let’s dive into the step-by-step guide to creating your own Image Magnifier component.
Before starting, make sure you have a Next.js project set up. If not, you can create one using the following commands:
npx create-next-app@latest
You can place one sample image inside the public folder. Now let’s break down the component step by step:
import React, { MouseEvent, useEffect, useState } from 'react';
import sampleImg from '../../public/sample.png';
import Image from 'next/image';
const MAGNIFIER_SIZE = 100;
const ZOOM_LEVEL = 2.5;
Here, necessary components and hooks are imported, along with the sample image and Next.js Image
component. Constants define the size of the magnifier and the level of zoom to be applied.
..
const ImageEffect = () => {
// State variables
const [zoomable, setZoomable] = useState(false);
const [imageSize, setImageSize] = useState({ width: 0, height: 0 });
const [position, setPosition] = useState({ x: 0, y: 0, mouseX: 0, mouseY: 0 });
This part defines the state variables using the useState
hook. zoomable
tracks whether the magnifier is active(later magnifier will activated or deactivated if cursor is on image or not), imageSize
stores the dimensions of the main image, and position
maintains the coordinates for the magnifier.
...
// Event handlers
const handleMouseEnter = (e: MouseEvent) => {
let element = e.currentTarget;
let { width, height } = element.getBoundingClientRect();
setImageSize({ width, height });
setZoomable(true);
updatePosition(e);
};
const handleMouseLeave = (e: MouseEvent) => {
setZoomable(false);
updatePosition(e);
};
const handleMouseMove = (e: MouseEvent) => {
updatePosition(e);
};
const updatePosition = (e: MouseEvent) => {
const { left, top } = e.currentTarget.getBoundingClientRect();
let x = e.clientX - left;
let y = e.clientY - top;
setPosition({
x: -x * ZOOM_LEVEL + (MAGNIFIER_SIZE / 2),
y: -y * ZOOM_LEVEL + (MAGNIFIER_SIZE / 2),
mouseX: x - (MAGNIFIER_SIZE / 2),
mouseY: y - (MAGNIFIER_SIZE / 2),
});
};
These functions handle mouse enter, leave, and move events. handleMouseEnter
extracts the dimensions of the main image with the help of current target which provides the element we are in and getting the width and height of that element after that we set the width and height to the state and, set zoomable
to true, and calls updatePosition
. handleMouseLeave
sets zoomable
to false, and handleMouseMove
also calls updatePosition
. Now in updatePosition
function we get the left(x) and top(y) value of the image / element, next we subtract e.clientX(current mouse x position) with the left value so that our x value will be clamped within the image area, similarly we calculate for y. Now will be setting position to state, for the magnifier box we calculate the values for mouseX and mouseY by subtracting x with half of magnifier size such that the box follows our cursor and it is aligned to center. Now for that zooming effect, we will use x, y values which will be then passed to background position css style, for x we need to multiple with the zoom level and add it to the half magnifier size for adjustments, similarly calculate for y.
return (
<div className='flex justify-center items-center'>
<div
onMouseLeave={handleMouseLeave}
onMouseEnter={handleMouseEnter}
onMouseMove={handleMouseMove}
className='w-80 h-96 relative overflow-hidden'>
<Image
className='object-cover border z-10'
alt=""
src={sampleImg}
fill
/>
<div
style={{
backgroundPosition: `${position.x}px ${position.y}px`,
backgroundImage: `url(${sampleImg.src})`,
backgroundSize: `${imageSize.width * ZOOM_LEVEL}px ${imageSize.height * ZOOM_LEVEL}px`,
backgroundRepeat: 'no-repeat',
display: zoomable ? 'block' : 'none',
top: `${position.mouseY}px`,
left: `${position.mouseX}px`,
width: `${MAGNIFIER_SIZE}px`,
height: `${MAGNIFIER_SIZE}px`,
}}
className={`z-50 border-4 rounded-full pointer-events-none absolute border-gray-500`}
/>
</div>
</div>
);
};
This outer div
creates a with flex layout, centering its content both horizontally and vertically. This inner div
is the container for the image and magnifier. It has a fixed width of 80
and height of 96
with tailwindcss. The relative
class is applied to allow child elements to be positioned relative to this container. The overflow-hidden
class ensures that any overflowing content is hidden. The Image
component is used from the next/image
library. It fills its container (object-cover
class) and has a border
with a z-10
z-index. The fill
prop ensures the image takes up the entire container. Finally the last div
represents the magnifier. Its styling is dynamically controlled by state variables and mouse events. Let's break down the style properties:
backgroundPosition
: Determines the position of the background image within the magnifier , value will be populated from position state object x and y properties.backgroundImage
: Sets the background image to the main image.backgroundSize
: Adjusts the size of the background image based on the zoom level and width & height of the image which is set in imageSize state variable.backgroundRepeat
: Ensures the background image doesn't repeat.display
: Controls whether the magnifier is visible (block
) or hidden (none
) based on the zoomable
state.top
and left
: Position the magnifier based on the mouse coordinates from the position object property mouseY and mouseX.width
and height
: Set the dimensions of the magnifier.className
includes styling for the magnifier, such as a border, rounded corners for making it as circle, and positioning.Final Code for the component.
// Import dependencies
import React, { MouseEvent, useEffect, useState } from 'react';
import sampleImg from '../../public/sample.png';
import Image from 'next/image';
// Constants for magnifier size and zoom level
const MAGNIFIER_SIZE = 100;
const ZOOM_LEVEL = 2.5;
// ImageEffect component
const ImageEffect = () => {
// State variables
const [zoomable, setZoomable] = useState(true);
const [imageSize, setImageSize] = useState({ width: 0, height: 0 });
const [position, setPosition] = useState({ x: 100, y: 100, mouseX: 0, mouseY: 0 });
// Event handlers
const handleMouseEnter = (e: MouseEvent) => {
let element = e.currentTarget;
let { width, height } = element.getBoundingClientRect();
setImageSize({ width, height });
setZoomable(true);
updatePosition(e);
};
const handleMouseLeave = (e: MouseEvent) => {
setZoomable(false);
updatePosition(e);
};
const handleMouseMove = (e: MouseEvent) => {
updatePosition(e);
};
const updatePosition = (e: MouseEvent) => {
const { left, top } = e.currentTarget.getBoundingClientRect();
let x = e.clientX - left;
let y = e.clientY - top;
setPosition({
x: -x * ZOOM_LEVEL + (MAGNIFIER_SIZE / 2),
y: -y * ZOOM_LEVEL + (MAGNIFIER_SIZE / 2),
mouseX: x - (MAGNIFIER_SIZE / 2),
mouseY: y - (MAGNIFIER_SIZE / 2),
});
};
// Render method
return (
<div className='flex justify-center items-center'>
<div
onMouseLeave={handleMouseLeave}
onMouseEnter={handleMouseEnter}
onMouseMove={handleMouseMove}
className='w-80 h-96 relative overflow-hidden'>
<Image className='object-cover border z-10' alt="" src={sampleImg} fill />
<div
style={{
backgroundPosition: `${position.x}px ${position.y}px`,
backgroundImage: `url(${sampleImg.src})`,
backgroundSize: `${imageSize.width * ZOOM_LEVEL}px ${imageSize.height * ZOOM_LEVEL}px`,
backgroundRepeat: 'no-repeat',
display: zoomable ? 'block' : 'none',
top: `${position.mouseY}px`,
left: `${position.mouseX}px`,
width: `${MAGNIFIER_SIZE}px`,
height: `${MAGNIFIER_SIZE}px`,
}}
className={`z-50 border-4 rounded-full pointer-events-none absolute border-gray-500`}
/>
</div>
</div>
);
};
export default ImageEffect;
Finally, you can add this component to your page and run the NEXT JS app with the command npm run dev
.
import ImageEffect from '@/components/image-effect'
export default function Home() {
return (
<main className="min-h-screen flex flex-col text-center justify-center items-center">
<h1 className='text-5xl mb-10 font-bold'>IMAGE MAGNIFIER</h1>
<ImageEffect />
</main>
)
}
Congratulations! You’ve successfully built an Image Magnifier component with Next.js. Feel free to customize the styles and add additional functionalities to suit your project’s needs.