npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-img-cutout

v1.4.0

Published

[![npm version](https://img.shields.io/npm/v/react-img-cutout.svg)](https://www.npmjs.com/package/react-img-cutout)

Readme

react-img-cutout

npm version

react-img-cutout provides a simple, composable component for creating interactive image regions. It enables pixel-perfect interaction using transparent PNG cutouts, while also supporting standard bounding boxes and polygons for geometric shapes.

Demo

Quick Start

1) Install

npm install react-img-cutout

Peer dependencies:

  • react >= 18
  • react-dom >= 18

2) Render a viewer

import { CutoutViewer } from "react-img-cutout"

export function ProductHero() {
  return (
    <CutoutViewer
      mainImage="/images/main.png"
      mainImageAlt="Product scene"
      effect="elevate"
      onSelect={(id) => console.log("selected:", id)}
    >
      <CutoutViewer.Cutout
        id="shoe"
        src="/images/cutouts/shoe.png"
        label="Shoe"
      >
        <CutoutViewer.Overlay placement="top-center">
          <button>View details</button>
        </CutoutViewer.Overlay>
      </CutoutViewer.Cutout>

      <CutoutViewer.Cutout
        id="bag"
        src="/images/cutouts/bag.png"
        label="Bag"
      />

      {/* No image needed — define regions with coordinates */}
      <CutoutViewer.BBoxCutout
        id="logo"
        bounds={{ x: 0.05, y: 0.05, w: 0.15, h: 0.1 }}
        label="Logo"
      />

      <CutoutViewer.PolygonCutout
        id="accent"
        points={[[0.7, 0.2], [0.9, 0.2], [0.85, 0.5], [0.65, 0.45]]}
        label="Accent"
      />
    </CutoutViewer>
  )
}

Cutout Types

The library supports three different cutout types, each suited for different use cases.

Image Cutout (CutoutViewer.Cutout)

The original cutout type — uses a transparent PNG aligned to the same coordinate space as the main image. Hit-testing is performed per-pixel using the alpha channel.

<CutoutViewer.Cutout
  id="shoe"
  src="/images/cutouts/shoe.png"
  label="Shoe"
>
  <CutoutViewer.Overlay placement="top-center">
    <button>View details</button>
  </CutoutViewer.Overlay>
</CutoutViewer.Cutout>

| Prop | Type | Description | |------|------|-------------| | id | string | Unique identifier for the cutout | | src | string | URL of the cutout image (transparent PNG, same resolution as mainImage) | | label | string? | Human-readable label (used as alt text) | | effect | HoverEffectPreset \| HoverEffect? | Override the viewer-level hover effect for this cutout | | renderLayer | (props: RenderLayerProps) => ReactNode? | Custom renderer replacing the default <img> | | children | ReactNode? | Overlay content |

Bounding Box Cutout (CutoutViewer.BBoxCutout)

Defines a rectangular region using normalized 0–1 coordinates. No image required — the component renders a styled rectangle. Ideal for highlighting areas of the image programmatically (e.g. from object-detection output).

<CutoutViewer.BBoxCutout
  id="face"
  bounds={{ x: 0.3, y: 0.1, w: 0.2, h: 0.25 }}
  label="Face"
>
  <CutoutViewer.Overlay placement="top-center">
    <span>Detected face</span>
  </CutoutViewer.Overlay>
</CutoutViewer.BBoxCutout>

| Prop | Type | Description | |------|------|-------------| | id | string | Unique identifier for the cutout | | bounds | { x, y, w, h } | Normalized 0–1 bounding box (x and y are the top-left corner) | | label | string? | Human-readable label | | effect | HoverEffectPreset \| HoverEffect? | Override the viewer-level hover effect for this cutout | | renderLayer | (props: RenderLayerProps) => ReactNode? | Custom renderer replacing the default rectangle | | children | ReactNode? | Overlay content |

Polygon Cutout (CutoutViewer.PolygonCutout)

