iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Directives

Vue ships a handful of built-in directives (v-if, v-for, v-model, v-show), and lets you register custom ones for low-level DOM manipulation that does not fit a component — autofocus, click-outside, intersection observers, third-party widget bootstrapping. Use them for cross-cutting DOM behaviour; reach for components for everything else.

Three custom directives: focus, click-outside, intersection

EXAMPLE
<script setup lang='ts'>
import { ref } from 'vue';

// 1) v-focus — focuses the element on mount
const vFocus = {
  mounted: (el: HTMLElement) => el.focus(),
};

// 2) v-click-outside — emits a callback when the user clicks elsewhere
const vClickOutside = {
  mounted(el: HTMLElement & { __handler?: (e: Event) => void }, binding: any) {
    el.__handler = (e: Event) => {
      if (!el.contains(e.target as Node)) binding.value(e);
    };
    document.addEventListener('click', el.__handler);
  },
  unmounted(el: HTMLElement & { __handler?: (e: Event) => void }) {
    document.removeEventListener('click', el.__handler!);
  },
};

// 3) v-visible — uses IntersectionObserver to fire once on first visibility
const vVisible = {
  mounted(el: HTMLElement & { __io?: IntersectionObserver }, binding: any) {
    el.__io = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        binding.value(entry);
        el.__io!.disconnect();
      }
    }, { threshold: binding.arg ? Number(binding.arg) : 0.25 });
    el.__io.observe(el);
  },
  unmounted(el: HTMLElement & { __io?: IntersectionObserver }) {
    el.__io?.disconnect();
  },
};

const open = ref(true);
const onAway = () => (open.value = false);
const onSeen = (e: IntersectionObserverEntry) =>
  console.log('seen:', e.target);
</script>

<template>
  <input v-focus placeholder='I am focused on mount' />

  <div v-if='open' v-click-outside='onAway' class='popover'>
    Click outside this panel to dismiss it.
  </div>

  <!-- argument 0.5 = fire when 50% visible -->
  <img v-visible:0.5='onSeen' src='/hero.jpg' alt='hero' />
</template>

<style>
.popover { padding: .75rem 1rem; border: 1px solid #ddd; background: #fff; }
</style>

Why it matters

Stash per-element state on the DOM node (el.__io, el.__handler) so unmounted() can clean it up — a forgotten listener is a real memory leak. If a directive needs reactive state, you are usually a step away from wanting a component instead.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
<!-- v-if v-for v-bind v-on v-model v-show v-html v-text -->
Try it Yourself »

Discussion

Loading…