@ozjsey/v-observe

Vue 3 directive owning IntersectionObserver + ResizeObserver + MutationObserver in one binding — cross-observer gating, threshold-crossed events, diff payloads, unified state attribute
771
Install
npm install @ozjsey/v-observe
Latest Version:0.2.2
Vue:3
License:MIT
Last Updated:Sep 19, 2026
Links: GitHub  ·  npm
Maintainer: ozjsey

@ozjsey/v-observe

IntersectionObserver + ResizeObserver + MutationObserver in one Vue 3 binding, with the diffs, thresholds and debounces you would otherwise write by hand.

npm

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.

CHANGELOG.md · ARCHITECTURE.md

License

MIT

Related Packages

vue-on-intersect

Intersection Observer for Vue JS

6,109 0
vue-scroll

scroll directive for vuejs 2.0

345,040 242
@centaurea/vue3-scroll

vue 3 version of vue-scroll

2,682 1
v-scroll-threshold

A vue directive that accepts a threshold value and a callback function that tell...

31,165 1
vue3-chat-scroll

Vue directive to keep things scrolled to the bottom

20,446 1

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