Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sanchedev/fraxel/llms.txt

Use this file to discover all available pages before exploring further.

Fraxel provides a unified input system that handles both pointer (mouse / touch) and keyboard events. The Input instance is created automatically by Game.setup() and exposed as Game.input. Pointer coordinates are automatically scaled from CSS pixels to game canvas pixels so they always match the coordinate space your nodes live in. Keyboard methods support modifier keys and per-frame “just pressed / just released” queries for clean game-feel logic.

Pointer Input

Properties

PropertyTypeDescription
input.pointerPositionReadonly<Vector2>Current pointer position in game canvas coordinates (read-only)
input.isPointerPressedbooleantrue while any pointer button is held down

Pointer Events

Subscribe to pointer events via the named event properties on the Input object. All pointer events pass the current game-coordinate Vector2 to their callbacks.
Property on inputCallback signatureDescription
pointerPressed(position: Vector2) => voidFires when the pointer is pressed
pointerUnpressed(position: Vector2) => voidFires when the pointer is released
pointerMoved(position: Vector2) => voidFires whenever the pointer moves
import { Game } from 'fraxel'

Game.input.pointerPressed.on((position) => {
  console.log('Pressed at', position.x, position.y)
})

Game.input.pointerUnpressed.on((position) => {
  console.log('Released at', position.x, position.y)
})

Game.input.pointerMoved.on((position) => {
  console.log('Moved to', position.x, position.y)
})

Keyboard Input

All keyboard methods accept an optional set of modifier key flags. Keys are matched case-insensitively.

Methods

MethodReturnDescription
isKeyPressed(key, ctrl?, shift?, alt?)booleantrue while the key is currently held
isJustKeyPressed(key, ctrl?, shift?, alt?)booleantrue only on the first frame the key was pressed
isJustKeyUnpressed(key, ctrl?, shift?, alt?)booleantrue only on the first frame the key was released
getKeyAxis(positiveKey, negativeKey)-1 | 0 | 1Returns 1, -1, or 0 based on held keys
getKeyAxis(positiveKey, negativeKey) returns 1 if the positive key is held, -1 if the negative key is held, 0 if neither or both are held. This is ideal for directional movement: getKeyAxis('d', 'a') gives you a horizontal axis for left/right control.
import { useNode, useEvent } from 'fraxel/hooks'
import { PrimaryNode, Game, Vector2 } from 'fraxel'

function Player() {
  const transform = useNode(PrimaryNode.Transform)

  useEvent(transform, 'updated', (delta) => {
    const input = Game.input

    // Continuous movement using axis
    const dx = input.getKeyAxis('d', 'a')
    const dy = input.getKeyAxis('s', 'w')
    transform.node.position.x += dx * 80 * delta
    transform.node.position.y += dy * 80 * delta

    // One-shot action on frame of press
    if (input.isJustKeyPressed(' ')) {
      console.log('Jump!')
    }
  })

  return <transform ref={transform}><sprite textureId={PLAYER} /></transform>
}

The Clickable Node

For per-node pointer hit-testing, use the <clickable> node. It performs an AABB check against the pointer position every frame and emits typed events when the pointer enters, exits, or clicks within its rectangular area.
import { useNode, useEvent } from 'fraxel/hooks'
import { PrimaryNode } from 'fraxel'

function Button() {
  const btn = useNode(PrimaryNode.Clickable)

  useEvent(btn, 'clicked', (pos) => {
    console.log('Button clicked at local position', pos.x, pos.y)
  })

  useEvent(btn, 'mouseEntered', () => {
    console.log('Pointer entered button area')
  })

  useEvent(btn, 'mouseExited', () => {
    console.log('Pointer left button area')
  })

  return (
    <clickable ref={btn} size={[64, 32]}>
      <sprite textureId={BUTTON_TEX} />
    </clickable>
  )
}

Props

PropTypeDefaultDescription
sizeVectorLikeWidth and height of the clickable area. Required. Accepts [w, h], {x, y}, or a single number for a square
disabledbooleanfalseWhen true, all hit-testing and event emission is suspended

Events

EventCallback signatureDescription
clicked(position: Vector2) => voidFires when pointer is released inside the area (local coordinates)
mouseEntered() => voidFires once when the pointer enters the area
mouseExited() => voidFires once when the pointer leaves the area
mouseOver(position: Vector2) => voidFires every frame while the pointer is inside the area

JSX shorthand callbacks

The <clickable> element also accepts inline callback props for convenience:
<clickable
  size={[64, 32]}
  onClick={(pos)        => console.log('click', pos)}
  onMouseEnter={()      => console.log('enter')}
  onMouseExit={()       => console.log('exit')}
/>

Debugging clickable areas

Enable testOptions.showClickables when setting up the game to draw a visible overlay over every clickable area:
Game.setup({
  width: 160,
  height: 90,
  root,
  testOptions: { showClickables: true },
})
Active clickable areas are drawn in a golden tint; disabled ones are drawn in grey.

Input Options

Pass inputOptions to Game.setup() to configure how the input system handles keyboard events:
Game.setup({
  width: 160,
  height: 90,
  root,
  inputOptions: {
    preventKeyDefaults: true, // default — blocks browser shortcuts (arrow keys, space, etc.)
  },
})
OptionTypeDefaultDescription
preventKeyDefaultsbooleantrueCalls preventDefault() on keyboard events to block browser shortcuts
Set preventKeyDefaults: false if your game runs alongside a page that needs normal keyboard behaviour (e.g. text input fields outside the canvas).

Cleanup

The Input instance registers several window event listeners (keydown, keyup, pointermove, pointerdown, pointerup, resize). Call input.destroy() when you are done with the game to remove all of them and prevent memory leaks:
import { Game } from 'fraxel'

// Tears down the game loop and removes all input listeners
Game.destroy()
Game.destroy() calls Game.input.destroy() internally, so you do not need to call it separately when using the Game class. If you ever instantiate Input manually, remember to call destroy() yourself. For a full example of player movement combined with physics, see Physics. For per-node event hooks, see API: Hooks (Core).

Build docs developers (and LLMs) love