Implementing a Draggable and Resizable Container Component in Vue

This article explains how to create a Vue component that supports both dragging and resizing by rendering CSS edges and corners, handling mouse events to track movement, calculating new dimensions, enforcing minimum sizes, and providing a complete template, script, and style implementation.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Implementing a Draggable and Resizable Container Component in Vue

When a UI requires drag‑and‑resize functionality, the core idea is to place invisible handles on the four sides and four corners of a container and update its size and position based on mouse movements.

Feature Demonstration

Images illustrate the scaling and moving effects; a demo link is provided.

Design Thinking for Scaling

Use CSS to draw four borders and four corner handles.

Position the lines and corners with CSS positioning.

Listen for mouse down and move events.

During movement, adjust the container's width, height, and position.

Core Design

Basic HTML Structure

<template></code>
<code>  <!-- Insert into body conditionally --></code>
<code>  <div ref="draggableContainer" class="draggable-container" @mousedown="startDrag" :style="containerStyle"></code>
<code>    <slot></slot></code>
<code>    <span v-for="type in resizeTypes" :key="type" :class="`${type}-resize`" @mousedown="startResize($event, type)"></span></code>
<code>  </div></code>
<code></template>

Basic Data

data: {</code>
<code>  resizeTypes: ["lt", "t", "rt", "r", "rb", "b", "lb", "l"],</code>
<code>  position: { x: this.left, y: this.top },</code>
<code>  size: { width: this.width, height: this.height },</code>
<code>  originMouseX: 0, originMouseY: 0,</code>
<code>  originContainX: 0, originContainY: 0,</code>
<code>  originWidth: 0, originHeight: 0,</code>
<code>  resizeType: ""</code>
<code>};

Core Code and Logic

Container new width = initial width + mouse movement distance

The algorithm records the container's initial size and mouse position, computes the delta, and updates width, height, and position according to the dragged edge.

1. Record Initial States

// Mouse down, start resize</code>
<code>startResize(event) {</code>
<code>  this.originMouseX = event.clientX;</code>
<code>  this.originMouseY = event.clientY;</code>
<code>  this.originWidth = this.size.width;</code>
<code>  this.originHeight = this.size.height;</code>
<code>  this.originContainX = this.position.x;</code>
<code>  this.originContainY = this.position.y;</code>
<code>},

2. Compute New Width for Right Edge

// deltaX = mouse move distance</code>
<code>const deltaX = event.clientX - this.originMouseX;</code>
<code>newWidth = this.originWidth + deltaX;

3. Determine Which Edge Is Dragged

<span v-for="type in resizeTypes" :key="type" :class="`${type}-resize`" @mousedown="startResize($event, type)"></span>

4. Enforce Minimum Width/Height

if (newWidth >= this.minWidth) { this.size.width = newWidth; }</code>
<code>if (newHeight >= this.minHeight) { this.size.height = newHeight; }

5. Add Drag‑Move Logic

The full component combines drag‑move and resize logic.

Complete Code

