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

alpha-mask-events

v1.1.1

Published

Enable click‑through on transparent regions of images (PNG, WebP, AVIF, GIF, SVG) using alpha masks.

Readme

Alpha Mask Events

npm version CI License: MIT Node

Make clicks fall through the transparent parts of an image and only land on the opaque pixels — so irregularly shaped PNG/WebP/AVIF elements behave naturally, without hand-authored CSS clip-path or SVG masks.

Why use Alpha Mask Events?

Highlights

  • 🪶 Zero runtime dependencies in the browser bundle (~3 KB min). The native canvas package is only needed for the optional CLI.
  • 🖼️ Modern formats — PNG, WebP, AVIF, SVG, plus graceful handling of GIF/JPEG/BMP/TIFF.
  • 🎯 Per-element thresholds and live global threshold adjustment.
  • 🔔 Custom events (alpha-mask-over / alpha-mask-out) for hover feedback.
  • 🔄 Auto-detection of new/removed .alpha-mask-events elements via MutationObserver.
  • Performance-awarerequestAnimationFrame throttling, ResizeObserver, IntersectionObserver, and a shared image cache.
  • 🧭 CSS-transform aware — handles rotated/scaled/skewed elements.
  • 📦 Ships ESM, CJS, and UMD builds with TypeScript types.

Table of Contents

Installation

npm install alpha-mask-events
# or
yarn add alpha-mask-events
# or
pnpm add alpha-mask-events

The browser library needs no native build. canvas is declared as an optional dependency and is required only if you use the ame-generate-masks CLI. If canvas fails to install on your platform, the library still works perfectly in the browser.

CDN (no build step)

<script src="https://cdn.jsdelivr.net/npm/alpha-mask-events@1/dist/alpha-mask-events.umd.min.js"></script>
<script>
  AlphaMaskEvents.init(); // scans and activates every .alpha-mask-events element
</script>

Browse versions on jsDelivr.

Quick Start

1. Auto-detect (recommended)

Add the alpha-mask-events class to any <img> or element with a background-image:

<img src="logo.png"   class="alpha-mask-events" />
<img src="hero.webp"  class="alpha-mask-events" />
<img src="avatar.avif" class="alpha-mask-events" />
<div class="alpha-mask-events" style="background-image: url('shape.png')"></div>
import AME from 'alpha-mask-events';

AME.init(); // auto-detects all .alpha-mask-events elements (now and in the future)

2. Manual registration

import AME from 'alpha-mask-events';

AME.init({ autoScan: false });          // initialize without scanning
AME.register('#logo');                   // by selector
AME.register(document.querySelector('.irregular-button'), { threshold: 0.95 });

How It Works

  1. For each registered element, an off-screen canvas is created and the image (respecting background-size/background-position) is drawn into it.
  2. On pointer movement (throttled with requestAnimationFrame), the pixel under the cursor is sampled with getImageData, mapping screen → canvas coordinates and accounting for any CSS transform.
  3. If the sampled alpha is above the threshold the element becomes interactive (pointer-events: auto); otherwise clicks pass through (pointer-events: none).
  4. Transitions across the opaque/transparent boundary dispatch alpha-mask-over / alpha-mask-out events.

Cross-origin images are supported when served with proper CORS headers (crossorigin="anonymous"); if a canvas becomes tainted, a conservative geometric fallback is used instead of throwing.

Supported Image Formats

| Format | Transparency | Browser support | Notes | | ------ | ------------ | --------------- | ----- | | PNG | ✅ Full alpha | Universal | Best general choice | | WebP | ✅ Full alpha | Chrome 23+, Firefox 65+, Safari 14+ | | | AVIF | ✅ Full alpha | Chrome 85+, Firefox 93+, Safari 16.4+ | | | SVG | ✅ Via opacity/CSS | Modern | | | GIF | ⚠️ Binary only | Universal | On/off transparency | | TIFF / ICO | ⚠️ Limited | Limited | | | JPEG / BMP | ❌ None | Universal / Limited | Registered with a console warning |

API Reference

The default export and named exports are equivalent:

import AME from 'alpha-mask-events';
// or: import { init, register, unregister, setThreshold } from 'alpha-mask-events';

init(options?) → Manager

| Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | threshold | number | 0.999 | Alpha cutoff (0–1). Pixels with alpha > threshold are opaque/clickable. | | autoScan | boolean | true | Auto-detect .alpha-mask-events elements. | | log | boolean | false | Verbose console logging for debugging. | | useIntersectionObserver | boolean | true | Skip hit-testing for off-screen elements. | | intersectionRootMargin | string | '100px' | Root margin for the IntersectionObserver. |

Returns the singleton Manager instance.

register(target, opts?)

Register an element or CSS selector for hit-testing.

  • targetHTMLElement | string
  • opts.thresholdnumber (per-element override of the global threshold)

unregister(target)

Stop hit-testing and restore the element's original pointer-events.

setThreshold(value)

Adjust the global threshold (0–1); applies to all registered elements.

Custom Events

Registered elements dispatch events as the cursor crosses the opaque/transparent boundary:

  • alpha-mask-over — cursor entered an opaque region.
  • alpha-mask-out — cursor left an opaque region (or the element).

Each event's detail contains:

| Property | Type | Description | | -------- | ---- | ----------- | | element | HTMLElement | The element that fired the event | | alpha | number | Sampled alpha (0–1) at the cursor | | coordinates | { x: number, y: number } | Canvas-space coordinates | | threshold | number | Threshold used for this element |

const el = document.querySelector('.my-image');
el.addEventListener('alpha-mask-over', (e) => el.classList.add('hover-opaque'));
el.addEventListener('alpha-mask-out',  (e) => el.classList.remove('hover-opaque'));

