Vue3 Component for resizable and draggable elements
—
Advanced functionality including aspect ratio locking, handle customization, programmatic control methods, and specialized configuration options.
Lock aspect ratio during resize operations to maintain proportional dimensions.
/**
* Aspect ratio locking configuration
*/
interface AspectRatioControl {
/** Enable aspect ratio locking (default: false) */
lockAspectRatio: boolean;
}Usage Examples:
<template>
<!-- Image with locked aspect ratio -->
<vue-draggable-resizable
:lock-aspect-ratio="true"
:w="300"
:h="200"
>
<img src="photo.jpg" style="width: 100%; height: 100%; object-fit: cover;" />
</vue-draggable-resizable>
<!-- Video player with 16:9 ratio -->
<vue-draggable-resizable
:lock-aspect-ratio="true"
:w="480"
:h="270"
:min-width="320"
:min-height="180"
>
<video controls style="width: 100%; height: 100%;">
<source src="video.mp4" type="video/mp4">
</video>
</vue-draggable-resizable>
</template>Configure which resize handles are available and customize their appearance and behavior.
/**
* Resize handle configuration and types
*/
interface HandleCustomization {
/** Array of enabled resize handles (default: all 8 handles) */
handles: HandleType[];
/** Base CSS class for handles, used to create handle-specific classes */
classNameHandle: string;
}
/** Available resize handle positions */
type HandleType = 'tl' | 'tm' | 'tr' | 'mr' | 'br' | 'bm' | 'bl' | 'ml';
/** Handle position meanings */
interface HandlePositions {
'tl': 'top-left'; // Corner: top-left
'tm': 'top-middle'; // Edge: top-middle
'tr': 'top-right'; // Corner: top-right
'mr': 'middle-right'; // Edge: middle-right
'br': 'bottom-right'; // Corner: bottom-right
'bm': 'bottom-middle';// Edge: bottom-middle
'bl': 'bottom-left'; // Corner: bottom-left
'ml': 'middle-left'; // Edge: middle-left
}Usage Examples:
<template>
<!-- Only corner handles -->
<vue-draggable-resizable
:handles="['tl', 'tr', 'br', 'bl']"
class-name-handle="corner-handle"
>
Corner resize only
</vue-draggable-resizable>
<!-- Only right and bottom handles -->
<vue-draggable-resizable
:handles="['mr', 'br', 'bm']"
>
Expand right and down only
</vue-draggable-resizable>
<!-- Custom handle slots -->
<vue-draggable-resizable :handles="['br']">
<template #br>
<div class="custom-handle">
<svg width="10" height="10">
<path d="M0,0 L10,10 M0,10 L10,0" stroke="black" />
</svg>
</div>
</template>
Custom handle appearance
</vue-draggable-resizable>
</template>
<style>
.corner-handle {
border-radius: 50%;
background: #ff6b6b;
}
.custom-handle {
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.7);
border-radius: 3px;
}
</style>Handle parent element scaling transformations correctly.
/**
* Scale factor configuration for transformed parent elements
*/
interface ScaleSupport {
/** Scale factor - single number or [scaleX, scaleY] array (default: 1) */
scale: number | [number, number];
}Usage Examples:
<template>
<!-- Parent container with CSS transform scale -->
<div class="scaled-container">
<vue-draggable-resizable
:scale="0.75"
:w="200"
:h="150"
>
Handles parent scaling
</vue-draggable-resizable>
</div>
<!-- Different X and Y scale factors -->
<div class="stretched-container">
<vue-draggable-resizable
:scale="[0.8, 1.2]"
>
Different X/Y scaling
</vue-draggable-resizable>
</div>
</template>
<style>
.scaled-container {
transform: scale(0.75);
transform-origin: top left;
}
.stretched-container {
transform: scaleX(0.8) scaleY(1.2);
transform-origin: top left;
}
</style>Specify custom elements within the component that act as drag handles or prevent dragging.
/**
* Custom drag handle configuration
*/
interface CustomDragHandles {
/** CSS selector for elements that act as drag handles */
dragHandle?: string;
/** CSS selector for elements that prevent dragging when clicked */
dragCancel?: string;
}Usage Examples:
<template>
<!-- Custom drag handle -->
<vue-draggable-resizable
drag-handle=".drag-handle"
drag-cancel=".no-drag"
>
<div class="drag-handle" style="background: #eee; padding: 10px; cursor: move;">
📐 Drag me here
</div>
<div style="padding: 10px;">
<p>Main content area</p>
<button class="no-drag">Don't drag when clicking this button</button>
</div>
</vue-draggable-resizable>
<!-- Multiple drag handles -->
<vue-draggable-resizable drag-handle=".handle">
<div class="handle title-bar">Title Bar (drag here)</div>
<div>Content area</div>
<div class="handle status-bar">Status Bar (or here)</div>
</vue-draggable-resizable>
</template>
<style>
.drag-handle {
cursor: move;
user-select: none;
}
.title-bar {
background: #333;
color: white;
padding: 8px;
}
.status-bar {
background: #f0f0f0;
padding: 4px 8px;
font-size: 0.9em;
}
.no-drag {
cursor: pointer;
}
</style>Methods for controlling component position and size programmatically.
/**
* Programmatic control methods available on component instance
*/
interface ProgrammaticMethods {
/**
* Set horizontal position programmatically
* @param val - New X position in pixels
*/
moveHorizontally(val: number): void;
/**
* Set vertical position programmatically
* @param val - New Y position in pixels
*/
moveVertically(val: number): void;
/**
* Set width programmatically
* @param val - New width in pixels
*/
changeWidth(val: number): void;
/**
* Set height programmatically
* @param val - New height in pixels
*/
changeHeight(val: number): void;
}Usage Examples:
<template>
<div>
<vue-draggable-resizable
ref="draggableElement"
:w="200"
:h="150"
>
Programmable element
</vue-draggable-resizable>
<div class="controls">
<button @click="moveToCenter">Center</button>
<button @click="resetSize">Reset Size</button>
<button @click="animateToCorner">Animate to Corner</button>
</div>
</div>
</template>
<script>
export default {
methods: {
moveToCenter() {
const container = this.$el;
const element = this.$refs.draggableElement;
const centerX = (container.offsetWidth - 200) / 2;
const centerY = (container.offsetHeight - 150) / 2;
element.moveHorizontally(centerX);
element.moveVertically(centerY);
},
resetSize() {
const element = this.$refs.draggableElement;
element.changeWidth(200);
element.changeHeight(150);
},
async animateToCorner() {
const element = this.$refs.draggableElement;
const steps = 20;
const targetX = 0;
const targetY = 0;
// Simple animation by stepping through positions
for (let i = 0; i <= steps; i++) {
const progress = i / steps;
const currentX = element.left * (1 - progress) + targetX * progress;
const currentY = element.top * (1 - progress) + targetY * progress;
element.moveHorizontally(currentX);
element.moveVertically(currentY);
await new Promise(resolve => setTimeout(resolve, 20));
}
}
}
}
</script>Configure grid-based snapping for precise positioning and sizing.
/**
* Grid snapping configuration
*/
interface GridSnapping {
/** Grid intervals [x, y] in pixels (default: [1, 1]) */
grid: [number, number];
}Usage Examples:
<template>
<!-- 20x20 pixel grid -->
<vue-draggable-resizable
:grid="[20, 20]"
:w="200"
:h="160"
>
Snaps to 20px grid
</vue-draggable-resizable>
<!-- Different X and Y grid sizes -->
<vue-draggable-resizable
:grid="[25, 15]"
>
25px horizontal, 15px vertical grid
</vue-draggable-resizable>
<!-- Fine grid for precision -->
<vue-draggable-resizable
:grid="[5, 5]"
>
Fine 5px grid
</vue-draggable-resizable>
</template>Comprehensive slot system allowing content customization and custom handle designs.
/**
* Available slots for content and handle customization
*/
interface SlotSystem {
/** Default slot for main component content */
default: Slot;
/** Named slots for each resize handle position */
'tl': Slot; // Top-left handle slot
'tm': Slot; // Top-middle handle slot
'tr': Slot; // Top-right handle slot
'mr': Slot; // Middle-right handle slot
'br': Slot; // Bottom-right handle slot
'bm': Slot; // Bottom-middle handle slot
'bl': Slot; // Bottom-left handle slot
'ml': Slot; // Middle-left handle slot
}Usage Examples:
<template>
<!-- Custom handle designs -->
<vue-draggable-resizable :handles="['tl', 'br', 'mr']">
<!-- Default slot content -->
<div class="content">
<h3>Resizable Card</h3>
<p>Content goes here</p>
</div>
<!-- Custom corner handle -->
<template #tl>
<div class="corner-handle top-left">
<svg width="12" height="12">
<path d="M2,2 L10,2 L10,10" stroke="currentColor" fill="none" stroke-width="2"/>
</svg>
</div>
</template>
<!-- Custom corner handle -->
<template #br>
<div class="corner-handle bottom-right">
<svg width="12" height="12">
<path d="M2,10 L10,10 L10,2" stroke="currentColor" fill="none" stroke-width="2"/>
</svg>
</div>
</template>
<!-- Custom edge handle -->
<template #mr>
<div class="edge-handle">
<div class="handle-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
</template>
</vue-draggable-resizable>
</template>
<style>
.content {
padding: 16px;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
height: 100%;
box-sizing: border-box;
}
.corner-handle {
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 123, 255, 0.1);
border: 2px solid #007bff;
border-radius: 6px;
color: #007bff;
cursor: nw-resize;
}
.corner-handle.bottom-right {
cursor: se-resize;
}
.edge-handle {
display: flex;
align-items: center;
justify-content: center;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
cursor: ew-resize;
}
.handle-dots {
display: flex;
flex-direction: column;
gap: 2px;
}
.handle-dots span {
width: 3px;
height: 3px;
background: #6c757d;
border-radius: 50%;
}
</style>Themed Handle Examples:
<template>
<!-- Dark theme handles -->
<vue-draggable-resizable class="dark-theme">
<div class="dark-content">Dark themed content</div>
<template #tl>
<div class="dark-handle corner">↖</div>
</template>
<template #tr>
<div class="dark-handle corner">↗</div>
</template>
<template #bl>
<div class="dark-handle corner">↙</div>
</template>
<template #br>
<div class="dark-handle corner">↘</div>
</template>
</vue-draggable-resizable>
<!-- Minimal handles -->
<vue-draggable-resizable :handles="['br']">
<div class="minimal-content">Minimal resize</div>
<template #br>
<div class="minimal-handle">
<div class="resize-icon">⤢</div>
</div>
</template>
</vue-draggable-resizable>
</template>
<style>
.dark-theme {
background: #343a40;
border: 1px solid #495057;
}
.dark-content {
color: white;
padding: 16px;
}
.dark-handle {
background: #495057;
color: #adb5bd;
border: 1px solid #6c757d;
border-radius: 3px;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.minimal-handle {
background: transparent;
color: #6c757d;
display: flex;
align-items: flex-end;
justify-content: flex-end;
cursor: se-resize;
}
.resize-icon {
font-size: 14px;
line-height: 1;
}
</style>Built-in support for touch devices with optimized touch event handling.
/**
* Touch device support is automatically enabled
* Component handles both mouse and touch events seamlessly
*/
interface TouchSupport {
/** Touch events are automatically detected and handled */
touchEnabled: true;
/** Enhanced touch targets on mobile devices via CSS media queries */
mobileFriendly: true;
}Usage Examples:
<template>
<!-- Works automatically on touch devices -->
<vue-draggable-resizable
:w="250"
:h="200"
class="touch-optimized"
>
Touch and drag on mobile
<!-- Larger handles for better touch experience -->
<template #br>
<div class="touch-handle">
<div class="touch-indicator"></div>
</div>
</template>
</vue-draggable-resizable>
</template>
<style>
.touch-optimized {
/* Component automatically includes mobile-friendly handle sizing */
}
.touch-handle {
width: 20px;
height: 20px;
background: rgba(0, 123, 255, 0.2);
border: 2px solid #007bff;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.touch-indicator {
width: 8px;
height: 8px;
background: #007bff;
border-radius: 2px;
}
/* Additional mobile optimizations */
@media (max-width: 768px) {
.touch-optimized {
min-width: 100px;
min-height: 80px;
}
.touch-handle {
width: 24px;
height: 24px;
}
}
</style>Install with Tessl CLI
npx tessl i tessl/npm-vue-draggable-resizable