# How Terrarium Drag and Drop Works: Native Pointer Events and Closure Scope Explained

> Learn how terrarium's drag and drop works using native pointer events and closure scope. Discover the DOM manipulation techniques behind repositioning plant images with inline CSS.

- Repository: [Microsoft/Web-Dev-For-Beginners](https://github.com/microsoft/Web-Dev-For-Beginners)
- Tags: deep-dive
- Published: 2026-02-27

---

**Terrarium implements drag-and-drop functionality using native pointer events and a closure-based helper function that tracks cursor deltas to reposition plant images via inline CSS styles.**

The drag-and-drop system in the Terrarium project from microsoft/Web-Dev-For-Beginners demonstrates fundamental DOM manipulation techniques without relying on external libraries. By leveraging standard browser APIs and JavaScript closures, the project enables users to drag fourteen different plant images into a virtual glass jar using lightweight, vanilla JavaScript.

## Registering Draggable Elements in script.js

In [`script.js`](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/script.js), the application initializes drag behavior by invoking the `dragElement` helper for every plant image in the selection panel. The script targets fourteen unique elements identified as `plant1` through `plant14` that reside in the left and right containers defined in [`index.html`](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/index.html).

```javascript
// Register each plant image as draggable
dragElement(document.getElementById('plant1'));
// … repeated for plant2 through plant14

```

(see **script.js lines 3‑16**)

## The Closure-Based Architecture

The `dragElement` function creates a private scope for each plant element by declaring four position variables inside its function body. This closure pattern ensures that `pos1`, `pos2`, `pos3`, and `pos4` remain accessible to the nested event handlers even after `dragElement` returns, allowing each plant to maintain independent state during drag operations.

```javascript
function dragElement(terrariumElement) {
    let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
    // Event handlers defined here retain access to these variables
}

```

(see **script.js lines 23‑28**)

Because every plant receives its own closure instance, users can drag multiple plants independently without position calculations interfering between elements.

## Handling Pointer Events

The implementation uses the **Pointer Events API** to handle input from mice, touchscreens, and styluses through a unified interface.

### Starting the Drag Operation

When a user presses on a plant image, the `onpointerdown` handler records the initial cursor coordinates and registers document-level listeners for movement and release events.

```javascript
terrariumElement.onpointerdown = function(e) {
    e.preventDefault();
    pos3 = e.clientX;   // initial cursor X
    pos4 = e.clientY;   // initial cursor Y
    document.onpointermove = elementDrag;
    document.onpointerup   = stopElementDrag;
};

```

(see **script.js lines 29‑41**)

### Calculating Movement Deltas

During the drag operation, the `elementDrag` function computes the distance between the previous cursor position and the current one. It calculates the horizontal delta (`pos1`) and vertical delta (`pos2`), then subtracts these values from the element's current `offsetTop` and `offsetLeft` properties to determine the new position.

```javascript
function elementDrag(e) {
    pos1 = pos3 - e.clientX;   // horizontal delta
    pos2 = pos4 - e.clientY;   // vertical delta
    pos3 = e.clientX;          // update previous X
    pos4 = e.clientY;          // update previous Y

    terrariumElement.style.top  = terrariumElement.offsetTop  - pos2 + 'px';
    terrariumElement.style.left = terrariumElement.offsetLeft - pos1 + 'px';
}

```

(see **script.js lines 44‑56**)

### Cleaning Up Event Listeners

When the user releases the pointer, the `stopElementDrag` function removes the document-level listeners to prevent further unintended movement.

```javascript
function stopElementDrag() {
    document.onpointerup   = null;
    document.onpointermove = null;
}

```

(see **script.js lines 59‑63**)

## HTML Structure Supporting Drag Operations

The [`index.html`](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/index.html) file provides the DOM structure that enables this functionality. Each plant image receives a unique identifier that [`script.js`](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/script.js) uses to attach event listeners, while CSS positioning allows the JavaScript to modify `top` and `left` properties freely.

```html
<div id="plant-selection">
  <div id="left-container" class="container">
    <div class="plant-holder">
      <img class="plant" id="plant1" src="./images/plant1.png" alt="plant">
    </div>
    <!-- … additional left-side plants … -->
  </div>
</div>

```

## Summary

- **Closure isolation**: The `dragElement` function creates private scope for each plant, preventing interference between multiple draggable elements.
- **Native pointer events**: The implementation uses `onpointerdown`, `onpointermove`, and `onpointerup` for cross-device compatibility without library dependencies.
- **Delta calculations**: Position tracking relies on cursor coordinate differences (stored in `pos1` through `pos4`) applied to `offsetTop` and `offsetLeft` properties.
- **Cleanup pattern**: Document-level listeners are explicitly removed in `stopElementDrag` to prevent memory leaks and ghost dragging.
- **File locations**: Core logic resides in [`3-terrarium/solution/script.js`](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/3-terrarium/solution/script.js) with DOM elements defined in [`3-terrarium/solution/index.html`](https://github.com/microsoft/Web-Dev-For-Beginners/blob/main/3-terrarium/solution/index.html).

## Frequently Asked Questions

### How does Terrarium's drag and drop differ from the HTML5 Drag and Drop API?

Terrarium uses manual pointer event handling and absolute positioning rather than the native HTML5 Drag and Drop API. This approach provides direct control over the visual presentation during dragging and avoids the ghost image previews and limited styling options inherent in the standard API.

### Why does the implementation use pointer events instead of mouse events?

Pointer events unify input handling across mice, touchscreens, pens, and other pointing devices. By using `onpointerdown`, `onpointermove`, and `onpointerup`, the Terrarium project supports both desktop and mobile interactions without requiring separate touch event handlers or feature detection code.

### How does the closure pattern prevent interference between multiple plants?

Each invocation of `dragElement` creates a new execution context containing independent instances of `pos1` through `pos4`. When event handlers execute, they reference their specific closure's variables through lexical scoping, ensuring that dragging `plant1` does not affect the position calculations of `plant2` or any other element.

### Can this drag and drop implementation handle simultaneous multi-touch interactions?

While the closure architecture supports multiple independent elements, the current implementation attaches listeners to `document` using direct property assignment (`document.onpointermove`), which handles one pointer interaction at a time per document. For true multi-touch support, the code would need to track pointer IDs and use `addEventListener` with the `pointerId` property to distinguish between simultaneous touches.