A draggable and resizable div using Angular (Part 1: Dragging)
In this tutorial series I’m going to show you how to create an Angular component that can be dragged as well as resized by dragging its edge or corner.
The repository for this project can be found on Github
Let’s first define the component: I’m going to call it FrameComponent
. This component will include both a div to act as a top-bar for dragging as well as a div to act as the resizer anchors.
frame.component.html
<div #wrapper class="frame-wrapper">
<div #topBar></div>
<div class="content-wrapper"></div>
<div class="resizers"></div>
</div>
frame.component.scss
.frame-wrapper {
position: absolute;
border: 2px solid black;
}.content-wrapper {
position: absolute;
background-color: lightblue;
overflow: auto;
}
frame.component.ts
import { Component, ElementRef, Inject, ViewChild } from '@angular/core';
import { DOCUMENT } from '@angular/common';
@Component({
selector: 'app-frame',
templateUrl: './frame.component.html',
styleUrls: [ './frame.component.scss' ]
})
export class FrameComponent {
@ViewChild('wrapper') wrapperRef!: ElementRef;
@ViewChild('topBar') topBarRef!: ElementRef;
position: { x: number, y: number } = { x: 100, y: 100 };
size: { w: number, h: number } = { w: 200, h: 200 };
lastPosition: { x: number, y: number };
lastSize: { w: number, h: number };
minSize: { w: number, h: number } = { w: 200, h: 200 };
constructor(@Inject(DOCUMENT) private _document: Document,
private _el: ElementRef) { }
}
Let’s break this down:
The FrameComponent
will need to keep track of its current position and size. We’ll give it a starting position of (100, 100) and a starting size of 200 x 200px.
The FrameComponent
will also need to keep track of its previous position and size, this will come into play when we start resizing and dragging.
It also needs to keep references to the wrapper and top-bar divs to we can access their dimensions. We bind to them using the @ViewChild
property decorator.
Running as-is we get the following:
Let’s now bind the size and position of our FrameComponent
to the variables.
frame.component.html
<div #wrapper
class="frame-wrapper"
[style.top.px]="position.y"
[style.left.px]="position.x"> <div #topBar></div> <div class="content-wrapper"
[style.width.px]="size.w"
[style.height.px]="size.h"></div> <div class="resizers"
[style.width.px]="size.w"
[style.height.px]="size.h"></div></div>
Now that our elements are bound to their respective sizes and positions, we should now see our FrameComponent
Let’s first work on dragging. We’ll build a top-bar to our div that will act as our draggable component
frame.component.html
...<div class="content-wrapper"
[style.width.px]="size.w"
[style.height.px]="size.h">
<div class="top-bar-wrapper"></div>
</div>...
frame.component.scss
.top-bar-wrapper {
width: 100%;
height: 20px;
background-color: black;
}
This will generate a simple black bar at the top of our div that we will treat as the dragging anchor.
We need the FrameComponent
to know when it’s time to start dragging, so to do that we bind the (mousedown)
event to a function that does that. This function will be responsible for implementing the following:
- When mouse is first pressed down, we capture the current position.
- Once the mouse begins to move, we update the position based on the mouse’s updated position.
- Upon releasing the mouse, we stop dragging.
This can be wrapped up in a function that looks like this:
frame.component.ts
startDrag($event): void {
$event.preventDefault();
const mouseX = $event.clientX;
const mouseY = $event.clientY;
const positionX = this.position.x;
const positionY = this.position.y;
const duringDrag = (e) => {
const dx = e.clientX - mouseX;
const dy = e.clientY - mouseY;
this.position.x = positionX + dx;
this.position.y = positionY + dy;
this.lastPosition = { ...this.position };
};
const finishDrag = (e) => {
this._document.removeEventListener('mousemove', duringDrag);
this._document.removeEventListener('mouseup', finishDrag);
};
this._document.addEventListener('mousemove', duringDrag);
this._document.addEventListener('mouseup', finishDrag);
}
Let’s break down the startDrag
function:
- We first call
$event.preventDefault()
because we’re overriding the anticipated functionality for our mouse event. - We capture the mouse X and Y positions using the
$event.clientX
and$event.clientY
attributes, respectively. - We then define two internal functions:
duringDrag
andfinishDrag
. duringDrag
computes the position of theFrameComponent
using the mouse event and$event.clientX
and$event.clientY
attributes. It then updates theposition
variables, which in turn update the position of div because it’s bound to those variables.finishDrag
simply removes themousemove
andmouseup
listeners from the document.- At the end of the function, we are adding event listeners to the mouse and binding them to our internal drag functions.
Now by clicking and dragging on the top-bar, we are able to move the div around the screen.
In the next tutorial, I will be adding onto this to make this div resizable.
Happy coding!