TypeScript

import AME, { AlphaMaskEvent } from 'alpha-mask-events';

const el = document.querySelector('.my-image') as HTMLElement;
el.addEventListener('alpha-mask-over', (event: AlphaMaskEvent) => {
  const { alpha, coordinates, threshold } = event.detail;
  console.log(`alpha=${alpha} at ${coordinates.x},${coordinates.y} (>${threshold})`);
});

Framework Integration

React

import { useEffect, useRef } from 'react';
import AME from 'alpha-mask-events';

function TransparentButton({ imageUrl, onClick, threshold = 0.8 }) {
  const ref = useRef(null);

  useEffect(() => {
    AME.register(ref.current, { threshold });
    return () => AME.unregister(ref.current);
  }, [threshold]);

  return (
    <button
      ref={ref}
      onClick={onClick}
      style={{ backgroundImage: `url(${imageUrl})`, backgroundSize: 'contain', border: 'none' }}
    />
  );
}

Vue 3

<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
import AME from 'alpha-mask-events';

const props = defineProps({ imageUrl: String, threshold: { type: Number, default: 0.8 } });
const btn = ref(null);

onMounted(() => AME.register(btn.value, { threshold: props.threshold }));
onBeforeUnmount(() => AME.unregister(btn.value));
</script>

<template>
  <button ref="btn" :style="{ backgroundImage: `url(${imageUrl})` }"><slot /></button>
</template>

Lazy registration with IntersectionObserver

import AME from 'alpha-mask-events';
AME.init({ threshold: 0.8 });

const io = new IntersectionObserver((entries) => {
  entries.forEach((e) => (e.isIntersecting ? AME.register(e.target) : AME.unregister(e.target)));
}, { rootMargin: '100px' });

document.querySelectorAll('.alpha-mask-events').forEach((el) => io.observe(el));

More patterns (product customizers, interactive infographics, maps) live in demo/examples.md.

CLI: Pre-generating Masks

For server-side hit-testing or to avoid decoding images at runtime, pre-compute compact opaque-region masks with the bundled CLI.

npx ame-generate-masks <images...> --out masks.json [--threshold 0.1] [--blur 1]

| Option | Default | Description | | ------ | ------- | ----------- | | <images...> | — | One or more image paths or glob patterns | | --out | (required) | Output JSON file | | --threshold | 0.1 | Alpha threshold; pixels above are opaque | | --blur | 1 | Box-blur radius applied to alpha before thresholding |

npx ame-generate-masks sprites/*.png --out masks.json
npx ame-generate-masks logo.png hero.webp avatar.avif --out masks.json --threshold 0.2

Output:

{
  "logo.png": {
    "width": 256,
    "height": 256,
    "rects": [{ "x": 10, "y": 10, "w": 50, "h": 1 }]
  }
}

Installing the CLI's native dependency

The CLI decodes images with node-canvas, which needs system libraries. The library prints these instructions if canvas is missing — install them, then run npm install canvas:

| OS | Command | | -- | ------- | | Debian/Ubuntu | sudo apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev | | macOS (Homebrew) | brew install pkg-config cairo pango libpng jpeg giflib librsvg | | Windows | See the node-canvas Windows guide |

Browser Compatibility

Chrome 50+ · Firefox 50+ · Safari 11+ · Edge 18+ · iOS Safari 11+ · Android 76+.

ResizeObserver, IntersectionObserver, and PointerEvent are used when available; provide polyfills for very old browsers.

Performance Tips

  1. Image caching is automatic — elements sharing a src share one cached Image.
  2. Pick a sensible threshold — higher = fewer clickable pixels.
  3. Size images to display dimensions — smaller canvases sample faster.
  4. Unregister removed elementsAME.unregister(el) (or rely on the MutationObserver).
  5. Pre-generate masks with the CLI for static images.

For AI Agents

This repo is set up to be agent-friendly:

  • AGENTS.md — architecture map, conventions, and the exact commands to build/test/lint. Start there.
  • CLAUDE.md — Claude Code–specific guidance.
  • In a fresh environment, run npm install --ignore-scripts to get a working toolchain without building the native canvas module (it's optional and only used by the CLI), then npm run lint && npm run build && npm test.

Quick map of the public surface:

| Symbol | File | Purpose | | ------ | ---- | ------- | | init / register / unregister / setThreshold | src/index.js | Public API | | Manager | src/manager.js | Hit-testing, observers, event dispatch | | ame-generate-masks | bin/generate-masks.js | Offline mask generation CLI | | Types | types/index.d.ts | TypeScript definitions |

Project Layout

src/         Browser library (ESM source; bundled to dist/ by Rollup)
bin/         ame-generate-masks CLI (Node)
types/       TypeScript declarations
__tests__/   Jest tests (jsdom for the browser, node for the CLI)
demo/        Interactive demo (deployed to GitHub Pages)
dist/        Build output (generated; not committed)

Development

npm install          # install deps (canvas is optional; safe to skip building it)
npm run build        # bundle ESM/CJS/UMD via Rollup
npm test             # run the Jest suite
npm run lint         # ESLint (src + bin)

Run a single test file:

node --experimental-vm-modules ./node_modules/jest/bin/jest.js __tests__/manager.test.js

CLI integration tests automatically skip when the native canvas module is unavailable, so the suite stays green on every OS. CI runs lint + build + test on Linux, macOS, and Windows across Node 18/20/22.

Contributing

Contributions welcome! Please read CONTRIBUTING.md, open an issue to discuss substantial changes, and make sure npm run lint, npm run build, and npm test all pass before submitting a PR.

License

MIT © Daniel Morgan