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;
}

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:

Not very exciting but it’s a start

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">

Now that our elements are bound to their respective sizes and positions, we should now see our FrameComponent

There it is!

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

...

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.

A simple top bar to be used 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 and finishDrag .
  • duringDrag computes the position of the FrameComponent using the mouse event and $event.clientX and $event.clientY attributes. It then updates the position variables, which in turn update the position of div because it’s bound to those variables.
  • finishDrag simply removes the mousemove and mouseup 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.

Our div is now draggable!

In the next tutorial, I will be adding onto this to make this div resizable.

Happy coding!

Geophysicist, Software Engineer, Web Developer, Data Scientist

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store