vue3-draggable-resizable-plus
v1.0.1
Published
[Vue3 Component] Draggable, resizable and rotatable with grid snap, spacing indicators and guide lines
Maintainers
Readme
Note: This is a maintained fork of [a7650/vue3-draggable-resizable/main-branch]. The original repository seems to be inactive since 2022. I have forked it to continue development, fix bugs, and add new features.
[Vue3 Component] Draggable, resizable and rotatable component for vue3, with support for collision detection, element snap alignment, real-time reference lines, grid snap, spacing indicators and unit conversion (px/%).
Table of Contents
Features
- Draggable and resizable
- Rotatable with snap angle support
- Define handles for resizing
- Restrict movement and size in parent node
- Customize various class names
- Provide your own markup for handles
- Adsorption alignment
- Reference line
- Grid system with snap-to-grid (supports rotated elements with center-point snap)
- Spacing indicators between elements (Figma-like)
- Unit conversion (px/%) support
- Utility functions for unit conversion
Usage
$ npm install vue3-draggable-resizable-plusRegister the Vue3DraggableResizable
// >main.js
import { createApp } from 'vue'
import App from './App.vue'
import Vue3DraggableResizable from 'vue3-draggable-resizable-plus'
// default styles
import 'vue3-draggable-resizable-plus/style.css'
// You will have a global component named "Vue3DraggableResizable"
createApp(App)
.use(Vue3DraggableResizable)
.mount('#app')You can also use it separately within the component
// >component.js
import { defineComponent } from 'vue'
import Vue3DraggableResizable from 'vue3-draggable-resizable-plus'
// default styles
import 'vue3-draggable-resizable-plus/style.css'
export default defineComponent({
components: { Vue3DraggableResizable }
// ...other
})Here is a complete example of using "vue-template"
<template>
<div id="app">
<div class="parent">
<Vue3DraggableResizable
:initW="110"
:initH="120"
v-model:x="x"
v-model:y="y"
v-model:w="w"
v-model:h="h"
v-model:active="active"
:typeX="typeX"
:typeY="typeY"
:typeW="typeW"
:typeH="typeH"
:draggable="true"
:resizable="true"
@activated="print('activated')"
@deactivated="print('deactivated')"
@drag-start="print('drag-start')"
@resize-start="print('resize-start')"
@dragging="print('dragging')"
@resizing="print('resizing')"
@drag-end="print('drag-end')"
@resize-end="print('resize-end')"
>
This is a test example
</Vue3DraggableResizable>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue'
import Vue3DraggableResizable from 'vue3-draggable-resizable-plus'
// default styles
import 'vue3-draggable-resizable-plus/style.css'
export default defineComponent({
components: { Vue3DraggableResizable },
data() {
return {
x: 100,
y: 100,
h: 25,
w: 25,
typeX: 'px',
typeY: 'px',
typeW: '%',
typeH: '%',
active: false
}
},
methods: {
print(val) {
console.log(val)
}
}
})
</script>
<style>
.parent {
width: 200px;
height: 200px;
position: absolute;
top: 100px;
left: 100px;
border: 1px solid #000;
user-select: none;
}
</style>Props
initW
type: Number
default: null
Set initial width(px)
<Vue3DraggableResizable :initW="100" />initH
type: Number
default: null
Set initial height(px)
<Vue3DraggableResizable :initH="100" />w
type: Number
default: 0
Current width of the container. Unit depends on typeW prop.
You can use "v-model:w" to keeps it up-to-date
<Vue3DraggableResizable v-model:w="100" />h
type: Number
default: 0
Current height of the container. Unit depends on typeH prop.
You can use "v-model:h" to keeps it up-to-date
<Vue3DraggableResizable v-model:h="100" />x
type: Number
default: 0
Current left position of the container. Unit depends on typeX prop.
You can use "v-model:x" to keeps it up-to-date
<Vue3DraggableResizable v-model:x="100" />y
type: Number
default: 0
The current top position of the container. Unit depends on typeY prop.
You can use "v-model:y" to keeps it up-to-date
<Vue3DraggableResizable v-model:y="100" />typeX
type: String
default: 'px'
validator: ['px', '%']
Unit type for x position. Can be 'px' or '%'.
<Vue3DraggableResizable :x="10" typeX="%" />typeY
type: String
default: 'px'
validator: ['px', '%']
Unit type for y position. Can be 'px' or '%'.
<Vue3DraggableResizable :y="10" typeY="%" />typeW
type: String
default: 'px'
validator: ['px', '%']
Unit type for width. Can be 'px' or '%'.
<Vue3DraggableResizable :w="50" typeW="%" />typeH
type: String
default: 'px'
validator: ['px', '%']
Unit type for height. Can be 'px' or '%'.
<Vue3DraggableResizable :h="50" typeH="%" />minW
type: Number
default: 20
Minimum width(px)
<Vue3DraggableResizable :minW="100" />minH
type: Number
default: 20
Minimum height(px)
<Vue3DraggableResizable :minH="100" />active
type: Boolean
default: false
Indicates whether the component is selected. You can use "v-model:active" to keeps it up-to-date
<Vue3DraggableResizable v-model:active="true" />draggable
type: Boolean
default: true
Defines the component can be draggable or not
<Vue3DraggableResizable :draggable="true" />resizable
type: Boolean
default: true
Defines the component can be resizable or not
<Vue3DraggableResizable :resizable="true" />lockAspectRatio
type: Boolean
default: false
The lockAspectRatio property is used to lock aspect ratio.
<Vue3DraggableResizable :lockAspectRatio="true" />disabledX
type: Boolean
default: false
Defines the component can be moved on x-axis or not
<Vue3DraggableResizable :disabledX="true" />disabledY
type: Boolean
default: false
Defines the component can be moved on y-axis or not
<Vue3DraggableResizable :disabledY="true" />disabledW
type: Boolean
default: false
Defines the component's width can be modified or not
<Vue3DraggableResizable :disabledW="true" />disabledH
type: Boolean
default: false
Defines the component's height can be modified or not
<Vue3DraggableResizable :disabledH="true" />parent
type: Boolean
default: false
Restrict movement and size within its parent node
<Vue3DraggableResizable :parent="true" />handles
type: Array
default: ['tl', 'tm', 'tr', 'ml', 'mr', 'bl', 'bm', 'br']
Define the array of handles to restrict the element resizing
tl: Top lefttm: Top middletr: Top rightmr: Middle rightml: Middle leftbl: Bottom leftbm: Bottom middlebr: Bottom right
<Vue3DraggableResizable :handles="['tl','tr','bl','br']" />gridSpacing
type: Number
default: 20
Grid spacing in pixels for snap-to-grid feature.
<Vue3DraggableResizable :gridSpacing="50" />snapToGrid
type: Boolean
default: false
Enable snap-to-grid during drag and resize operations.
<Vue3DraggableResizable :snapToGrid="true" :gridSpacing="50" />rotatable
type: Boolean
default: false
Enable rotation functionality. When enabled, a rotation handle appears above the component.
<Vue3DraggableResizable :rotatable="true" />rotation
type: Number
default: 0
Current rotation angle in degrees. You can use "v-model:rotation" to keep it up-to-date.
<Vue3DraggableResizable :rotatable="true" v-model:rotation="rotation" />rotationSnap
type: Number
default: 0
Snap angle in degrees during rotation. Set to 0 to disable snap. For example, 15 will snap to 0°, 15°, 30°, etc.
<Vue3DraggableResizable :rotatable="true" :rotationSnap="15" />classNameRotateHandle
type: String
default: ''
Custom class name for the rotation handle element.
<Vue3DraggableResizable :rotatable="true" classNameRotateHandle="my-rotate-handle" />classNameDraggable
type: String
default: draggable
Used to set the custom class of a draggable-resizable component when draggable is enable.
<Vue3DraggableResizable classNameDraggable="draggable" />classNameResizable
type: String
default: resizable
Used to set the custom class of a draggable-resizable component when resizable is enable.
<Vue3DraggableResizable classNameResizable="resizable" />classNameDragging
type: String
default: dragging
Used to set the custom class of a draggable-resizable component when is dragging.
<Vue3DraggableResizable classNameDragging="dragging" />classNameResizing
type: String
default: resizing
Used to set the custom class of a draggable-resizable component when is resizing.
<Vue3DraggableResizable classNameResizing="resizing" />classNameActive
type: String
default: active
Used to set the custom class of a draggable-resizable component when is active.
<Vue3DraggableResizable classNameActive="active" />classNameHandle
type: String
default: handle
Used to set the custom common class of each handle element.
<Vue3DraggableResizable classNameHandle="my-handle" />following handle nodes will be rendered
...
<div class="vdr-handle vdr-handle-tl my-handle my-handle-tl"></div>
<div class="vdr-handle vdr-handle-tm my-handle my-handle-tm"></div>
<div class="vdr-handle vdr-handle-tr my-handle my-handle-tr"></div>
<div class="vdr-handle vdr-handle-ml my-handle my-handle-mr"></div>
...Events
activated
payload: -
<Vue3DraggableResizable @activated="activatedHandle" />deactivated
payload: -
<Vue3DraggableResizable @deactivated="deactivatedHandle" />drag-start
payload: { x: number, y: number }
<Vue3DraggableResizable @drag-start="dragStartHandle" />dragging
payload: { x: number, y: number }
<Vue3DraggableResizable @dragging="draggingHandle" />drag-end
payload: { x: number, y: number }
<Vue3DraggableResizable @drag-end="dragEndHandle" />resize-start
payload: { x: number, y: number, w: number, h: number }
<Vue3DraggableResizable @resize-start="resizeStartHandle" />resizing
payload: { x: number, y: number, w: number, h: number }
<Vue3DraggableResizable @resizing="resizingHandle" />resize-end
payload: { x: number, y: number, w: number, h: number }
<Vue3DraggableResizable @resize-end="resizeEndHandle" />rotate-start
payload: { rotation: number }
<Vue3DraggableResizable :rotatable="true" @rotate-start="rotateStartHandle" />rotating
payload: { rotation: number }
<Vue3DraggableResizable :rotatable="true" @rotating="rotatingHandle" />rotate-end
payload: { rotation: number }
<Vue3DraggableResizable :rotatable="true" @rotate-end="rotateEndHandle" />Use-adsorption-alignment
You need to import another component to use the "adsorption alignment" feature.
This can be used as follows.
<template>
<div id="app">
<div class="parent">
<DraggableContainer>
<Vue3DraggableResizable>
Test
</Vue3DraggableResizable>
<Vue3DraggableResizable>
Another test
</Vue3DraggableResizable>
</DraggableContainer>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue'
import Vue3DraggableResizable from 'vue3-draggable-resizable-plus'
// This component is not exported by default
// If you used "app.use(Vue3DraggableResizable)", then you don't need to import it, you can use it directly.
import { DraggableContainer } from 'vue3-draggable-resizable-plus'
// default styles
import 'vue3-draggable-resizable-plus/style.css'
export default defineComponent({
components: { Vue3DraggableResizable, DraggableContainer }
})
</script>
<style>
.parent {
width: 200px;
height: 200px;
position: absolute;
top: 100px;
left: 100px;
border: 1px solid #000;
user-select: none;
}
</style>DraggableContainer Props
These props apply to DraggableContainer
disabled
type: Boolean
default: false
Disable adsorption alignment feature
<DraggableContainer :disabled="true">
<Vue3DraggableResizable>
Test
</Vue3DraggableResizable>
<Vue3DraggableResizable>
Another test
</Vue3DraggableResizable>
</DraggableContainer>adsorbParent
type: Boolean
default: true
Adsorption near parent component
<DraggableContainer :adsorbParent="false">
<Vue3DraggableResizable>
Test
</Vue3DraggableResizable>
<Vue3DraggableResizable>
Another test
</Vue3DraggableResizable>
</DraggableContainer>adsorbCols
type: Array<Number>
default: null
Custom guides(column)
<DraggableContainer :adsorbCols="[10,20,30]">
<Vue3DraggableResizable>
Test
</Vue3DraggableResizable>
<Vue3DraggableResizable>
Another test
</Vue3DraggableResizable>
</DraggableContainer>adsorbRows
type: Array<Number>
default: null
Custom guides(row)
<DraggableContainer :adsorbRows="[10,20,30]">
<Vue3DraggableResizable>
Test
</Vue3DraggableResizable>
<Vue3DraggableResizable>
Another test
</Vue3DraggableResizable>
</DraggableContainer>referenceLineVisible
type: Boolean
default: true
Reference line visible
<DraggableContainer :referenceLineVisible="false">
<Vue3DraggableResizable>
Test
</Vue3DraggableResizable>
<Vue3DraggableResizable>
Another test
</Vue3DraggableResizable>
</DraggableContainer>referenceLineColor
type: String
default: #f00
Reference line color
<DraggableContainer referenceLineColor="#0f0">
<Vue3DraggableResizable>
Test
</Vue3DraggableResizable>
<Vue3DraggableResizable>
Another test
</Vue3DraggableResizable>
</DraggableContainer>showSpacing
type: Boolean
default: true
Show spacing indicators between active element and other elements/container edges. Displays distance lines with measurements (Figma-like feature).
<DraggableContainer :showSpacing="false">
<Vue3DraggableResizable>
Test
</Vue3DraggableResizable>
</DraggableContainer>spacingColor
type: String
default: #ff6b6b
Color of the spacing indicator lines and text.
<DraggableContainer spacingColor="#00ff00">
<Vue3DraggableResizable>
Test
</Vue3DraggableResizable>
</DraggableContainer>Grid System
DraggableContainer supports a visual grid system with snap-to-grid functionality.
gridSpacing
type: Number
default: 20
Grid spacing in pixels
<DraggableContainer :gridSpacing="50">
<Vue3DraggableResizable :snapToGrid="true" :gridSpacing="50">
Test
</Vue3DraggableResizable>
</DraggableContainer>showGrid
type: Boolean
default: false
Show visual grid lines
<DraggableContainer :showGrid="true" :gridSpacing="50">
<Vue3DraggableResizable>
Test
</Vue3DraggableResizable>
</DraggableContainer>gridColor
type: String
default: #e0e0e0
Grid line color
<DraggableContainer :showGrid="true" gridColor="#ccc">
<Vue3DraggableResizable>
Test
</Vue3DraggableResizable>
</DraggableContainer>showGridNumbersX
type: Boolean
default: false
Show grid line numbers on X-axis (horizontal)
<DraggableContainer :showGrid="true" :showGridNumbersX="true">
<Vue3DraggableResizable>
Test
</Vue3DraggableResizable>
</DraggableContainer>showGridNumbersY
type: Boolean
default: false
Show grid line numbers on Y-axis (vertical)
<DraggableContainer :showGrid="true" :showGridNumbersY="true">
<Vue3DraggableResizable>
Test
</Vue3DraggableResizable>
</DraggableContainer>Spacing Indicators
The spacing indicators feature shows distance measurements between the active element and other elements or container edges, similar to design tools like Figma.
<template>
<DraggableContainer :showSpacing="true" spacingColor="#ff6b6b">
<Vue3DraggableResizable>
Element 1
</Vue3DraggableResizable>
<Vue3DraggableResizable>
Element 2
</Vue3DraggableResizable>
</DraggableContainer>
</template>When an element is active (selected), the spacing indicators will display:
- Distance to container edges (top, right, bottom, left)
- Distance to nearby elements
The indicators automatically update during drag, resize, and rotation operations. For rotated elements, spacing is calculated based on the axis-aligned bounding box.
Rotation
The rotation feature allows elements to be rotated around their center point. Enable rotation with the rotatable prop.
<template>
<DraggableContainer>
<Vue3DraggableResizable
:rotatable="true"
v-model:rotation="rotation"
:rotationSnap="15"
@rotate-start="onRotateStart"
@rotating="onRotating"
@rotate-end="onRotateEnd"
>
Rotatable element
</Vue3DraggableResizable>
</DraggableContainer>
</template>
<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
const rotation = ref(0)
const onRotateStart = (e) => console.log('Rotation started:', e.rotation)
const onRotating = (e) => console.log('Rotating:', e.rotation)
const onRotateEnd = (e) => console.log('Rotation ended:', e.rotation)
return { rotation, onRotateStart, onRotating, onRotateEnd }
}
})
</script>Rotation Features
- Rotation Handle: A circular handle appears above the element when
rotatableis enabled - Angle Snap: Use
rotationSnapprop to snap to specific angles (e.g., 15° intervals) - Cursor Direction: Resize handle cursors automatically adjust based on rotation angle
- Center-point Grid Snap: When grid snap is enabled, rotated elements snap based on their center point
- Rotated Resize: Resizing works correctly regardless of rotation angle, maintaining the anchor point
Unit Conversion
The component supports both px and % units for position and size. Use the typeX, typeY, typeW, and typeH props to specify the unit type.
<template>
<DraggableContainer>
<Vue3DraggableResizable
:x="10"
:y="10"
:w="50"
:h="50"
typeX="%"
typeY="%"
typeW="%"
typeH="%"
>
50% width and height at 10% position
</Vue3DraggableResizable>
</DraggableContainer>
</template>Utility Functions
The package exports utility functions for unit conversion that you can use in your application.
import {
convertToPixel,
convertFromPixel,
validatePercentage,
validatePixel,
generateRandomColor
} from 'vue3-draggable-resizable-plus'
// Convert percentage to pixels
const pixelValue = convertToPixel(50, '%', 1000) // 500
// Convert pixels to percentage
const percentValue = convertFromPixel(500, '%', 1000) // 50
// Validate percentage value (0-100)
const isValidPercent = validatePercentage(50) // true
// Validate pixel value within parent bounds
const isValidPixel = validatePixel(100, 500) // true
// Generate random hex color
const color = generateRandomColor() // e.g., '#3A7FE1'convertToPixel(value, unit, parentSize)
Converts a value to pixels based on the unit type.
value: The numeric value to convertunit: The unit type ('px' or '%')parentSize: The parent element size in pixels- Returns: The value converted to pixels
convertFromPixel(value, unit, parentSize)
Converts a pixel value to the specified unit type.
value: The pixel value to convertunit: The target unit type ('px' or '%')parentSize: The parent element size in pixels- Returns: The value converted to the target unit (with 2 decimal precision for %)
validatePercentage(value)
Validates if a percentage value is within valid range (0-100).
value: The percentage value to validate- Returns:
trueif value is between 0 and 100 (inclusive),falseotherwise
validatePixel(value, parentSize)
Validates if a pixel value is within parent bounds.
value: The pixel value to validateparentSize: The parent element size in pixels- Returns:
trueif value is between 0 and parentSize (inclusive),falseotherwise
generateRandomColor()
Generates a random hex color string.
- Returns: A random color in hex format (e.g., '#3A7FE1')
