alpha-mask-events
Version:
Enable click‑through on transparent regions of images (PNG, WebP, AVIF, GIF, SVG) using alpha masks.
576 lines (436 loc) • 16.2 kB
Markdown
# Alpha Mask Events
[](https://www.npmjs.com/package/alpha-mask-events)
[](https://opensource.org/licenses/MIT)
[](https://github.com/themorgantown/alpha-mask-events)
> Enable click-through on transparent parts of images (PNG, WebP, AVIF, etc.) and background-images, making irregularly shaped UI elements behave naturally.

## Why use Alpha Mask Events?
Ever been frustrated when:
- You had to use CSS clip-paths or complex SVG masks to make irregularly shaped elements clickable only on their visible parts?
- You wanted to stack elements and have clicks "fall through" the transparent regions?
- You want greater control over pointer events and transparent images?
This lightweight library solves these problems with minimal setup and supports all modern image formats with transparency.
## Supported Image Formats
### ✅ Full Transparency Support
- **PNG** - Universal browser support, full alpha channel
- **WebP** - Modern browsers (Chrome 23+, Firefox 65+, Safari 14+), full alpha channel
- **AVIF** - Latest browsers (Chrome 85+, Firefox 93+, Safari 16.4+), full alpha channel
- **SVG** - Modern browsers, transparency via CSS/opacity
### ⚠️ Limited Transparency Support
- **GIF** - Universal support, binary transparency only
- **TIFF** - Limited browser support, some transparency capability
- **ICO** - Limited browser support, basic transparency
### ❌ No Transparency (processed with warnings)
- **JPEG/JPG** - Universal support, no transparency
- **BMP** - Limited support, no transparency
## Table of Contents
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Use Cases](#use-cases)
- [API Reference](#api-reference)
- [CLI Usage](#cli-usage)
- [Advanced Examples](#advanced-examples)
- [Browser Compatibility](#browser-compatibility)
- [Performance Tips](#performance-tips)
- [Development](#development)
- [Testing](#testing)
- [Contributing](#contributing)
- [License](#license)
## Installation
```bash
npm install alpha-mask-events
# or
yarn add alpha-mask-events
```
## Quick Start
### Method 1: Auto-detect images (recommended)
Add `.alpha-mask-events` class to any image or element with background-image:
```html
<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>
<div class="alpha-mask-events" style="background-image: url('icon.webp')"></div>
```
Initialize the library:
```js
import AME from 'alpha-mask-events';
// Initialize and auto-detect all elements with .alpha-mask-events class
AME.init();
```
### Method 2: Manual Registration
```js
import AME from 'alpha-mask-events';
// Initialize without auto-detection
const manager = AME.init({ autoScan: false });
// Register specific elements
AME.register('#logo');
AME.register(document.querySelector('.irregular-button'));
```
## CDN via JsDelivr
Grab the `<script>` code here: https://www.jsdelivr.com/package/npm/alpha-mask-events
JS:
```
AlphaMaskEvents.init(); // Scan and activate all .alpha-mask-events
AlphaMaskEvents.register(document.querySelector('#myImg'), { threshold: 0.95 });
```
HTML:
```
<img src="sprite.png" class="alpha-mask-events">
```
## Use Cases
### Interactive Maps with Irregularly Shaped Regions
```html
<div class="map-container">
<img src="map-background.jpg" style="width: 100%; height: auto;" />
<img src="region1.png" class="alpha-mask-events region" data-region="north" />
<img src="region2.png" class="alpha-mask-events region" data-region="south" />
</div>
<script>
import AME from 'alpha-mask-events';
// Initialize click-through behavior
AME.init();
// Add click handlers to regions
document.querySelectorAll('.region').forEach(region => {
region.addEventListener('click', () => {
alert(`You clicked the ${region.dataset.region} region!`);
});
});
</script>
```
### Dynamic UI with Overlapping Elements
```js
// Create a layered UI where clicks pass through transparent areas
const layers = document.querySelectorAll('.ui-layer');
layers.forEach(layer => AME.register(layer));
// Dynamically adjust threshold based on user interaction
document.getElementById('sensitivity-slider').addEventListener('change', (e) => {
AME.setThreshold(e.target.value / 100);
});
```
### Shape-Conforming Buttons
Make clickable areas match the actual visible shape of buttons:
```html
<button class="shaped-button alpha-mask-events">
<img src="button-shape.png" alt="Custom Button" />
<span class="button-text">Click Me</span>
</button>
<style>
.shaped-button {
position: relative;
background: transparent;
border: none;
padding: 0;
}
.button-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
pointer-events: none; /* Let clicks go through to the image */
}
</style>
```
## API Reference
### init(options)
Initialize click-through manager on the page.
- **options** (object)
- **threshold** (number, optional): Transparency cutoff (0–1). Pixels with alpha ≤ threshold are click-through. Default: `0.999`
- **autoScan** (boolean, optional): Auto-detect elements with `.alpha-mask-events` class. Default: `true`
- **log** (boolean, optional): Enable console logs for debugging. Default: `false`
Returns the manager instance.
### register(target, opts)
Register an element or selector for click-through.
- **target** (HTMLElement|string): Element or CSS selector
- **opts** (object, optional)
- **threshold** (number, optional): Per-element transparency cutoff. Default: global threshold
### unregister(target)
Stop hit-testing and restore normal pointer behavior on the element.
- **target** (HTMLElement|string): Element or CSS selector
### setThreshold(value)
Adjust global transparency threshold.
- **value** (number): New threshold (0–1), where lower values make more pixels click-through
## Custom Events
Alpha Mask Events dispatches custom events when the mouse cursor transitions between opaque and transparent regions of registered elements.
### alpha-mask-over
Fired when the cursor moves from a transparent region to an opaque region (or enters an opaque region from outside the element).
### alpha-mask-out
Fired when the cursor moves from an opaque region to a transparent region (or leaves an opaque region by exiting the element).
### Event Object Properties
Both events include a `detail` object with the following properties:
- **element** (HTMLElement): The element that triggered the event
- **alpha** (number): The alpha value (0-1) at the cursor position
- **coordinates** (object): Canvas coordinates `{ x: number, y: number }` where the event occurred
- **threshold** (number): The threshold value used for this element
### Usage Example
```javascript
import AME from 'alpha-mask-events';
// Initialize and register an element
AME.init();
const element = document.querySelector('.my-image');
// Listen for opacity transition events
element.addEventListener('alpha-mask-over', (event) => {
console.log('Mouse entered opaque region');
console.log('Alpha value:', event.detail.alpha);
console.log('Coordinates:', event.detail.coordinates);
console.log('Threshold:', event.detail.threshold);
// Add visual feedback
element.classList.add('hover-opaque');
});
element.addEventListener('alpha-mask-out', (event) => {
console.log('Mouse left opaque region');
// Remove visual feedback
element.classList.remove('hover-opaque');
});
```
### TypeScript Support
```typescript
import AME, { AlphaMaskEvent } from 'alpha-mask-events';
const element = document.querySelector('.my-image') as HTMLElement;
element.addEventListener('alpha-mask-over', (event: AlphaMaskEvent) => {
// TypeScript knows about event.detail properties
const { alpha, coordinates, threshold, element } = event.detail;
console.log(`Alpha: ${alpha}, Coords: ${coordinates.x},${coordinates.y}`);
});
```
## CLI Usage
Generate compact masks for opaque regions in transparent images (useful for server-side optimizations).
```bash
npx ame-generate-masks <images...> --out <file> [options]
```
**Supported formats**: PNG, WebP, AVIF, GIF, BMP, TIFF
**Examples:**
```bash
# Process multiple PNG files
npx ame-generate-masks sprites/*.png --out masks.json
# Process mixed formats with custom threshold
npx ame-generate-masks logo.png hero.webp avatar.avif --out masks.json --threshold 0.2
# Process with blur for smoother edges
npx ame-generate-masks button.png --out button-mask.json --blur 2
```
**Options:**
- **<images...>**: One or more image paths or glob patterns
- **--out** (string, required): Path to output JSON file
- **--threshold** (number, default: `0.1`): Alpha threshold (0–1). Pixels with alpha > threshold are included in the opaque mask
- **--blur** (number, default: `1`): Box blur radius in pixels applied to alpha channel before thresholding
**Output format:**
```json
{
"logo.png": {
"width": 256,
"height": 256,
"rects": [
{ "x": 10, "y": 10, "w": 50, "h": 1 },
{ "x": 8, "y": 11, "w": 54, "h": 1 }
]
},
"hero.webp": {
"width": 800,
"height": 600,
"rects": [...]
}
}
```
## Advanced Examples
### React Integration
```jsx
import React, { useEffect, useRef } from 'react';
import AME from 'alpha-mask-events';
function TransparentButton({ imageUrl, onClick, threshold = 0.8 }) {
const buttonRef = useRef(null);
useEffect(() => {
if (buttonRef.current) {
// Register the element when component mounts
AME.register(buttonRef.current, { threshold });
// Clean up when component unmounts
return () => {
AME.unregister(buttonRef.current);
};
}
}, [threshold]);
return (
<button
ref={buttonRef}
onClick={onClick}
style={{
backgroundImage: `url(${imageUrl})`,
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
border: 'none',
padding: '20px'
}}
/>
);
}
// Usage
function App() {
useEffect(() => {
AME.init({ log: process.env.NODE_ENV === 'development' });
}, []);
return (
<div>
<TransparentButton
imageUrl="/irregular-button.png"
onClick={() => console.log('Button clicked!')}
threshold={0.5}
/>
</div>
);
}
```
### Vue Integration
```vue
<template>
<button
ref="transparentBtn"
class="transparent-btn"
@click="handleClick"
:style="{ backgroundImage: `url(${imageUrl})` }"
>
<slot></slot>
</button>
</template>
<script>
import AME from 'alpha-mask-events';
export default {
props: {
imageUrl: String,
threshold: {
type: Number,
default: 0.8
}
},
mounted() {
// Initialize AME if this is the first component
if (!this.$AME) {
this.$AME = AME.init({ log: process.env.NODE_ENV === 'development' });
}
// Register this button
AME.register(this.$refs.transparentBtn, { threshold: this.threshold });
},
beforeDestroy() {
AME.unregister(this.$refs.transparentBtn);
},
methods: {
handleClick() {
this.$emit('click');
}
}
}
</script>
<style scoped>
.transparent-btn {
background-size: contain;
background-repeat: no-repeat;
border: none;
padding: 20px;
cursor: pointer;
}
</style>
```
### Dynamic Content and Intersection Observer
For dynamic content or long pages, combine with IntersectionObserver for better performance:
```js
import AME from 'alpha-mask-events';
// Initialize
AME.init({ threshold: 0.8 });
// Setup IntersectionObserver to only process visible elements
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const el = entry.target;
if (entry.isIntersecting) {
// Element is visible, register it
AME.register(el);
} else {
// Element is off-screen, unregister to save resources
AME.unregister(el);
}
});
}, { rootMargin: '100px' });
// Observe all transparency-enabled elements
document.querySelectorAll('.alpha-mask-events').forEach(el => {
observer.observe(el);
});
```
## Browser Compatibility
- Chrome 50+
- Firefox 50+
- Safari 11+
- Edge 18+
- iOS Safari 11+
- Android Browser 76+
Polyfill required for:
- `ResizeObserver` (for older browsers)
- `PointerEvent` (falls back to `touch` events on older mobile browsers)
## Performance Tips
1. **Automatic Image Caching**:
- The library automatically caches loaded images to avoid redundant network requests
- Multiple elements using the same image source share a single cached `Image` object
- Cache persists for the entire page session, providing instant registration for repeated images
- Memory efficient: images are cached by URL, not duplicated per element
2. **Use appropriate threshold values**:
- Higher values (closer to 1.0) make fewer pixels click-through
- Lower values (closer to 0.0) make more pixels click-through
3. **Optimize image sizes**:
- Large images take longer to process
- Consider resizing images to actual displayed dimensions
4. **Unregister elements when not needed**:
- Use `AME.unregister()` for elements being removed from DOM
5. **Use the CLI tool for static masks**:
- For static images, pre-generate mask data
- Load JSON masks instead of analyzing images at runtime
## How to Create a Mask Using the CLI
To generate a mask for an image using the CLI tool, use the `npx ame-generate-masks` command. This will analyze the transparency of your image and output a JSON mask file describing the opaque regions.
### Example: Generate a Mask for a PNG Image
```bash
npx ame-generate-masks logo.png --out logo-mask.json
```
This command processes `logo.png` and writes the mask data to `logo-mask.json`.
### Example: Custom Threshold and Blur
```bash
npx ame-generate-masks hero.webp --out hero-mask.json --threshold 0.2 --blur 2
```
- `--threshold 0.2`: Pixels with alpha > 0.2 are considered opaque (included in mask).
- `--blur 2`: Applies a box blur of radius 2 pixels to the alpha channel before thresholding.
### Example: Multiple Images
```bash
npx ame-generate-masks sprites/*.png --out masks.json
```
Processes all PNG files in the `sprites` directory and outputs a combined mask file.
### Output Format
The output JSON contains the dimensions and rectangles for each image's opaque regions:
```json
{
"logo.png": {
"width": 256,
"height": 256,
"rects": [
{ "x": 10, "y": 10, "w": 50, "h": 1 },
{ "x": 8, "y": 11, "w": 54, "h": 1 }
]
}
}
```
**Tip:** Use the generated mask file for server-side hit-testing or to optimize client-side performance by loading precomputed mask data.
## Development
- **Build**: `npm run build` (uses Rollup)
- **Test**: `npm test` (Jest)
- **Lint**: `npm run lint`
## Testing
The library includes comprehensive tests to ensure reliability:
### Browser Library Tests (`manager.test.js`)
- **Element Registration**: Verifies elements can be added to and removed from the registry
- **Threshold Management**: Tests that `setThreshold` updates all registered elements
- **Auto-scanning**: Confirms that `.alpha-mask-events` elements are automatically detected
- **Event Handling**: Validates pointer event handling infrastructure
### CLI Tool Tests (`generate-masks.test.js`)
- **Mask Generation**: Tests creation of JSON-based rectangle masks from PNG transparency
- **Output Validation**: Ensures generated masks contain proper width, height and rectangle data
- **Error Handling**: Verifies appropriate error when no images are provided
- **User Feedback**: Confirms proper success messages are displayed
Run all tests with `npm test`.
## Contributing
Contributions welcome! Please open an issue or pull request on GitHub.
## License
MIT