SoFunction
Updated on 2025-03-10

vue3 uses custom instructions to implement el dialog drag and drop function examples for vue3 to use custom instructions

Implement the drag and drop function of el-dialog

Here we refer to the el-dialog component of element-plus. At the beginning, this component did not implement the drag function. Of course, drag and drop can now be achieved by setting properties.

The built-in drag function is very rigorous. When dragging, determine whether to drag out of the window. If it goes out, it will prevent dragging.

If the built-in drag and drop function can meet the needs, you can skip this article.

Drag and drop function through custom instructions

Because you have to operate the dom by yourself (set events), it feels like using custom instructions is more direct and has a smaller impact on native components.

Let's first define a custom directive _dialogDrag:

import dialogDrag from './_dialog-drag'
import { watch } from 'vue'
const _dialogDrag = {
  // mounted 
  mounted (el: any, binding: any) {
    // Listen to whether the dialog is displayed    watch (, () => {
      // dialog is not visible, exit      if (!) return
      // Find the el-dialog component      const container = 
      // Drag event has been set, exit      if () return
      // Wait for the DOM to be rendered      setTimeout(() => {
        // Drag “handle”        const _dialogTitle = ('el-dialog__header')
        if (_dialogTitle.length === 0) {
          // The rendering has not been completed, or for other reasons          ('No el-dialog to be dragged', el)
        } else {
          const { setDialog } = dialogDrag()
          const dialogTitle = _dialogTitle[0]
          // Pop-up window          const dialog = 
          // Find the width set by el-dialog via css          const arr = (';')
          const width = arr[0].replace('%', '').replace('--el-dialog-width:', '') //
          // Set el-dialog component, pop-up window, handle, width          setDialog(container, dialog, dialogTitle, width)
        }
      },300)
    })
  },
}
/**
  * Register custom commands for dragging and dropping dialogs
  * @param app
  * @param options
  */
const install = (app: any, options: any) => {
  ('dialogDrag', _dialogDrag)
}
export {
  _dialogDrag,
  install
}

Here are two more annoying things:

  • The timing of the DOM rendering. When executing mounted, the DOM may not be rendered. If you do not use setTimeout, the DOM will not be found, so this stupid method is used.
  • The hiding of the dialog. Generally speaking, el-dialog is initially hidden, and hiding means that the DOM will not be rendered. However, the custom instruction will be executed at the beginning. At this time, no matter how long the waiting time of setTimeout is, it is useless, so you have to listen to the status of the dialog.
  • After ref is passed through template and passed into the component again, it will lose the responsiveness of the ref, so it can only pass in reactive. In this way, the components calling the instruction will be awkward. At present, no better implementation method is expected.

Implement drag and drop function

Defining instructions and implementing drag and drop, I divided them into two files, and I think, try to decouple them as much as possible.

Define a drag and drop function (dialogDrag):

/**
  * The drag and drop dialog function currently supports element-plus
  */
export default function dialogDrag () {
  /**
    * Set drag event
    * @param container Large container, such as mask.
    * @param dialog The dragged window
    * @param dialogTitle drag-down title
    * @param width width ratio
    */
  const setDialog = (container: any, dialog: any, dialogTitle: any, width: number) => {
    const oldCursor = 
    // The width of the visible window    const clientWidth = 
    // The height of the visible window    const clientHeight = 
    // Calculate the width according to the percentage    const tmpWidth = clientWidth * (100 - width) / 200
    // Default width and height    const domset = {
      x: tmpWidth,
      y: clientHeight * 15 / 100 // Calculate based on 15vh    }
    // Check the current width and height of the dialog    if ( === '') {
       =  + 'px'
    } else {
       = ('px','') * 1
    }
    if ( === '') {
       =  + 'px'
    } else {
       = ('px','') * 1
    }
    // Record the cursor coordinates at the beginning of dragging, 0 means that there is no dragging    const start = { x: 0, y: 0 }
    // Record offset while moving    const move = { x: 0, y: 0 }
    // Change the shape of the mouse pointer when passing by     = () => {
       = 'move' // Change the cursor shape    }
    // Press the mouse and start dragging     = (e: any) => {
       = 
       = 
       = 'move' // Change the cursor shape    }
    // Mouse movement, real-time tracking of dialog     = (e: any) => {
      if ( === 0) { // Not dragging        return
      }
       =  - 
       =  - 
      // Initial position + drag distance       = ( + ) + 'px'
       = ( + ) + 'px'
    }
    // Lift the mouse and end dragging     = (e: any) => {
      if ( === 0) { // Not dragging        return
      }
       =  - 
       =  - 
      // Record the new coordinates as the initial position for the next drag       += 
       += 
       = oldCursor
       =  + 'px'
       =  + 'px'
      // End dragging       = 0
    }
  }
  return {
    setDialog // set up  }
}

First, we observe the DOM structure after el-dialog rendering and found that it is through the properties of marginLeft and marginTop, and then our drag and drop can also be achieved by modifying these two properties.

Then there is the ancient drag and drop idea: when pressing the mouse, record the initial coordinates of the cursor, and when lifting the mouse, record the end coordinates of the cursor, then calculate the "offset" of x and y, and then modify the two properties of marginLeft and marginTop to achieve the drag and drop effect.

This is the core idea, and the rest is to improve the details.

There is also a small problem, drag and close it, and then turn it on again, hoping that it can be opened where the drag ends, rather than the default position. So I thought of another way to record this location.

You still need to observe the behavior of el-dialog and finally find the pattern. At the beginning, marginLeft is empty, and the position will be retained after dragging. So, just make a judgment.

How to use

I originally wanted to set custom instructions for el-dialog directly, but found that it was "invalid", so I had to put a div outside.

<template>
  <!--Drag and drag-->
  <el-button @click=" = true">Open</el-button>
  <div v-dialog-drag="dialog" >
    <el-dialog
      v-model=""
      title="Custom Drag and Drop 2"
      width="25%"
    >
      <span>Drag and drag测试</span>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click=" = false">Cancel</el-button>
          <el-button type="primary" @click=" = false">Confirm</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script lang="ts">
  import { defineComponent, ref, reactive } from 'vue'
  import { _dialogDrag } from '../../../lib/main'
  export default defineComponent({
    name: 'nf-dialog-move',
    directives: {
      dialogDrag: _dialogDrag
    },
    props: {
    },
    setup(props, context) {
      const dialog = reactive({
        visible: false
      })
      return {
        meta,
        dialog
      }
    }
  })
</script>

If a custom directive is registered globally, then there is no need to register in the component.

visible of dialog: The name of visible property has been written to death and cannot be used with other names. This is a lazy setting.

Source code

/naturefw-co…

Online Demo

/nf-rollup-u…

The above is the detailed explanation of the example of using custom instructions to implement the el dialog drag function of vue3. For more information about the el dialog drag and drop of vue3 instructions, please follow my other related articles!