Why I chose a directive for Vue-Right-Click

After spending the past couple of months on work projects, I finally found a small but interesting problem worth writing about. One of our web applications needed to take over the browser’s right-click menu and provide an experience flexible enough to resemble the context menu on a desktop.

A working prototype took only two or three hours to build, but the more interesting part was deciding how the plugin should be structured.

My first thought was to implement the menu as a Vue component. After considering the maintenance implications—and later comparing the approach with similar plugins—I decided that a component would add more development work than the actual business requirement justified. A custom directive was a better fit: it could attach the behavior directly to the target element without making the consuming application more complicated as it grew.

A minimal Vue plugin structure

I had not previously spent much time on Vue plugin development, so I first reviewed the plugin documentation. Vue plugins can expose global methods, register directives, inject mixins, or add methods to Vue instances. A basic plugin can be created with an install method like this:

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或 property
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }
    ...
  })

  // 3. 注入组件选项
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}

For this plugin, the directive API was the most appropriate option. Vue provides five directive hooks: bind, inserted, update, componentUpdated, and unbind. Each hook runs at a different point in the directive or component lifecycle.

The hooks receive the same basic arguments:

  • el: the element bound to the directive, which can be used for direct DOM operations
  • binding: an object containing the directive’s binding information
  • vnode: the virtual node generated by Vue
  • oldVnode: the previous virtual node, available only in the update and componentUpdated hooks

A context menu is not needed immediately when the page loads. It only needs to become active after the target element has been inserted into the document, so the implementation uses the inserted hook as its starting point.

Breaking the behavior into smaller tasks

Before writing the menu itself, the required behavior can be divided into a few straightforward pieces:

  • determine where the mouse was clicked
  • insert the context-menu view into the page
  • render the menu entries passed to the plugin
  • execute the selected item’s action
  • remove the menu after use
  • ensure that only one context menu exists at a time

This list provided a practical outline for the rest of the implementation.

Getting the click position and rendering the menu

The directive’s el argument makes it possible to attach an oncontextmenu handler to the target element. From that callback, the mouse position can be obtained through offsetX and offsetY; these coordinates determine where the menu should appear.

Once the position is known, the directive can use the target element to insert the necessary DOM structure. The inserted container then serves as the mount point for a Vue-based popup component. The component renders its menu items from the options supplied to the plugin, handles item selection, and is removed when the menu is dismissed.

The implementation also needs to prevent multiple menus from being left in the document. Right-clicking again should update or replace the existing menu rather than create another independent popup. Keeping that constraint in mind makes the interaction behave more like a native desktop context menu.

There are several possible ways to organize the DOM insertion and component mounting logic, and the directive approach leaves room to refine those details without forcing every consuming page to manage a separate menu component.

Packaging the plugin with Vue CLI

For packaging, I initially considered using a separate build tool such as webpack or Rollup. While checking the Vue CLI documentation, I found that Vue CLI can build JavaScript libraries as well as applications.

The library build can be configured with a command like this:

"build:lib": "vue-cli-service build --target lib --name vue-right-click ./src/index.js"

This avoids introducing another build tool just for packaging. The resulting library is also built for compatibility with different module standards, which makes it more convenient to reuse in Vue projects.

The finished plugin provides a compact way to replace the browser’s default right-click behavior with a configurable Vue menu, while keeping the implementation attached to the element through a custom directive.