Skip to content

Transforms

Ripl supports element-level transformations — translate, scale, and rotate — that apply to any element or group. Transforms are set as properties on the element and are automatically applied to the rendering context before the element draws.

Transform Properties

Every element exposes these transform properties:

PropertyTypeDefaultDescription
translateXnumber0Horizontal translation in pixels
translateYnumber0Vertical translation in pixels
transformScaleXnumber1Horizontal scale factor
transformScaleYnumber1Vertical scale factor
rotationnumber | string0Rotation angle (see below)
transformOriginXnumber | string0Horizontal origin for rotation/scale
transformOriginYnumber | string0Vertical origin for rotation/scale

Rotation

Rotation can be specified as a number (radians) or a string with a unit suffix:

ts
// Radians (number)
rect.rotation = Math.PI / 4;

// Degrees (string)
rect.rotation = '45deg';

// Radians (string)
rect.rotation = '0.785rad';

When no unit suffix is provided on a string value, it is treated as radians.

Transform Origin

Transform origin defines the pivot point for rotation and scale. It can be a number (pixels) or a percentage string relative to the element's bounding box:

ts
// Pixel values
rect.transformOriginX = 50;
rect.transformOriginY = 50;

// Percentage of element dimensions
rect.transformOriginX = '50%';
rect.transformOriginY = '50%';

When set to '50%', the element rotates and scales around its center. The percentage is resolved relative to the element's width and height properties.

Application Order

Transforms are applied to the rendering context in the following order:

  1. Translate to the origin point (transformOriginX, transformOriginY)
  2. Translate by translateX, translateY
  3. Rotate by rotation
  4. Scale by transformScaleX, transformScaleY
  5. Translate back from the origin point

This order ensures that rotation and scaling happen around the specified origin.

Group Inheritance

When transforms are applied to a group, all children inherit the transformation. The context is saved before the group renders and restored afterward, so transforms compose naturally:

ts
import {
    createCircle,
    createGroup,
    createRect,
} from '@ripl/core';

const group = createGroup({
    children: [
        createRect({ x: 0,
            y: 0,
            width: 80,
            height: 80,
            fillStyle: '#3a86ff' }),
        createCircle({ cx: 120,
            cy: 40,
            radius: 30,
            fillStyle: '#ff006e' }),
    ],
});

// Both children will be translated and rotated together
group.translateX = 100;
group.translateY = 50;
group.rotation = '15deg';

Animating Transforms

All transform properties are fully animatable using renderer.transition(). String values (degrees, percentages) are interpolated smoothly:

ts
// Animate rotation from 0 to 360 degrees
await renderer.transition(rect, {
    duration: 1000,
    ease: easeOutCubic,
    state: {
        rotation: '360deg',
    },
});

// Animate translation
await renderer.transition(rect, {
    duration: 800,
    state: {
        translateX: 200,
        translateY: 100,
    },
});

// Animate scale
await renderer.transition(rect, {
    duration: 600,
    state: {
        transformScaleX: 2,
        transformScaleY: 2,
    },
});

SVG Support

Transforms work identically for both Canvas and SVG contexts. In the SVG context, transforms are serialised as SVG transform attribute values (e.g., translate(10,20) rotate(45) scale(2,2)), and the transform state is saved and restored alongside the context state stack.

Demo