Detect Clicks with Matter.Query.point in Matter.js

This article explains how to detect user clicks on physics bodies in a Matter.js simulation using the built-in Matter.Query.point method. You will learn how to capture click coordinates from the canvas, query the physics world to find intersecting bodies, and trigger custom logic based on the clicked body.

What is Matter.Query.point?

Matter.Query.point is a spatial query function provided by Matter.js. It tests a specific { x, y } coordinate against an array of bodies to determine if the point lies inside any of their collision shapes.

The syntax is:

Matter.Query.point(bodies, point)

Step-by-Step Implementation

1. Listen for the Click Event

Attach an event listener to the canvas element running the Matter.js renderer.

const canvas = render.canvas;

canvas.addEventListener('click', (event) => {
    // Coordinate conversion and query logic goes here
});

2. Get Canvas-Relative Coordinates

Mouse events provide screen or viewport coordinates. You must adjust these to match the canvas coordinate space using getBoundingClientRect():

const rect = canvas.getBoundingClientRect();
const mousePosition = {
    x: event.clientX - rect.left,
    y: event.clientY - rect.top
};

If your canvas uses CSS scaling (where the display size differs from canvas.width and canvas.height), scale the coordinates proportionally:

const mousePosition = {
    x: (event.clientX - rect.left) * (canvas.width / rect.width),
    y: (event.clientY - rect.top) * (canvas.height / rect.height)
};

3. Run the Query

Fetch all active bodies from the engine's world, pass them to Matter.Query.point, and evaluate the results:

const allBodies = Matter.Composite.allBodies(engine.world);
const clickedBodies = Matter.Query.point(allBodies, mousePosition);

if (clickedBodies.length > 0) {
    // The top-most or first detected body
    const targetBody = clickedBodies[0];
    
    console.log('Body clicked:', targetBody);
    
    // Example action: Remove the clicked body
    Matter.Composite.remove(engine.world, targetBody);
}

Complete Working Example

const { Engine, Render, Runner, Bodies, Composite, Query } = Matter;

// 1. Setup Engine and Renderer
const engine = Engine.create();
const render = Render.create({
    element: document.body,
    engine: engine,
    options: {
        width: 800,
        height: 600,
        wireframes: false
    }
});

Render.run(render);
Runner.run(Runner.create(), engine);

// 2. Add Bodies
const boxA = Bodies.rectangle(400, 200, 80, 80, { render: { fillStyle: '#e74c3c' } });
const boxB = Bodies.rectangle(450, 50, 80, 80, { render: { fillStyle: '#3498db' } });
const ground = Bodies.rectangle(400, 590, 810, 60, { isStatic: true });

Composite.add(engine.world, [boxA, boxB, ground]);

// 3. Click Detection via Matter.Query.point
render.canvas.addEventListener('click', (event) => {
    const rect = render.canvas.getBoundingClientRect();
    const clickPoint = {
        x: (event.clientX - rect.left) * (render.canvas.width / rect.width),
        y: (event.clientY - rect.top) * (render.canvas.height / rect.height)
    };

    const bodies = Composite.allBodies(engine.world);
    const matches = Query.point(bodies, clickPoint);

    if (matches.length > 0) {
        const clickedBody = matches[0];

        // Ignore static bodies like the ground
        if (!clickedBody.isStatic) {
            // Apply an upward impulse to the clicked body
            Matter.Body.applyForce(clickedBody, clickedBody.position, {
                x: 0,
                y: -0.05
            });
        }
    }
});

Handling Overlapping Bodies

If multiple bodies overlap at the clicked point, Matter.Query.point returns all of them in the array. If you only want to affect the body that visually appears on top, sort or filter the array using custom rendering layers or creation order before taking action.