Collision detection algorithms

If you're familiar with how 2D games are built, you may have come across the notion of collision detection algorithms.

One of the simpler forms of collision detection is between two rectangles that are axis aligned — meaning rectangles that are not rotated. This form of collision detection is generally referred to as Axis-Aligned Bounding Box (AABB).

The built-in collision detection algorithms assume a rectangular bounding box.

The bounding box of an element is the smallest possible rectangle (aligned with the axes of that element's user coordinate system) that entirely encloses it and its descendants. – Source: MDN

This means that even if the draggable or droppable nodes look round or triangular, their bounding boxes will still be rectangular:

If you'd like to use other shapes than rectangles for detecting collisions, build your own custom collision detection algorithm.

Rectangle intersection

The algorithm works by ensuring there is no gap between any of the 4 sides of the rectangles. Any gap means a collision does not exist.

This means that in order for a draggable item to be considered **over** a droppable area, there needs to be an intersection between both rectangles:

Closest center

While the rectangle intersection algorithm is well suited for most drag and drop use cases, it can be unforgiving, since it requires both the draggable and droppable bounding rectangles to come into direct contact and intersect.

For some use cases, such as sortable lists, using a more forgiving collision detection algorithm is recommended.

As its name suggests, the closest center algorithm finds the droppable container who's center is closest to the center of the bounding rectangle of the active draggable item:

Closest corners

Like to the closest center algorithm, the closest corner algorithm doesn't require the draggable and droppable rectangles to intersect.

Rather, it measures the distance between all four corners of the active draggable item and the four corners of each droppable container to find the closest one.

The distance is measured from the top left corner of the draggable item to the top left corner of the droppable bounding rectangle, top right to top right, bottom left to bottom left, and bottom right to bottom right.

In most cases, the **closest center** algorithm works well, and is generally the recommended default for sortable lists because it provides a more forgiving experience than the **rectangle intersection algorithm**.

In general, the closest center and closest corners algorithms will yield the same results. However, when building interfaces where droppable containers are stacked on top of one another, for example, when building a Kanban, the closest center algorithm can sometimes return the underlaying droppable of the entire Kanban column rather than the droppable areas within that column.

Closest center is 'A', though the human eye would likely expect 'A2'

In those situations, the **closest corners** algorithm is preferred and will yield results that are more aligned with what the human eye would predict:

Closest corners is 'A2', as the human eye would expect.

Pointer within

As its name suggests, the pointer within collision detection algorithm only registers collision when the pointer is contained within the bounding rectangle of other droppable containers.

This collision detection algorithm is well suited for high precision drag and drop interfaces.

As its name suggests, this collision detection algorithm **only works with pointer-based sensors**. For this reason, we suggest you use composition of collision detection algorithms if you intend to use the

`pointerWithin`

collision detection algorithm so that you can fall back to a different collision detection algorithm for the Keyboard sensor.Custom collision detection algorithms

In advanced use cases, you may want to build your own collision detection algorithms if the ones provided out of the box do not suit your use case.

You can either write a new collision detection algorithm from scratch, or compose two or more existing collision detection algorithms.

Composition of existing algorithms

Sometimes, you don't need to build custom collision detection algorithms from scratch. Instead, you can compose existing collision algorithms to augment them.

A common example of this is when using the

`pointerWithin`

collision detection algorithm. As its name suggest, this collision detection algorithm depends on pointer coordinates, and therefore does not work when using other sensors like the Keyboard sensor. It's also a very high precision collision detection algorithm, so it can sometimes be helpful to fall back to a more tolerant collision detection algorithm when the `pointerWithin`

algorithm does not return any collisions.1

import {pointerWithin, rectIntersection} from '@dnd-kit/core';

2

3

function customCollisionDetectionAlgorithm(args) {

4

// First, let's see if there are any collisions with the pointer

5

const pointerCollisions = pointerWithin(args);

6

7

// Collision detection algorithms return an array of collisions

8

if (pointerCollisions.length > 0) {

9

return pointerCollisions;

10

}

11

12

// If there are no collisions with the pointer, return rectangle intersections

13

return rectIntersection(args);

14

};

Copied!

Another example where composition of existing algorithms can be useful is if you want some of your droppable containers to have a different collision detection algorithm than the others.

For instance, if you were building a sortable list that also supported moving items to a trash bin, you may want to compose both the

`closestCenter`

and `rectangleIntersection`

collision detection algorithms.Use the closest corners algorithm for all droppables except 'trash'.

Use the intersection detection algorithm for the 'trash' droppable.

From an implementation perspective, the custom intersection algorithm described in the example above would look like:

1

import {closestCorners, rectIntersection} from '@dnd-kit/core';

2

3

function customCollisionDetectionAlgorithm({

4

droppableContainers,

5

...args,

6

}) {

7

// First, let's see if the `trash` droppable rect is intersecting

8

const rectIntersectionCollisions = rectIntersection({

9

...args,

10

droppableContainers: droppableContainers.filter(({id}) => id === 'trash')

11

});

12

13

// Collision detection algorithms return an array of collisions

14

if (rectIntersectionCollisions.length > 0) {

15

// The trash is intersecting, return early

16

return rectIntersectionCollisions;

17

}

18

19

// Compute other collisions

20

return closestCorners({

21

...args,

22

droppableContainers: droppableContainers.filter(({id}) => id !== 'trash')

23

});

24

};

Copied!

Building custom collision detection algorithms

For advanced use cases or to detect collision between non-rectangular or non-axis aligned shapes, you'll want to build your own collision detection algorithms.

1

/**

2

* Sort collisions in descending order (from greatest to smallest value)

3

*/

4

export function sortCollisionsDesc(

5

{data: {value: a}},

6

{data: {value: b}}

7

) {

8

return b - a;

9

}

10

11

function getCircleIntersection(entry, target) {

12

// Abstracted the logic to calculate the radius for simplicity

13

var circle1 = {radius: 20, x: entry.offsetLeft, y: entry.offsetTop};

14

var circle2 = {radius: 12, x: target.offsetLeft, y: target.offsetTop};

15

16

var dx = circle1.x - circle2.x;

17

var dy = circle1.y - circle2.y;

18

var distance = Math.sqrt(dx * dx + dy * dy);

19

20

if (distance < circle1.radius + circle2.radius) {

21

return distance;

22

}

23

24

return 0;

25

}

26

27

/**

28

* Returns the circle that has the greatest intersection area

29

*/

30

function circleIntersection({

31

collisionRect,

32

droppableRects,

33

droppableContainers,

34

}) => {

35

const collisions = [];

36

37

for (const droppableContainer of droppableContainers) {

38

const {id} = droppableContainer;

39

const rect = droppableRects.get(id);

40

41

if (rect) {

42

const intersectionRatio = getCircleIntersection(rect, collisionRect);

43

44

if (intersectionRatio > 0) {

45

collisions.push({

46

id,

47

data: {droppableContainer, value: intersectionRatio},

48

});

49

}

50

}

51

}

52

53

return collisions.sort(sortCollisionsDesc);

54

};

Copied!

To learn more, refer to the implementation of the built-in collision detection algorithms.

Last modified 23h ago