Star on GitHub
npm install react-driftkit
Zoom
3.00×
Lens size
200px
Crosshair
Zoom badge
Hover anywhere on the photo — the lens snaps to your cursor, stays inside the image, and hides when you leave. No activation, no drag; the target ref wires it to this one element. Zoom into the sails, the hulls, the water taxi wake, or the ripple texture.
Prop Type Default
active
Controlled active state. Omit for uncontrolled.
boolean
defaultActive
Uncontrolled initial active state.
boolean false
target
Scope the lens to one element (product-image-zoom style). When set, the lens only appears over the target, follows the cursor inside it, and hides on leave. Dragging is disabled. Accepts a CSS selector, Element, or React ref.
string | Element | RefObject<Element>
defaultPosition
Starting position — 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center' or { x, y } in viewport px. Ignored in target mode.
Corner | { x; y } 'center'
zoom
Controlled zoom factor. Omit for uncontrolled.
number
defaultZoom
Uncontrolled initial zoom factor.
number 2
minZoom
Minimum zoom factor.
number 1.25
maxZoom
Maximum zoom factor.
number 10
zoomStep
Zoom increment applied per wheel notch.
number 0.25
size
Lens diameter in pixels.
number 180
behavior
Behavior knobs: hotkey, exitOnEscape, wheelToZoom, ignoreSelector.
ZoomLensBehavior
behavior.hotkey
Keyboard shortcut to toggle active, e.g. "cmd+shift+z". Supports cmd/meta, ctrl, shift, alt/option + key.
string
behavior.exitOnEscape
Deactivate when Escape is pressed.
boolean true
behavior.wheelToZoom
Allow mouse-wheel over the lens to change zoom.
boolean true
behavior.ignoreSelector
CSS selector for elements to strip from the magnified clone. Elements with [data-zoom-lens-ignore] are always stripped.
string
on
Event handlers: activeChange, zoomChange, positionChange. All optional.
ZoomLensEvents
on.activeChange
Fires whenever active toggles — Escape, hotkey, or a setter.
(active: boolean) => void
on.zoomChange
Fires when the zoom level changes (wheel, clamp, setZoom).
(zoom: number) => void
on.positionChange
Fires while the lens is being dragged.
(pos: { x: number; y: number }) => void
borderColor
Rim color of the lens circle.
string 'rgba(255,255,255,0.9)'
borderWidth
Rim thickness in px.
number 2
showCrosshair
Render a 1px crosshair through the lens center.
boolean true
showZoomBadge
Show the current zoom level in the corner of the lens.
boolean true
zIndex
z-index for the overlay.
number 2147483647
className
CSS class added to the lens circle.
string ''
style
Inline styles merged into the lens circle.
CSSProperties {}

The lens clones a live copy of document.body, debounced on mutations. Mark your own control UI with data-zoom-lens-ignore so it stays out of the magnified view. Known limits: <canvas>, <video>, and <iframe> elements appear blank inside the lens, and position: fixed elements re-anchor to the top-left of the clone.

tsx
import { useState } from 'react';
import { ZoomLens } from 'react-driftkit';

function App() {
  const [active, setActive] = useState(false);

  return (
    <>
      <button
        data-zoom-lens-ignore
        onClick={() => setActive((a) => !a)}
      >
        {active ? 'Close lens' : 'Zoom in'}
      </button>

      <ZoomLens
        active={active}
        behavior={{ hotkey: 'cmd+shift+z' }}
        on={{ activeChange: setActive }}
      />
    </>
  );
}
typescript
type ZoomLensTarget =
  | string                         // CSS selector, resolved via querySelector
  | Element                        // a live element
  | RefObject<Element | null>      // a React ref
  | null;

interface ZoomLensProps {
  active?: boolean;
  defaultActive?: boolean;

  // Scope the lens to a single element (product-image-zoom style). When
  // set, the lens only appears while the cursor is over the target, follows
  // the cursor inside it, and hides on leave. Dragging is disabled in target
  // mode. Omit to magnify the whole page and make the lens free-draggable.
  target?: ZoomLensTarget;

  // Starting position — a corner, "center", or viewport coords. Ignored
  // in target mode (lens position is cursor-driven).
  defaultPosition?:
    | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center'
    | { x: number; y: number };

  // Zoom — controlled / uncontrolled, with clamping.
  zoom?: number;
  defaultZoom?: number;     // default 2
  minZoom?: number;         // default 1.25
  maxZoom?: number;         // default 10
  zoomStep?: number;        // default 0.25 per wheel notch

  // Lens diameter in px.
  size?: number;            // default 180

  behavior?: {
    hotkey?: string;                 // toggle, e.g. "cmd+shift+z"
    exitOnEscape?: boolean;          // default true
    wheelToZoom?: boolean;           // default true
    ignoreSelector?: string;         // strip from the magnified clone
  };

  on?: {
    activeChange?: (active: boolean) => void;
    zoomChange?: (zoom: number) => void;
    positionChange?: (pos: { x: number; y: number }) => void;
  };

  borderColor?: string;
  borderWidth?: number;
  showCrosshair?: boolean;  // default true
  showZoomBadge?: boolean;  // default true

  zIndex?: number;          // default 2147483647
  className?: string;
  style?: CSSProperties;
}

Enjoying react-driftkit?

Star the repo on GitHub to help more devs discover it.

Star on GitHub