
useMutationObserver
A composable for detecting DOM changes using the Mutation Observer API with automatic cleanup.
Usage
The useMutationObserver composable wraps the Mutation Observer API to detect changes to the DOM tree. It’s useful for monitoring attribute changes, child element modifications, and character data updates.
<script setup>
import { useMutationObserver } from '@vuetify/v0'
import { ref, useTemplateRef } from 'vue'
const target = useTemplateRef('target')
const mutationCount = ref(0)
useMutationObserver(target, (mutations) => {
mutationCount.value += mutations.length
mutations.forEach(mutation => {
console.log('Type:', mutation.type)
console.log('Added nodes:', mutation.addedNodes)
console.log('Removed nodes:', mutation.removedNodes)
})
}, {
childList: true,
attributes: true,
attributeOldValue: true
})
</script>
<template>
<div>
<div ref="target">
<p>Mutations detected: {{ mutationCount }}</p>
</div>
</div>
</template>API
| Composable | Description |
|---|---|
| useIntersectionObserver→ | Detect element visibility |
| useResizeObserver→ | Observe element size changes |
| useEventListener→ | General event handling |
useMutationObserver
Type
interface MutationObserverOptions { immediate?: boolean childList?: boolean attributes?: boolean characterData?: boolean subtree?: boolean attributeOldValue?: boolean characterDataOldValue?: boolean attributeFilter?: string[] } function useMutationObserver( target: Ref<Element | undefined>, callback: (mutations: MutationRecord[]) => void, options?: MutationObserverOptions ): { isActive: Readonly<Ref<boolean>> isPaused: Readonly<Ref<boolean>> pause: () => void resume: () => void stop: () => void }Details
Observes changes to the DOM tree including attributes, child elements, and text content. Automatically handles cleanup on component unmount.
Parameters
target: Ref to the element to observecallback: Function called when mutations occuroptions:immediate: Trigger callback immediately (default: false)childList: Observe child element additions/removals (default: false)attributes: Observe attribute changes (default: false)characterData: Observe text content changes (default: false)subtree: Observe all descendants, not just direct children (default: false)attributeOldValue: Record previous attribute value (default: false)characterDataOldValue: Record previous text value (default: false)attributeFilter: Array of specific attributes to observe (default: all)
Returns
isActive: Whether the observer is currently created and observingisPaused: Whether observation is pausedpause(): Pause observationresume(): Resume observationstop(): Stop observation permanently
Example
const container = useTemplateRef('container') const { pause, resume } = useMutationObserver( container, (mutations) => { mutations.forEach(mutation => { if (mutation.type === 'attributes') { console.log('Attribute changed:', mutation.attributeName) } else if (mutation.type === 'childList') { console.log('Children changed') } }) }, { childList: true, attributes: true, attributeFilter: ['class', 'data-state'], subtree: true } ) // Pause/resume as needed pause() resume()
Lifecycle & Cleanup
Automatic Cleanup
useMutationObserver automatically disconnects the observer when:
- The component unmounts
- The Vue effect scope is disposed
- You call the returned
stop()function
Implementation:
// Uses Vue's onScopeDispose internally
onScopeDispose(() => observer.disconnect())This prevents memory leaks by ensuring observers don’t continue running after the component is destroyed.
Manual Control
The composable returns control functions for fine-grained lifecycle management:
const { isActive, isPaused, pause, resume, stop } = useMutationObserver(
element,
callback,
options
)
// Check if observer is active
console.log(isActive.value) // true
// Temporarily pause observation (keeps observer alive)
pause()
console.log(isActive.value) // false
// Resume observation
resume()
console.log(isActive.value) // true
// Permanently stop and disconnect observer
stop()
console.log(isActive.value) // falseState properties:
isActive: True when the observer exists and is observing (false when paused or stopped)isPaused: True when observation is temporarily paused
Difference between pause and stop:
pause(): Temporarily stops observing, can be resumed withresume()stop(): Permanently disconnects the observer, cannot be restarted
Reactive Target
The target element can be reactive. When the target ref changes, the observer automatically re-attaches:
const element = ref<HTMLElement | null>(null)
useMutationObserver(element, callback, options)
// Later - observer automatically reconnects to new element
element.value = document.querySelector('.new-target')Template Refs
Works seamlessly with Vue’s template refs:
<script setup>
import { useTemplateRef } from 'vue'
import { useMutationObserver } from '@vuetify/v0'
const content = useTemplateRef('content')
useMutationObserver(
content,
(mutations) => {
console.log('DOM changed:', mutations.length, 'mutations')
},
{
childList: true,
subtree: true
}
)
</script>
<template>
<div ref="content">
<!-- Mutations will be observed here -->
</div>
</template>Usage Outside Components
If called outside a component setup function:
- No automatic cleanup (no active effect scope)
- Must manually call
stop()to prevent memory leaks - Consider wrapping in
effectScope():
import { effectScope } from 'vue'
const scope = effectScope()
scope.run(() => {
useMutationObserver(element, callback, options)
})
// Later, cleanup all observers in the scope
scope.stop()SSR Considerations
MutationObserver is a browser-only API. The composable checks for browser environment internally:
// Safe to call during SSR - will not throw
const { isActive, isPaused } = useMutationObserver(element, callback, options)
// isActive.value and isPaused.value will be false in SSRPerformance Tips
Observe only what you need:
// Instead of observing everything
useMutationObserver(element, callback, {
childList: true,
attributes: true,
characterData: true,
subtree: true // Expensive!
})
// Be specific
useMutationObserver(element, callback, {
attributes: true,
attributeFilter: ['class', 'style'] // Only these attributes
})Use subtree sparingly:
// Observing subtree on large DOM can be expensive
useMutationObserver(element, callback, {
subtree: true, // Watches all descendants
childList: true
})
// Consider observing specific child elements instead
const child = ref(element.value?.querySelector('.specific-child'))
useMutationObserver(child, callback, { childList: true })Debounce frequent mutations:
import { debounce } from 'lodash-es'
const debouncedCallback = debounce((mutations: MutationRecord[]) => {
console.log('Mutations:', mutations)
}, 100)
useMutationObserver(element, debouncedCallback, {
childList: true,
subtree: true
})Pause during heavy operations:
const { pause, resume } = useMutationObserver(element, callback, options)
// Pause during bulk DOM updates
pause()
performBulkDOMUpdates()
resume()