An open API service indexing awesome lists of open source software.

https://github.com/ocramz/htmx-intersect

HTMX extension for IntersectionObserver
https://github.com/ocramz/htmx-intersect

htmx-extension intersection-observer scrollytelling

Last synced: 2 months ago
JSON representation

HTMX extension for IntersectionObserver

Awesome Lists containing this project

README

          

# htmx-intersect

[![Playwright Tests](https://github.com/ocramz/htmx-intersect/actions/workflows/test.yml/badge.svg)](https://github.com/ocramz/htmx-intersect/actions/workflows/test.yml)

A lightweight HTMX extension that integrates the Intersection Observer API to simplify scroll-based web experiences like lazy loading, infinite scroll, and visibility tracking.

## Features

- πŸš€ **Simple API** - Just add attributes to your HTML
- 🎯 **Lazy Loading** - Load content only when visible
- ♾️ **Infinite Scroll** - Automatically load more content
- πŸ‘οΈ **Visibility Tracking** - Track when elements enter/exit viewport
- 🎨 **No Dependencies** - Works with vanilla HTMX
- ⚑ **Performant** - Uses native IntersectionObserver API
- πŸ”„ **Reusable Observers** - Shares observers across elements with same config

## Installation

### Via CDN

```html

```

### Via npm

```bash
npm install htmx-intersect
```

```javascript
import 'htmx-intersect';
```

## Quick Start

### Basic Usage

```html


This content will load when scrolled into view

```

### Lazy Loading Images

```html
Lazy loaded image
```

### Infinite Scroll

```html




Loading more...

```

## Configuration Attributes

### Core Attributes

#### `hx-trigger="intersect"`
Triggers an HTMX request when the element intersects with the viewport.

**Modifiers:**
- `once` - Trigger only the first time (perfect for lazy loading)
- Example: `hx-trigger="intersect once"`

### Extension-Specific Attributes

#### `intersect-root`
Specifies the root element to observe intersection against. If not set or set to `null` or `viewport`, uses the browser viewport.

```html


Observes intersection within #scrollContainer

```

#### `intersect-threshold`
The percentage of the element that must be visible to trigger. Can be:
- Single value: `0.5` (50% visible)
- Multiple values: `0,0.25,0.5,0.75,1` (triggers at each threshold)

```html


```

**Default:** `0` (triggers as soon as any pixel is visible)

#### `intersect-margin`
Margin around the root element (similar to CSS margin). Positive values expand the root's area, negative values shrink it.

```html


```

**Default:** `"0px"`

#### `intersect-scroll-margin`
Margin around nested scroll containers. Useful when you have scrollable elements within the root.

```html


```

**Default:** `"0px"`

#### `intersect-unload`
Controls whether and how to unload/remove elements when they exit the viewport. Great for memory management in infinite scroll scenarios.

**Values:**
- `"true"` or `"remove"` - Completely remove element from DOM
- `"content"` - Remove only innerHTML, keep element shell (content restored on re-entry)
- `"hide"` - Set `display: none` (faster than removal)
- `"false"` or omit - No unloading (default)

```html


```

**Default:** Not set (no unloading)

#### `intersect-unload-delay`
Delay in milliseconds before unloading. Prevents flickering when scrolling quickly.

```html


```

**Default:** `0` (immediate)

#### `intersect-unload-placeholder`
HTML to show when using `intersect-unload="content"`. Only used with content mode.

```html


```

## Events

The extension emits custom events you can listen to:

### `intersect:enter`
Fired when element enters the viewport.

```javascript
element.addEventListener('intersect:enter', (event) => {
console.log('Element entered!', event.detail);
// detail: { ratio, time, bounds }
});
```

### `intersect:exit`
Fired when element exits the viewport.

```javascript
element.addEventListener('intersect:exit', (event) => {
console.log('Element exited!', event.detail);
// detail: { ratio, time }
});
```

### `intersect:visible`
Continuously fired with visibility updates.

```javascript
element.addEventListener('intersect:visible', (event) => {
console.log('Visibility ratio:', event.detail.ratio);
// detail: { ratio, isIntersecting }
});
```

### `intersect:beforeunload`
Fired before an element is unloaded (when using `intersect-unload`). Can be prevented.

```javascript
element.addEventListener('intersect:beforeunload', (event) => {
console.log('About to unload', event.detail.mode);
// Prevent unloading if needed
if (shouldKeepElement) {
event.preventDefault();
}
});
```

### `intersect:unload`
Fired after an element is unloaded. Fired on the parent element.

```javascript
parent.addEventListener('intersect:unload', (event) => {
console.log('Element unloaded:', event.detail.element);
// detail: { mode, element }
});
```

## Use Cases & Examples

### 1. Lazy Loading Components

```html


Loading...


```

### 2. Infinite Scroll with Loading Indicator

```html





Loading more posts...


```

### 3. Analytics Tracking

```html


Article content here

```

### 4. Progressive Image Loading

```html

Photo

```

### 5. Video Autoplay on Scroll

```html

```

### 6. Sticky Header Detection

```html


Header content

```

### 7. Content Loading in Scrollable Container

```html



Nested scrollable content


```

### 8. Memory-Efficient Infinite Scroll (Unload Off-Screen Content)

```html


Loading more...


Post content here...

```

### 9. Virtual Scrolling with Content Unloading

```html


Heavy content here...

```

### 10. Remove Ads After Viewing

```html


```

## Advanced Usage

### Manual Observer Control

For complex scenarios, you can use the JavaScript API:

```javascript
// Start observing an element
htmx.intersect.observe(element);

// Stop observing
htmx.intersect.unobserve(element);

// Create custom observer
const observer = htmx.intersect.createObserver(
{
root: null,
rootMargin: '0px',
threshold: [0, 0.5, 1]
},
(entries) => {
entries.forEach(entry => {
console.log('Intersection:', entry);
});
}
);

observer.observe(element);
```

### Combining with Other HTMX Features

```html


Start polling when visible


Connect to WebSocket when visible


Animated content

```

## CSS Classes

The extension automatically adds/removes the `intersecting` class:

```css
.my-element {
opacity: 0;
transform: translateY(50px);
transition: all 0.6s ease;
}

.my-element.intersecting {
opacity: 1;
transform: translateY(0);
}
```

## Browser Support

Works in all browsers that support:
- HTMX 1.9+
- IntersectionObserver API (all modern browsers)

For older browsers, consider using a [polyfill](https://github.com/w3c/IntersectionObserver/tree/main/polyfill).

## Performance Considerations

1. **Use `once` modifier** for one-time loads to automatically clean up observers
2. **Set appropriate thresholds** - Don't use too many threshold values
3. **Use root margins wisely** - Preload content just before it's needed
4. **Shared observers** - Elements with identical configs share observers

## Troubleshooting

### Element not triggering
- Ensure `hx-trigger="intersect"` is set
- Check that element has non-zero dimensions
- Verify `intersect-threshold` is appropriate
- Check browser console for errors

### Multiple triggers
- Add `once` modifier: `hx-trigger="intersect once"`
- Or use `intersect-threshold` to be more specific

### Not working in nested scrollers
- Set `intersect-root` to the scroll container
- Consider using `intersect-scroll-margin`

## Contributing

Contributions welcome! Please open an issue or PR.

## License

MIT License - see LICENSE file for details

## Credits

Built with ❀️ for the HTMX community

- [HTMX](https://htmx.org/)
- [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)