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
- Host: GitHub
- URL: https://github.com/ocramz/htmx-intersect
- Owner: ocramz
- License: mit
- Created: 2026-02-16T08:52:15.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-02-16T09:39:05.000Z (4 months ago)
- Last Synced: 2026-03-25T00:24:52.368Z (3 months ago)
- Topics: htmx-extension, intersection-observer, scrollytelling
- Language: HTML
- Homepage:
- Size: 59.6 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# htmx-intersect
[](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
```
### 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

```
### 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
Ad content (removed 5s after leaving viewport)
```
## 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)