Search Docs
Loading...
Skip to content

Composition Groups

Give the children of a group their own clock, run them faster with the group speed, and start them with the group.

6 mins
estimated time
Download
StackBlitz
GitHub

A composition group runs its children on its own clock, like a precomposition in a motion design tool. The clock starts when the group starts on the page, and the group’s speed runs it faster or slower. The editor has no composition control, so you set it with the block API.

This guide covers how to turn composition on, how to run the children faster, how to start them with the group, what a plain group does, and the error to expect. It ends with a frame export.

Creating the Scene#

We start the headless engine and create a scene with a page of 6 seconds.

// Initialize the headless Creative Engine
const engine = await CreativeEngine.init({
baseURL: process.env.IMGLY_LOCAL_ASSETS_URL
// license: process.env.CESDK_LICENSE
});
try {
// Create a scene with a 6 second page
const scene = engine.scene.create('DepthStack');
const page = engine.block.create('page');
engine.block.setWidth(page, 1920);
engine.block.setHeight(page, 1080);
engine.block.appendChild(scene, page);
engine.block.setDuration(page, 6);

Creating the Lanes#

Each lane of the example is a group with a gray line and a ball. The ball has a position/x track and crosses the page in 4 seconds of its own time. group() groups the two blocks.

/**
* Creates a lane: a group with a line and a ball that crosses the page in
* 4 seconds of its own time.
*/
function createLane(
engine: CreativeEngine,
page: number,
y: number,
color: { r: number; g: number; b: number; a: number }
): number {
const line = engine.block.create('graphic');
engine.block.setShape(line, engine.block.createShape('rect'));
const lineFill = engine.block.createFill('color');
engine.block.setColor(lineFill, 'fill/color/value', {
r: 0.85,
g: 0.85,
b: 0.85,
a: 1
});
engine.block.setFill(line, lineFill);
engine.block.setWidth(line, 1680);
engine.block.setHeight(line, 6);
engine.block.setPositionX(line, 120);
engine.block.setPositionY(line, y + 37);
engine.block.appendChild(page, line);
// Long enough for every lane clock, so the line stays visible
engine.block.setDuration(line, 12);
const ball = engine.block.create('graphic');
engine.block.setShape(ball, engine.block.createShape('ellipse'));
const ballFill = engine.block.createFill('color');
engine.block.setColor(ballFill, 'fill/color/value', color);
engine.block.setFill(ball, ballFill);
engine.block.setWidth(ball, 80);
engine.block.setHeight(ball, 80);
engine.block.setPositionY(ball, y);
engine.block.appendChild(page, ball);
engine.block.setDuration(ball, 4);
engine.block.setKeyframes(ball, 'position/x', [
{ time: 0, value: { type: 'Float', value: 120 } },
{ time: 1, value: { type: 'Float', value: 1720 } }
]);
const lane = engine.block.group([line, ball]);
engine.block.setDuration(lane, 6);
return lane;
}

Turning Composition On#

group/composition is a bool property of a group and is off by default. setBool() turns it on, and getBool() reads it. The children of the first lane now run on the group’s clock, which starts when the group starts on the page.

// The first lane is a composition group. Its children run on the group's
// own clock, which starts when the group starts on the page.
const normal = createLane(engine, page, 150, {
r: 0.6,
g: 0.6,
b: 0.6,
a: 1
});
engine.block.setBool(normal, 'group/composition', true);
console.log(engine.block.getBool(normal, 'group/composition')); // true

Running the Children Faster#

setPlaybackSpeed() on a composition group runs its children that many times faster. The speed of a group has no upper bound, and getPlaybackSpeed() reads it back. At speed 2, the blue ball crosses the page in 2 seconds. Its duration of 4 seconds is measured on the composition clock, so the ball disappears after 2 seconds of the page.

// At speed 2, the children see 2 seconds for every second of the page
const fast = createLane(engine, page, 400, {
r: 0.2,
g: 0.4,
b: 0.9,
a: 1
});
engine.block.setBool(fast, 'group/composition', true);
engine.block.setPlaybackSpeed(fast, 2);
console.log(engine.block.getPlaybackSpeed(fast)); // 2

Starting the Children With the Group#

The time offsets of the children are measured on the composition clock, which starts with the group. setTimeOffset() starts the third group 1 second into the page, so its ball starts 1 second later than the others.

// The group starts 1 second into the page, and its children start with it
const late = createLane(engine, page, 650, {
r: 0.9,
g: 0.3,
b: 0.4,
a: 1
});
engine.block.setBool(late, 'group/composition', true);
engine.block.setTimeOffset(late, 1);
engine.block.setDuration(late, 5);

A Plain Group#

A group without composition keeps its children on the page clock and ignores its speed. The green ball crosses the page at the same pace as the gray one, although its group has speed 2.

// Without composition, the speed of a group has no effect
const plain = createLane(engine, page, 900, {
r: 0.2,
g: 0.7,
b: 0.3,
a: 1
});
engine.block.setPlaybackSpeed(plain, 2);
console.log(engine.block.getBool(plain, 'group/composition')); // false

Handling Errors#

A group runs at 0.25 times the page speed or faster. A lower speed throws an error with a code, and the speed stays unchanged.

try {
// A group runs at 0.25 times the page speed or faster
engine.block.setPlaybackSpeed(normal, 0.1);
} catch (error) {
// Engine errors carry a stable catalog id in `code`
const { code } = error as { code?: string };
console.log(code); // 'EDITOR.PLAYBACK_SPEED_TOO_LOW'
}

Exporting a Frame#

We export the page as PNG at 1.5 seconds. The gray and green balls are at 37.5 percent of their lanes, the blue ball at 75 percent, and the red ball at 12.5 percent, because its group started 1 second later.

// Export the frame at 1.5 seconds
engine.block.setPlaybackTime(page, 1.5);
mkdirSync('output', { recursive: true });
const blob = await engine.block.export(page, { mimeType: 'image/png' });
writeFileSync(
'output/composition-groups.png',
Buffer.from(await blob.arrayBuffer())
);
console.log('Frame saved to output/composition-groups.png');

Cleaning Up#

We dispose of the engine in a finally block, so it frees its resources also after an error.

// Always dispose of the engine to free resources
engine.dispose();

Composition Groups and Other Block Operations#

  • Nested composition groups multiply their speeds.
  • A child’s duration is not clamped to the group duration. A child is hidden after its end on the composition clock.
  • playback/timeRemap with playback/timeRemapEnabled replaces the group’s local time with a value or a keyframe track, and the speed still multiplies it.
  • Ungrouping a composition group converts the time offset and duration of each child to the page clock, so the children keep playing at the same moments.
  • Saving and loading keep group/composition and the speed, and undo and redo restore them.
  • The HTML5 export does not export the composition clock, so the children play at page time there.

Troubleshooting#

  • The speed is too low (EDITOR.PLAYBACK_SPEED_TOO_LOW): use a speed of 0.25 or more.
  • The speed is not a number (BLOCK.VALUE_NOT_FINITE): pass a finite speed.
  • The speed has no effect: turn on group/composition for the group.
  • A child disappears early: its duration is measured on the composition clock, so a faster group ends it sooner on the page. Make the child longer.

API Reference#

Method Description
block.group(ids) Group blocks into a group
block.setBool(group, 'group/composition', value) Turn composition on or off
block.getBool(group, 'group/composition') Check whether a group is a composition group
block.setPlaybackSpeed(group, speed) Set the speed of a group, 0.25 or more
block.getPlaybackSpeed(group) Read the speed of a group
block.setTimeOffset(block, offset) Start a block or group later on the page
block.setDuration(block, duration) Set the duration of a block or group
block.setPlaybackTime(block, time) Show a frame at a playback time
block.export(block, options) Export the frame as PNG

Next Steps#