Defines an arbitrary closed shape using an array of [x, y] normalized 0–1 points. Rendered as an SVG <polygon>. Great for non-rectangular regions such as segmentation masks or hand-drawn annotations.

<CutoutViewer.PolygonCutout
  id="lake"
  points={[
    [0.2, 0.6],
    [0.5, 0.55],
    [0.6, 0.7],
    [0.35, 0.8],
  ]}
  label="Lake"
>
  <CutoutViewer.Overlay placement="center">
    <span>Lake area</span>
  </CutoutViewer.Overlay>
</CutoutViewer.PolygonCutout>

| Prop | Type | Description | |------|------|-------------| | id | string | Unique identifier for the cutout | | points | [number, number][] | Array of normalized 0–1 [x, y] points forming a closed path | | label | string? | Human-readable label | | effect | HoverEffectPreset \| HoverEffect? | Override the viewer-level hover effect for this cutout | | renderLayer | (props: RenderLayerProps) => ReactNode? | Custom renderer replacing the default SVG polygon | | children | ReactNode? | Overlay content |

Draw Polygon (CutoutViewer.DrawPolygon)

Adds an interactive drawing layer over the viewer so users can define their own polygon regions directly on the image. Completed polygons are returned via onComplete as normalized [x, y][] coordinates, ready for direct use with PolygonCutout.

const [regions, setRegions] = useState<[number, number][][]>([])
const [drawing, setDrawing] = useState(true)

<CutoutViewer mainImage="/photo.png">
  <CutoutViewer.DrawPolygon
    enabled={drawing}
    onComplete={(points) => setRegions((prev) => [...prev, points])}
    strokeColor="#3b82f6"
    minPoints={3}
    closeThreshold={0.03}
  />
  {regions.map((pts, i) => (
    <CutoutViewer.PolygonCutout key={i} id={`region-${i}`} points={pts} />
  ))}
</CutoutViewer>

| Interaction | Effect | |---|---| | Click | Add a vertex | | Click near first vertex (≥ minPoints) | Snap-close → fires onComplete | | Double-click | Complete the polygon immediately | | Right-click | Remove the last vertex | | Esc | Cancel and reset the in-progress drawing |

| Prop | Type | Default | Description | |---|---|---|---| | onComplete | (points: [number, number][]) => void | — | Called with normalized points when a polygon is finished | | enabled | boolean | true | Toggle drawing on/off without unmounting. When false, pointer events pass through to the viewer and any in-progress drawing is cleared | | strokeColor | string | "#3b82f6" | Accent color for the in-progress polygon overlay | | minPoints | number | 3 | Minimum vertices required before snap-close or double-click complete | | closeThreshold | number | 0.03 | Normalized (0–1) snap radius for closing near the first vertex | | style | CSSProperties? | — | Additional styles for the overlay container | | className | string? | — | Additional class name for the overlay container |

useDrawPolygon (headless hook)

For building completely custom drawing UIs without the built-in SVG overlay:

import { useDrawPolygon } from "react-img-cutout"

const { points, previewPoint, willClose, reset, containerRef, containerProps } =
  useDrawPolygon({
    onComplete: (pts) => console.log(pts),
    minPoints: 3,
    closeThreshold: 0.03,
  })

return (
  <div ref={containerRef} style={{ position: "relative" }} {...containerProps}>
    <img src="/main.png" style={{ width: "100%" }} />
    {/* render your own SVG overlay using points / previewPoint / willClose */}
  </div>
)

