NarraLeaf

Camera

This class extends Displayble

The camera transforms the whole stage as one unit: every Scene, its backgrounds and sprites, and any playing Video move together. The dialog box, menus, and NVL layer stay fixed.

Every Story has exactly one camera, reachable as story.camera. You author camera actions like any other element action.

import { Story, Scene, Character } from "narraleaf-react";

const story = new Story("entry");
const scene = new Scene("scene 1");
const aria = new Character("Aria");

scene.action([
    story.camera.zoom(2, 800, "easeInOut"),   // zoom the whole stage in
    story.camera.pan({ xalign: 0.3 }, 800),   // slide the view across
    story.camera.rotate(3, 400),              // tilt
    story.camera.darken(0.6, 500),            // dim the stage
    aria.say`It's getting dark...`,
    story.camera.resetCamera(600),            // return to the neutral pose
]);

The camera reuses the same Transform pipeline as images and layers, so it inherits every chainable transform method from Displayable: pos, zoom, scale, rotate, opacity, transform, filter, effect, and more. They all apply to the whole stage.

Accessing the camera

story.camera is a single, always-present camera. There is exactly one per story, its pose persists across scene changes, and it is captured by save/load like any other element.

scene.action([
    story.camera.zoom(1.5, 600),
    story.camera.darken(0.4, 400),
]);

Public Methods

pan

Pan the camera so the given position sits at the centre of the view. This is an alias of the inherited pos method with camera-oriented naming.

story.camera.pan({ xalign: 0.3 }, 800, "easeInOut");

darken

Darken the whole stage. darkness is a value between 0 (normal) and 1 (black). Under the hood it drives the camera's CSS filter to brightness(1 - darkness), the same mechanism as Image.darken.

story.camera.darken(0.6, 500, "easeInOut");

Darken shares the single CSS filter channel, so it overwrites (and is overwritten by) other filters. To combine darken with another filter such as blur, write the full string yourself via camera.filter("blur(4px) brightness(0.4)").

resetCamera

Return the camera to its neutral pose: centred, zoom 1, no rotation, fully opaque, and no filter (which also clears darken).

story.camera.resetCamera(600, "easeInOut");

The filter is cleared on the first frame, and the pose eases over the duration given.

Give no duration when a grade is replaced. A filter eased from one chain to another takes the stage through the colors between them.

Renamed in 0.26.0: this was camera.reset. Every element carries an internal reset() — the lifecycle hook the engine runs over the cast when a new game starts or a save loads — and the camera was spending that name on this authoring helper, so newGame() never restored the camera's pose and a story that panned or zoomed kept that framing into the next playthrough.

Rename any camera.reset(...) in your script. Calling reset() on a camera now reaches the lifecycle hook instead: it restores the configured pose at once, animates nothing, and returns the camera rather than a chainable action.

Inherited transform methods

The camera is a Displayable, so its full transform surface moves the whole stage:

  • zoom(factor, duration?, easing?) — zoom in/out uniformly.
  • scale(scaleX, scaleY, duration?, easing?) — non-uniform scale.
  • rotate(deg, duration?, easing?) — rotate the view.
  • pos(position, duration?, easing?) — pan (see pan).
  • opacity(value, duration?, easing?) — fade the whole stage.
  • filter(css, options?) / effect(...) — colour-grade the stage (e.g. "contrast(1.2) saturate(1.3)").
  • transform(new Transform(...)) — run a full multi-step Transform sequence.

Setting the initial pose

There is exactly one camera per story. Pass your own only to set its starting pose:

import { Story, Camera } from "narraleaf-react";

const story = new Story("entry", {
    camera: new Camera({ zoom: 1.2 }),
});

The camera moves the visual stage only. The dialog box, menus, and the NVL layer are rendered outside it and are intentionally unaffected, so text stays readable while the camera moves.

On this page