bigbullui logobigbullui
Componentmedia

ZoomImage

Image with hover magnifier lens tracking cursor; framed double border.

Preview

Live Preview
Product image

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add zoom-image

Or install the full npm package:

npm install bigbullui

Usage

import { ZoomImage } from "@/components/ui/zoom-image"

<ZoomImage src="/placeholder.svg?seed=1" alt="Product image" zoom={2} />

Source code

zoom-image.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export type ZoomImageProps = {
  src: string;
  alt?: string;
  zoom?: number;
};

export function ZoomImage({ src, alt, zoom = 2 }: ZoomImageProps) {
  const [scale, setScale] = React.useState(1);
  const [offset, setOffset] = React.useState({ x: 0, y: 0 });

  const handleMouseMove = (e: React.MouseEvent) => {
    const target = e.currentTarget as HTMLElement;
    const rect = target.getBoundingClientRect();
    const clientX = e.clientX - rect.left;
    const clientY = e.clientY - rect.top;
    const x = (clientX / rect.width - 0.5) * (zoom - 1) * 2;
    const y = (clientY / rect.height - 0.5) * (zoom - 1) * 2;
    setOffset({ x, y });
    setScale(zoom);
  };

  const handleMouseLeave = () => {
    setScale(1);
    setOffset({ x: 0, y: 0 });
  };

  const lensClass = cn(
    "absolute pointer-events-none w-24 h-24 rounded-full bg-white/50 transform-gpu blur-2xl",
    "motion-reduce:animate-none",
    "motion-reduce:transition-none",
  );

  return (
    <div
      className={cn(
        "relative inline-block rounded-md border border-border",
        "overflow-hidden",
        "motion-reduce:animate-none",
      )}
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
    >
      <img
        src={src}
        alt={alt}
        className={cn(
          "relative w-full h-auto transition-transform duration-300",
          "group-hover:scale-110 transition-transform",
          `transform: scale(${scale})`,
          "motion-reduce:animate-none",
          "motion-reduce:transition-none",
        )}
      />
      {scale > 1 && (
        <div
          className={lensClass}
          style={{
            left: offset.x,
            top: offset.y,
          }}
        />
      )}
    </div>
  );
}

Props

PropTypeDescription
srcstringImage source URL.
altstringOptional alt text.
zoomnumberZoom level (e.g., 2 for 2x).