pointer-events

Forward DOM pointer events into a Three.js scene and filter them with @pmndrs/pointer-events for click/tap/hover interaction with 3D objects and portals. Use when objects in the scene need to be clickable, selectable, or hoverable.

Skill file

Preview skill file
---
name: pointer-events
description: "Forward DOM pointer events into a Three.js scene and filter them with @pmndrs/pointer-events for click/tap/hover interaction with 3D objects and portals. Use when objects in the scene need to be clickable, selectable, or hoverable."
---

# Pointer Events via `@pmndrs/pointer-events`

`@pmndrs/pointer-events` forwards real input into a Three.js scene so 3D objects fire pointer listeners, filters which objects get hit, and lets you define the shape of the pointer itself.

## Overview

The library rests on three pillars:

1. **Forward input in.** `forwardHtmlEvents` brings DOM events into the scene; `forwardObjectEvents` brings events from a surface into a separate portal scene. Each returns an `update()` that runs every frame.
2. **Filter what's hit.** The `pointerEvents` property (CSS-derived: `none`/`listener`/`auto`) and the `pointerEventsType` property (`all`/`allow`/`deny`/function) decide, per object, which events land.
3. **Define the pointer.** You can construct your own `Pointer` and choose how its intersection is computed.

These chain together: forwarding produces events that each carry a `pointerType`; filtering decides per object which of those events land; and custom pointers determine how and where intersection is computed. The sections below follow that order — forwarding events into a scene, filtering what gets hit, and custom pointers.

## Forwarding events into a scene

Forwarding installs a source of pointer events on the scene and gives you an `update()` to advance it each frame.

`forwardHtmlEvents` forwards the HTML document's events into the 3D scene. Call it with the DOM target, a camera getter, and the scene — `forwardHtmlEvents(document.body, () => camera, scene)` — and it returns `{ update }`. Call `update()` every frame in the render loop, before `renderer.render(scene, camera)`. Objects then receive events through `addEventListener('pointerover' | 'pointerout', (e: PointerEvent) => ...)`, where the `PointerEvent` type is imported from `@pmndrs/pointer-events` alongside `forwardHtmlEvents`. Each event exposes `e.point` (a vector, with `e.point.toArray()`), and you can attach multiple listeners for the same event type.

```ts
import * as THREE from 'three'
import { forwardHtmlEvents, PointerEvent } from '@pmndrs/pointer-events'

const width = window.innerWidth,
  height = window.innerHeight

const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10)
camera.position.z = 1
const { update } = forwardHtmlEvents(document.body, () => camera, scene)

const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2)
const material = new THREE.MeshBasicMaterial({ color: new THREE.Color('red') })
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)

mesh.addEventListener('pointerover', (e: PointerEvent) => material.color.set('blue'))
mesh.addEventListener('pointerover', (e: PointerEvent) => console.log(e.point.toArray()))
mesh.addEventListener('pointerout', (e: PointerEvent) => material.color.set('red'))

const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(width, height)
renderer.setAnimationLoop(() => {
  update()
  renderer.render(scene, camera)
})
```

`forwardObjectEvents` forwards events from a surface — for example a plane — into a separate scene that is rendered onto that surface, producing an interactive portal. It has the same shape as `forwardHtmlEvents`: pass the surface mesh, a camera getter, and the portal scene. It returns its own `update`, shown here as `updatePortal`, which must be called each frame alongside the main `update()`.

```ts
const { update: updatePortal } = forwardObjectEvents(planeMesh, () => portalCamera, portalScene)
// in the render loop, alongside the main update():
updatePortal()
```

## Filtering what gets hit

Two per-object properties control which events reach an object: `pointerEvents` selects whether the object is targetable at all, and `pointerEventsType` filters by the kind of pointer.

`pointerEvents` is based on the CSS `pointer-events` property and takes `none`, `listener`, or `auto`.

```js
object.pointerEvents = 'none'
```

- `none` — the object is never directly targeted.
- `auto` — the object is always targeted for events, matching the CSS property.
- `listener` — the additional value, and the **default**: the object is targeted only if it has any listeners attached.

The `listener` default is more reasonable than `auto` (the web default) for 3D scenes, which often contain semi-transparent content — particles, for instance — that should not catch pointer events by default.

Each object can also filter by pointer type through `pointerEventsType`, which defaults to `all` (events from every pointer type are accepted). To filter, set `{ allow: "screen-mouse" }` or `{ deny: "screen-touch" }` (`screen-mouse` is a normal mouse acting through a 2D screen). Both `allow` and `deny` accept a string or an array of strings, and `pointerEventsType` also accepts a function for custom logic.

The pointer types used by default are `screen-touch`, `screen-pen`, `ray`, `grab`, and `touch`. Events forwarded from the DOM carry a `pointerType` prefixed `screen-`, and events forwarded through a portal via `forwardObjectEvents` carry a `pointerType` prefixed `forward-`.

## Custom pointers

You can create your own `Pointer` — for example, to let first-person controls interact with their environment by placing the pointer inside the camera, or somewhere else entirely. A custom `Pointer` can use a normal `Ray` for intersection, a set of `Lines`, or a `Sphere` for grab and touch events.

## Pitfalls

The `pointerEvents` attribute of a `Mesh`/`Object3D` is not cloned when the object is cloned; reapply it on the clone.

Source

Creator's repository · drawcall-ai/skills

View on GitHub

Security

Security checks in progress
Results will appear here once audits complete
Checked by 3 independent security firms
Does it try to trick the AI?Not yet checkedPending · Gen Agent Trust Hub
Does it sneak in hidden code?Not yet checkedPending · Socket
Does it have known bugs?Not yet checkedPending · Snyk