<template></code>
<code>  <div ref="draggableContainer" class="draggable-container" @mousedown="startDrag" :style="containerStyle"></code>
<code>    <slot></slot></code>
<code>    <span v-for="type in resizeTypes" :key="type" :class="`${type}-resize`" @mousedown="startResize($event, type)"></span></code>
<code>  </div></code>
<code></template></code>
<code><script></code>
<code>export default {</code>
<code>  props: { zIndex: {type: Number, default: 1}, left:{type:Number,default:0}, top:{type:Number,default:0}, width:{type:Number,default:300}, height:{type:Number,default:300}, minWidth:{type:Number,default:100}, minHeight:{type:Number,default:100} },</code>
<code>  data() { return { resizeTypes:["lt","t","rt","r","rb","b","lb","l"], position:{x:this.left,y:this.top}, size:{width:this.width,height:this.height}, originMouseX:0, originMouseY:0, originContainX:0, originContainY:0, originWidth:0, originHeight:0, resizeType:"" }; },</code>
<code>  computed:{ containerStyle(){ return { top:`${this.position.y}px`, left:`${this.position.x}px`, width:`${this.size.width}px`, height:`${this.size.height}px`, zIndex:this.zIndex }; } },</code>
<code>  methods:{</code>
<code>    startDrag(event){ this.originMouseX=event.clientX; this.originMouseY=event.clientY; this.originContainX=this.position.x; this.originContainY=this.position.y; document.addEventListener("mousemove",this.handleDrag); document.addEventListener("mouseup",this.stopDrag); },</code>
<code>    handleDrag(event){ this.position.x = this.originContainX + event.clientX - this.originMouseX; this.position.y = this.originContainY + event.clientY - this.originMouseY; },</code>
<code>    startResize(event,type){ this.resizeType=type; this.originMouseX=event.clientX; this.originMouseY=event.clientY; this.originWidth=this.size.width; this.originHeight=this.size.height; this.originContainX=this.position.x; this.originContainY=this.position.y; event.stopPropagation(); document.addEventListener("mousemove",this.handleResize); document.addEventListener("mouseup",this.stopDrag); },</code>
<code>    handleResize(event){ const deltaX=event.clientX-this.originMouseX; const deltaY=event.clientY-this.originMouseY; let newWidth=this.originWidth; let newHeight=this.originHeight; switch(this.resizeType){ case "lt": newWidth=this.originWidth-deltaX; newHeight=this.originHeight-deltaY; if(newWidth>=this.minWidth){ this.position.x=this.originContainX+deltaX; this.size.width=newWidth; } if(newHeight>=this.minHeight){ this.position.y=this.originContainY+deltaY; this.size.height=newHeight; } break; case "t": newHeight=this.originHeight-deltaY; if(newHeight>=this.minHeight){ this.position.y=this.originContainY+deltaY; this.size.height=newHeight; } break; case "rt": newWidth=this.originWidth+deltaX; newHeight=this.originHeight-deltaY; if(newWidth>=this.minWidth){ this.size.width=newWidth; } if(newHeight>=this.minHeight){ this.position.y=this.originContainY+deltaY; this.size.height=newHeight; } break; case "r": newWidth=this.originWidth+deltaX; if(newWidth>=this.minWidth){ this.size.width=newWidth; } break; case "rb": newWidth=this.originWidth+deltaX; newHeight=this.originHeight+deltaY; if(newWidth>=this.minWidth){ this.size.width=newWidth; } if(newHeight>=this.minHeight){ this.size.height=newHeight; } break; case "b": newHeight=this.originHeight+deltaY; if(newHeight>=this.minHeight){ this.size.height=newHeight; } break; case "lb": newWidth=this.originWidth-deltaX; newHeight=this.originHeight+deltaY; if(newWidth>=this.minWidth){ this.position.x=this.originContainX+deltaX; this.size.width=newWidth; } if(newHeight>=this.minHeight){ this.size.height=newHeight; } break; case "l": newWidth=this.originWidth-deltaX; if(newWidth>=this.minWidth){ this.position.x=this.originContainX+deltaX; this.size.width=newWidth; } break; } },</code>
<code>    stopDrag(){ document.removeEventListener("mousemove",this.handleDrag); document.removeEventListener("mousemove",this.handleResize); document.removeEventListener("mouseup",this.stopDrag); }</code>
<code>  },</code>
<code>  beforeDestroy(){ this.stopDrag(); }</code>
<code>};</code>
<code></script></code>
<code><style lang="scss" scoped></code>
<code>.draggable-container{ position:fixed; cursor:move; user-select:none; background:#ccc; span{position:absolute;display:block;} .l-resize,.r-resize{width:8px;height:100%;top:0;cursor:w-resize;} .l-resize{left:-6px;} .r-resize{right:-6px;} .t-resize,.b-resize{width:100%;height:8px;left:0;cursor:s-resize;} .t-resize{top:-6px;} .b-resize{bottom:-6px;} .lt-resize,.rt-resize,.rb-resize,.lb-resize{width:15px;height:15px;z-index:10;} .lt-resize,.lb-resize{left:-8px;} .lt-resize,.rt-resize{top:-8px;} .rt-resize,.rb-resize{right:-8px;} .rb-resize,.lb-resize{bottom:-8px;} .lt-resize,.rb-resize{cursor:se-resize;} .rt-resize,.lb-resize{cursor:sw-resize;} }</code>
<code></style>

Component Usage

<DraggableContainer :width="400" :height="400" :min-height="300" :min-width="300"></code>
<code>  <div>能拖动我了</div></code>
<code></DraggableContainer>

Conclusion

Part of the design references the popular third‑party library vxe‑modal.

The article provides a complete, functional draggable‑and‑resizable component that can be extended with custom features as needed.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendJavaScriptComponentVueCSSdraggableresizable
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

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.