@ozjsey/v-observe
| Install | |
|---|---|
npm install @ozjsey/v-observe |
|
| Latest Version: | 0.2.2 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Sep 19, 2026 |
| Links: | GitHub · npm |
@ozjsey/v-observe
IntersectionObserver + ResizeObserver + MutationObserver in one Vue 3 binding, with the
diffs, thresholds and debounces you would otherwise write by hand.
The problem
VueUse wraps the three observers separately — vIntersectionObserver, vResizeObserver,
useMutationObserver — each one a near-1:1 over the native API. A consumer who needs more than one signal
on the same element wires up more than one of them, and then writes the interesting part themselves: which
threshold was crossed and in which direction, what the box was before it changed, which children arrived,
and how to stop all of it firing while the element is off-screen.
The solution
One directive, three modes, one binding — and the integrations that are only possible when a single
directive owns all three signals. gateOnIntersect: true skips resize and mutate callbacks while the host
is off-screen. data-observe-state="intersect:…;resize:…;mutate:…" reflects all three at
once, so a CSS-only reveal or a
responsive grid needs no callback at all. Every mode reports a diff rather than a raw record: from /
to / delta for resize, per-threshold crossed with direction for intersect, semantic event types with
before/after values for mutate.
Neither crossed mode fires on its first callback. intersect.crossed and resize's
on: 'crossed' both need a previous measurement to have crossed from; an element that mounts 60%
visible has not been scrolled in, and saying so would make an infinite-scroll sentinel load page 2
during mount.
Install
npm install @ozjsey/v-observe
Requires Vue 3. Zero runtime dependencies. The plugin registers the directive under observe, so
templates use v-observe="…"; the published tarball also contains src/, if you would rather
vendor than install.
import { createApp } from 'vue'
import { ObservePlugin } from '@ozjsey/v-observe'
import App from './App.vue'
createApp(App).use(ObservePlugin).mount('#app')
Usage
Lazy-load — fire once when visible
<script setup lang="ts">
import { ref } from 'vue'
const loaded = ref(false)
</script>
<template>
<img
v-observe="{
intersect: {
once: true,
on: (e) => { if (e.isIntersecting) loaded = true },
},
}"
:src="loaded ? '/big.jpg' : '/placeholder.svg'"
/>
</template>
once: true disconnects the observer the first time the element intersects.
Responsive UI via breakpoint brackets
<script setup lang="ts">
import { ref } from 'vue'
import type { ResizeEvent } from '@ozjsey/v-observe'
const bracket = ref('xs')
function onResize(e: ResizeEvent) {
if (e.mode === 'tick' && e.bracket) bracket.value = e.bracket
}
</script>
<template>
<div
v-observe="{
resize: {
breakpoints: { xs: 0, sm: 320, md: 640, lg: 960 },
handler: onResize,
},
}"
class="grid"
>
Bracket: {{ bracket }}
</div>
</template>
<style scoped>
.grid[data-observe-state*='resize:md'] { grid-template-columns: repeat(3, 1fr); }
</style>
The bracket arrives on the event and on the state attribute, so the layout above changes with no JavaScript in the loop.
Watch children, but only while on screen
<script setup lang="ts">
import type { MutateEvent } from '@ozjsey/v-observe'
function onAdd(e: MutateEvent) {
if (e.added) e.added.forEach((el) => el.classList.add('is-new'))
}
</script>
<template>
<ul
v-observe="{
intersect: { thresholds: [0.5] },
mutate: { on: 'children:added', match: '.item', handler: onAdd, gateOnIntersect: true },
}"
>
<li class="item">…</li>
</ul>
</template>
No DOM-diff work happens while the list is scrolled away. Where IntersectionObserver does not
exist the gate stops gating rather than swallowing every event — every degradation here fails open.
Everything else
The v-observe playground tab is the reference: 17 cards, every option and every
event driven in a real scroller, each one editable in the browser.
- intersect —
once· multi-thresholdcrossed· direction · CSS-only ·root+rootMargin - resize — tick, with from/to/delta · brackets ·
on: 'crossed'·on: 'orientation'· box modes and debounce, wheredevice-pixelis the one a canvas needs and the one you cannot eyeball from a table - mutate — attribute diffs · children, filtered · text edits · self-removal · multi-type + debounce
- together — the gate · all three modes on one element, where the state attribute's three segments move independently
CHANGELOG.md · ARCHITECTURE.md
License
MIT
Related Packages
A vue directive that accepts a threshold value and a callback function that tell...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.2.2 | ^3.0.0 | MIT | |
| 0.2.1 | ^3.0.0 | MIT | |
| 0.2.0 | ^3.0.0 | MIT | |
| 0.1.0 | ^3.0.0 | MIT |