Frontend Development 16 min read

Implementing Resizable Elements with Vue Directives (v‑resize)

This article explains how to create a Vue directive for element resizing, covering basic right‑side resizing, handling trigger zones, enforcing minimum dimensions, adding left/right multi‑direction support, and providing complete JavaScript and TypeScript implementations with usage examples and future enhancements.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Implementing Resizable Elements with Vue Directives (v‑resize)

Introduction

In many projects we need to stretch layout elements; this guide shows how to achieve that using a custom Vue directive.

Online Demo

Try the live example at Codesandbox .

Basic Right‑Side Resize

We start with a simple right‑resize using the v-resize.right modifier.

const pointermove = e => {
  const { right } = el.getBoundingClientRect();
  const { clientX } = e;
  if (right - clientX < 8) {
    // can stretch
  }
};

Template for two adjacent divs:

<template>
  <div class="container">
    <div v-resize.right class="left"/>
    <div class="right"/>
  </div>
</template>
<style lang="scss" scoped>
.container {
  width: 400px;
  height: 100px;
  > div { float: left; height: 100%; width: 50%; }
  .left { background-color: lightcoral; }
  .right { background-color: lightblue; }
}
</style>

The directive detects the trigger zone (8 px from the right edge) and changes the cursor to col-resize :

export const resize = {
  inserted: function(el, binding) {
    el.addEventListener('pointermove', (e) => {
      const { right } = el.getBoundingClientRect();
      if (right - e.clientX < 8) {
        el.style.cursor = 'col-resize';
      } else {
        el.style.cursor = '';
      }
    });
  }
};

We also handle pointerdown to start resizing, calculate offsets, and update the widths of the target and its sibling while enforcing a minimum width:

el.addEventListener('pointerdown', (e) => {
  const rightDom = el.nextElementSibling;
  const startX = e.clientX;
  const { width } = el.getBoundingClientRect();
  const { width: nextWidth } = rightDom.getBoundingClientRect();
  el.setPointerCapture(e.pointerId);
  const onDocumentMouseMove = (e) => {
    const offsetX = e.clientX - startX;
    if (width + offsetX < MIN_WIDTH || nextWidth - offsetX < MIN_WIDTH) return;
    el.style.width = width + offsetX + 'px';
    rightDom.style.width = nextWidth - offsetX + 'px';
  };
  document.addEventListener('mousemove', onDocumentMouseMove);
  el.addEventListener('pointerup', (e) => {
    el.releasePointerCapture(e.pointerId);
    document.removeEventListener('mousemove', onDocumentMouseMove);
  });
});

Two issues were observed: the left width could exceed the total and the resize continued after mouse release. They are solved by limiting minimum width and releasing the pointer on pointerup .

Advanced Left/Right Multi‑Direction Resize

For simultaneous left and right resizing we track the current position (left or right) and a resizing flag.

export const resize = {
  inserted: function (el, binding) {
    let position = '', resizing = false;
    el.addEventListener('pointermove', (e) => {
      if (resizing) return;
      const { left, right } = el.getBoundingClientRect();
      const { clientX } = e;
      if (right - clientX < 8) { position = 'right'; el.style.cursor = 'col-resize'; }
      else if (clientX - left < 8) { position = 'left'; el.style.cursor = 'col-resize'; }
      else { position = ''; el.style.cursor = '' }
    });
    // pointerdown, mousemove, pointerup logic similar to the right‑only version
    // but uses the detected position to adjust widths of the element and its sibling accordingly
  }
};

The full JavaScript implementation includes event binding, cleanup via a WeakMap , support for minimum dimensions, and optional flex‑based sizing.

Complete JavaScript Version

const elEventsWeakMap = new WeakMap();
const MIN_WIDTH = 50;
const MIN_HEIGHT = 50;
const TRIGGER_SIZE = 8;
// utility functions: getElStyleAttr, getSiblingByPosition, getSiblingsSize, updateSize
// initResize sets up state, default cursor, and event listeners (pointermove, pointerdown, pointerup, pointerleave)
// bindElEvents / unBindElEvents manage lifecycle
export const resize = {
  inserted: function(el, binding) {
    const { modifiers, value } = binding;
    const positions = Object.keys(modifiers);
    initResize({ el, positions, ...value });
  },
  unbind: function(el) {
    const unBindElEvents = elEventsWeakMap.get(el);
    unBindElEvents();
  }
};

TypeScript Version

import type { DirectiveBinding } from 'vue';
const elEventsWeakMap = new WeakMap();
const MIN_WIDTH = 50;
const MIN_HEIGHT = 50;
const TRIGGER_SIZE = 8;
enum RESIZE_CURSOR { COL_RESIZE = 'col-resize', ROW_RESIZE = 'row-resize' }
enum POSITION { TOP = 'top', BOTTOM = 'bottom', LEFT = 'left', RIGHT = 'right' }
// Types: Positions, ResizeState, ResizeInfo, etc.
// Functions: getElStyleAttr, getSiblingByPosition, getSiblingsSize, updateSize, initResize
export const resize = {
  mounted: function (el: HTMLElement, binding: DirectiveBinding) {
    const { modifiers, value } = binding;
    const positions = Object.keys(modifiers);
    initResize({ el, positions, ...value });
  },
  beforeUnmount: function (el: HTMLElement) {
    const unBindElEvents = elEventsWeakMap.get(el);
    unBindElEvents();
  }
};

Future Work

The author plans to create a v-ellipsis-tooltip directive based on el-tooltip to show tooltips only when text overflows.

References: Juejin article 1 , Juejin article 2 .

End note: the author welcomes feedback and encourages readers to like, follow, and bookmark the article.

frontendtypescriptJavaScriptVueResizeDirective
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.