Infinite Board for Drag-And-Drop Notes using Angular

Shikhar Vaish
Level Up Coding
Published in
5 min readApr 24, 2020

--

Interfaces, Transformations, Zooming, and Meth…

Hello There! For the last few days, I’ve been working on a project where I had to implement an infinite whiteboard and place sticky notes. The idea is to build a component that can be scrolled indefinitely. A user should be able to drag-and-drop widgets inside it anywhere in this infinite space. Moreover, it gets pretty interesting while adding a zoom-tool, a handy tool for such applications. I learned quite a lot about the transformation and scaling mechanisms while building this project. Read on to see how to build this from scratch!

Component Breakdown

Let’s start by deciding what models and components will be needed. Clearly, two things are obvious: Notes to represent the draggable widgets and Board to wrap everything up inside it are needed. Looking at our goal of infinite drag-and-drop space, our Notes container should expand on its own, and Board should allow us to scroll to any area of the big whiteboard. This can be achieved by adding another intermediate wrapper component: Background. This component is going to expand when needed, thereby, giving an infinite space experience.

Our final component structure, in a bottom-up manner, will be:

  1. Note: This is the draggable widget.
  2. Background: This contains all nodes. By default, it occupies 100% of the width and height of its parent: Board. It will expand when a node is dragged outside it.
  3. Board: The super-most wrapper of every other component. Its width and height are fixed. When Background expands, scrollbars are automatically triggered in Board.

How to implement an infinite space effect?

Looking closely, we find there are simply 4 cases when we have to increase dimensions of Background: overflow in any 4 directions, up, down, left, and right. This can be done simply by tracking the current position of the note being dragged and check if it is near the border position of Background or not. If yes, increase width and height as needed.

Increasing width or height will trigger scrollbars to automatically appear.

How to zoom at mouse position?

This is a little tricky to implement. I’ll give an overview that will help you get started. When we say that we want to Zoom-In at a point, we want everything to get enlarged. More precisely, every object gets enlarged with respect to our “ZoomCenter”. Or, we can say that every point moves away by a certain distance from our ZoomCenter.

This can be achieved by transforming our (x, y) values using:

newX = zoomFactor*(x-zoomCenter.x) + zoomCenter.x

newY = zoomFactor*(y-zoomCenter.y) + zoomCenter.y

newWidth = zoomFactor*width

newHeight = zoomFactor*height

There’s a catch here though. Our default zoomFactor is 1.0

When we want to zoom-in, we increase our zoomFactor by 0.1

When we want to zoom-out, we decrease our zoomFactor by 0.1

Now, say we have zoomed in to 1.1. Now, when we zoom-out, our zoomFactor will become 1.0. According to our transformation operations above, no change takes place because 1.0 multiplying factor does not change anything. This can be handled by resetting our values to default before applying transformations.

A transformation can be reset simply by dividing it by the last zoomFactor. Before we apply the transformation using the above rules, we will reset the values by using the following transformations:

x = ( (x-lastZoomCenter.x) / oldZoomFactor ) + lastZoomCenter.x

y = ( (y-lastZoomCenter.y) / oldZoomFactor ) + lastZoomCenter.y

width = width/oldZoomFactor

height = height/oldZoomFactor

That’s it. This should get you going.

Models, Controllers, and Utilities

Position

Position
A simple Position model will be used to represent coordinates on the whiteboard.
We will need to keep track of mouse movements and maintain initial and final states of cursor during dragging. This is done by creating a MouseController.

MouseController

MouseController
It helps us simulate mouse behavior. This is logically achieved by maintaining the last “mousedown” coordinates and storing the “current” cursor position. Besides, we will be using this controller during the “drag-and-drop” event only. This is why we have the “isPressed” flag.

Note

Note
This Note is wrapping up the attributes of our text-material. We will be having a Controller for this Model that the user will be directly interacting with most of the time.
A small DOM interaction is provided here just to make it easy to reposition notes through BoardController.

Before I move on to our last components: NoteController and BoardController, let us breakdown our application behavior:

  1. Drag-And-Drop Notes: We need to know the drag-start-point and drop-point for a note. The position of note will be used to render notes on the screen. This operation can be done using Position, MouseController, and Note described above. However, we still need to tell Note that the “drag-event” has been initiated. This will be done through NoteController that will be defined below.
  2. Detect Out-Of-Bound-Drag: There should be a way to detect if the node is in “border-area” so our Background element could increase its width or height.

There is no need for having a separate Angular component to represent Background. It can be integrated inside Board component HTML directly.

BoardController-NoteController Interactions

The idea is to present NoteController as a “sensor” element that is going to send Drag-And-Drop event signal to BoardController. All the logic of Notes involving positioning of Notes and handling Out-Of-Bound-Drag is defined inside BoardController.

Here is an overview of how the NoteController sends signals.

NoteController

Here,

  • startDrag() is called on “mousedown”.
  • drag() is called on “mousemove. It simply sends a signal to BoardController that this node was dragged.
  • stopDrag() is called on “mouseup”.
BoardController
Zoom Handler

BoardController handles all the logical transformations. Moreover, writing everything modular using MVC architecture will make it easy to code.

Hopefully, these sample code snippets will give you an idea of the actual implementation.

That’s all folks.
Boiboi.

--

--