Public API

  • CutoutViewer
    • Props: mainImage, mainImageAlt, effect, enabled, showAll, alphaThreshold, hoverLeaveDelay, onHover, onActiveChange, onSelect
  • CutoutViewer.Cutout — image-based cutout (alpha hit-testing)
  • CutoutViewer.BBoxCutout — bounding-box cutout (rectangular region)
  • CutoutViewer.PolygonCutout — polygon cutout (arbitrary closed shape)
  • CutoutViewer.DrawPolygon — interactive polygon drawing overlay
    • Props: onComplete, enabled, strokeColor, minPoints, closeThreshold, style, className
  • CutoutViewer.Overlay
    • Props: placement, className, style
  • useCutout()
    • Read nearest cutout state (id, bounds, isActive, isHovered, isSelected, effect)
  • useDrawPolygon(options)
    • Headless hook; returns points, previewPoint, willClose, reset, containerRef, containerProps
  • hoverEffects
    • Built-in presets: elevate, glow, lift, subtle, trace, shimmer
  • defineKeyframes(name, css) — helper for declaring CSS @keyframes in custom effects

How It Works

  • Image cutouts are loaded into an offscreen canvas; opaque bounds and alpha data are computed once for pixel-level hit-testing.
  • BBox cutouts use simple point-in-rect checks against normalized coordinates.
  • Polygon cutouts use a ray-casting algorithm for point-in-polygon testing.
  • Pointer positions are normalized to the container and hit-tested from front to back.
  • Click locks selection; clicking empty space clears selection.
  • Overlays are positioned from normalized cutout bounds using one of 9 placements.

Important Implementation Notes

  • Use transparent PNG/WebP cutouts aligned to the same coordinate space as mainImage.
  • Best results come from matching cutout resolution to the base image resolution.
  • BBox and Polygon cutouts do not require any image — they are defined purely by coordinates.
  • Geometry cutouts (BBox/Polygon) are invisible when idle and appear on hover/selection, unlike image cutouts which blend naturally with the background.
  • If a cutout image cannot be read from canvas (for example, CORS restrictions), hit testing for that cutout gracefully falls back and does not crash.
  • The component does not require Tailwind to render correctly.

Effects

Pass a preset name or a fully custom HoverEffect object to the effect prop.

Built-in presets

| Preset | Description | |--------|-------------| | elevate | Lifts the hovered cutout with a blue glow and deep shadow | | glow | Warm glow around the hovered cutout, no lift | | lift | Strong lift with deep shadow, no color glow | | subtle | Minimal — dims non-hovered cutouts with no animation | | trace | A white dash continuously traces the cutout border | | shimmer | style brightness flash that sweeps over the hovered subject |

Custom static effects

import { CutoutViewer, type HoverEffect } from "react-img-cutout"

const customEffect: HoverEffect = {
  name: "neon",
  transition: "all 0.4s ease",
  mainImageHovered: { filter: "brightness(0.2) grayscale(1)" },
  vignetteStyle: { background: "rgba(0,0,0,0.45)" },
  cutoutActive: { transform: "scale(1.03)", opacity: 1 },
  cutoutInactive: { transform: "scale(1)", opacity: 0.35 },
  cutoutIdle: { transform: "scale(1)", opacity: 1 },
}

Custom animated effects

Use defineKeyframes to declare CSS @keyframes that the viewer injects automatically. Reference the keyframe name in any animation property.

import { defineKeyframes, type HoverEffect } from "react-img-cutout"

const pulse = defineKeyframes("my-pulse", `
  0%, 100% { transform: scale(1);    filter: brightness(1);    }
  50%      { transform: scale(1.06); filter: brightness(1.15); }
`)

const pulseEffect: HoverEffect = {
  name: "pulse",
  transition: "all 0.4s ease",
  keyframes: [pulse],
  mainImageHovered: { filter: "brightness(0.3)" },
  vignetteStyle: { background: "rgba(0,0,0,0.4)" },
  cutoutActive: {
    animation: `${pulse.name} 1.2s ease-in-out infinite`,
    opacity: 1,
  },
  cutoutInactive: { opacity: 0.3 },
  cutoutIdle: { opacity: 1 },
}

Geometry cutouts (BBox / Polygon) also support strokeDasharray and animation on their geometryActive style for SVG-level animations like the built-in trace effect.

Local Development

  • npm run storybook for interactive component testing
  • npm run lint for lint checks
  • npm run build:lib to generate dist/ for npm