Introducing Aceternity UI Pro - Premium component packs and templates to build awesome websites.
Logo

Lens

A lens component to zoom into images, videos, or practically anything.

Open in
image

Apple Vision Pro

The all new apple vision pro was the best thing that happened around 8 months ago, not anymore.

Installation

Install dependencies

npm i motion clsx tailwind-merge

Add util file

lib/utils.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Copy the source code

components/ui/lens.tsx

"use client";
 
import React, { useRef, useState } from "react";
import { AnimatePresence, motion } from "motion/react";
 
interface LensProps {
  children: React.ReactNode;
  zoomFactor?: number;
  lensSize?: number;
  position?: {
    x: number;
    y: number;
  };
  isStatic?: boolean;
  isFocusing?: () => void;
  hovering?: boolean;
  setHovering?: (hovering: boolean) => void;
}
 
export const Lens: React.FC<LensProps> = ({
  children,
  zoomFactor = 1.5,
  lensSize = 170,
  isStatic = false,
  position = { x: 200, y: 150 },
  hovering,
  setHovering,
}) => {
  const containerRef = useRef<HTMLDivElement>(null);
 
  const [localIsHovering, setLocalIsHovering] = useState(false);
 
  const isHovering = hovering !== undefined ? hovering : localIsHovering;
  const setIsHovering = setHovering || setLocalIsHovering;
 
  // const [isHovering, setIsHovering] = useState(false);
  const [mousePosition, setMousePosition] = useState({ x: 100, y: 100 });
 
  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    const rect = e.currentTarget.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    setMousePosition({ x, y });
  };
 
  return (
    <div
      ref={containerRef}
      className="relative overflow-hidden rounded-lg z-20"
      onMouseEnter={() => {
        setIsHovering(true);
      }}
      onMouseLeave={() => setIsHovering(false)}
      onMouseMove={handleMouseMove}
    >
      {children}
 
      {isStatic ? (
        <div>
          <motion.div
            initial={{ opacity: 0, scale: 0.58 }}
            animate={{ opacity: 1, scale: 1 }}
            exit={{ opacity: 0, scale: 0.8 }}
            transition={{ duration: 0.3, ease: "easeOut" }}
            className="absolute inset-0 overflow-hidden"
            style={{
              maskImage: `radial-gradient(circle ${lensSize / 2}px at ${
                position.x
              }px ${position.y}px, black 100%, transparent 100%)`,
              WebkitMaskImage: `radial-gradient(circle ${lensSize / 2}px at ${
                position.x
              }px ${position.y}px, black 100%, transparent 100%)`,
              transformOrigin: `${position.x}px ${position.y}px`,
            }}
          >
            <div
              className="absolute inset-0"
              style={{
                transform: `scale(${zoomFactor})`,
                transformOrigin: `${position.x}px ${position.y}px`,
              }}
            >
              {children}
            </div>
          </motion.div>
        </div>
      ) : (
        <AnimatePresence>
          {isHovering && (
            <div>
              <motion.div
                initial={{ opacity: 0, scale: 0.58 }}
                animate={{ opacity: 1, scale: 1 }}
                exit={{ opacity: 0, scale: 0.8 }}
                transition={{ duration: 0.3, ease: "easeOut" }}
                className="absolute inset-0 overflow-hidden"
                style={{
                  maskImage: `radial-gradient(circle ${lensSize / 2}px at ${
                    mousePosition.x
                  }px ${mousePosition.y}px, black 100%, transparent 100%)`,
                  WebkitMaskImage: `radial-gradient(circle ${
                    lensSize / 2
                  }px at ${mousePosition.x}px ${
                    mousePosition.y
                  }px, black 100%, transparent 100%)`,
                  transformOrigin: `${mousePosition.x}px ${mousePosition.y}px`,
                  zIndex: 50,
                }}
              >
                <div
                  className="absolute inset-0"
                  style={{
                    transform: `scale(${zoomFactor})`,
                    transformOrigin: `${mousePosition.x}px ${mousePosition.y}px`,
                  }}
                >
                  {children}
                </div>
              </motion.div>
            </div>
          )}
        </AnimatePresence>
      )}
    </div>
  );
};

Examples

Basic with Animation

Open in
image

Apple Vision Pro

The all new apple vision pro was the best thing that happened around 8 months ago, not anymore.

Static

Open in
image
image

Apple Vision Pro

The all new apple vision pro was the best thing that happened around 8 months ago, not anymore.

Lens on a React Component

Open in
image

Apple Vision Pro

The all new apple vision pro was the best thing that happened around 8 months ago, not anymore.

Props

PropTypeDefaultDescription
childrenReact.ReactNodeRequiredThe content to be displayed inside the lens
zoomFactornumber1.5The magnification factor for the lens
lensSizenumber170The diameter of the lens in pixels
position{ x: number, y: number }{ x: 200, y: 150 }The static position of the lens (when isStatic is true)
isStaticbooleanfalseIf true, the lens stays in a fixed position; if false, it follows the mouse
isFocusing() => void-Callback function when the lens is focusing (not used in current implementation)
hoveringboolean-External control for the hover state
setHovering(hovering: boolean) => void-External setter for the hover state

Build websites faster and 10x better than your competitors with Aceternity UI Pro

With the best in class components and templates, stand out from the crowd and get more attention to your website. Trusted by founders and entrepreneurs from all over the world.

Go Pro
I'd highly recommend working with Manu on a site redesign. We came to Manu with a basic website shell. Manu quickly took our rough concept and transformed it into a polished, user-friendly website....

Ray Thai

Head of Product at Fireworks

A product by Aceternity
Building in public at @mannupaaji