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

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 Lanes#
Each lane of the example is a group with a gray line and a ball on page, the 6-second page of the scene. 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 timefunc createLane(y: Float, color: Color) throws -> DesignBlockID { let line = try engine.block.create(.graphic) try engine.block.setShape(line, shape: engine.block.createShape(.rect)) let lineFill = try engine.block.createFill(.color) try engine.block.setColor(lineFill, property: "fill/color/value", color: .rgba(r: 0.85, g: 0.85, b: 0.85, a: 1)) try engine.block.setFill(line, fill: lineFill) try engine.block.setWidth(line, value: 1680) try engine.block.setHeight(line, value: 6) try engine.block.setPositionX(line, value: 120) try engine.block.setPositionY(line, value: y + 37) try engine.block.appendChild(to: page, child: line) // Long enough for every lane clock, so the line stays visible try engine.block.setDuration(line, duration: 12)
let ball = try engine.block.create(.graphic) try engine.block.setShape(ball, shape: engine.block.createShape(.ellipse)) let ballFill = try engine.block.createFill(.color) try engine.block.setColor(ballFill, property: "fill/color/value", color: color) try engine.block.setFill(ball, fill: ballFill) try engine.block.setWidth(ball, value: 80) try engine.block.setHeight(ball, value: 80) try engine.block.setPositionY(ball, value: y) try engine.block.appendChild(to: page, child: ball) try engine.block.setDuration(ball, duration: 4) try engine.block.setKeyframes(ball, property: "position/x", keyframes: [ Keyframe(time: 0, value: .float(120)), Keyframe(time: 1, value: .float(1720)), ])
let lane = try engine.block.group([line, ball]) try engine.block.setDuration(lane, duration: 6) return lane}Turning Composition On#
group/composition is a bool property of a group and is off by default. setBool(_:property:value:) turns it on, and getBool(_:property:) 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.let normal = try createLane(y: 150, color: .rgba(r: 0.6, g: 0.6, b: 0.6, a: 1))try engine.block.setBool(normal, property: "group/composition", value: true)let normalIsComposition = try engine.block.getBool(normal, property: "group/composition")print(normalIsComposition) // trueRunning the Children Faster#
setPlaybackSpeed(_:speed:) 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 pagelet fast = try createLane(y: 400, color: .rgba(r: 0.2, g: 0.4, b: 0.9, a: 1))try engine.block.setBool(fast, property: "group/composition", value: true)try engine.block.setPlaybackSpeed(fast, speed: 2)let fastSpeed = try engine.block.getPlaybackSpeed(fast)print(fastSpeed) // 2.0Starting the Children With the Group#
The time offsets of the children are measured on the composition clock, which starts with the group. setTimeOffset(_:offset:) 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 itlet late = try createLane(y: 650, color: .rgba(r: 0.9, g: 0.3, b: 0.4, a: 1))try engine.block.setBool(late, property: "group/composition", value: true)try engine.block.setTimeOffset(late, offset: 1)try engine.block.setDuration(late, duration: 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 effectlet plain = try createLane(y: 900, color: .rgba(r: 0.2, g: 0.7, b: 0.3, a: 1))try engine.block.setPlaybackSpeed(plain, speed: 2)let plainIsComposition = try engine.block.getBool(plain, property: "group/composition")print(plainIsComposition) // falseHandling Errors#
A group runs at 0.25 times the page speed or faster. A lower speed throws an EngineError whose catalogCode names the cause, and the speed stays unchanged.
var errorCode: EngineErrorCode?do { // A group runs at 0.25 times the page speed or faster try engine.block.setPlaybackSpeed(normal, speed: 0.1)} catch let error as EngineError { errorCode = error.catalogCode print(errorCode == .editorPlaybackSpeedTooLow) // true}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 secondstry engine.block.setPlaybackTime(page, time: 1.5)let pngData = try await engine.block.export(page, mimeType: .png)print(!pngData.isEmpty) // trueComposition 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/timeRemapwithplayback/timeRemapEnabledreplaces 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/compositionand 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 (
EngineErrorCode.editorPlaybackSpeedTooLow): use a speed of 0.25 or more. - The speed is not a number (
EngineErrorCode.blockValueNotFinite): pass a finite speed. - The speed has no effect: turn on
group/compositionfor 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#
Methods#
| Method | Description |
|---|---|
engine.block.create(_:) |
Create a graphic block |
engine.block.createShape(_:) |
Create a rect or ellipse shape |
engine.block.setShape(_:shape:) |
Give a graphic its shape |
engine.block.createFill(_:) |
Create a color fill |
engine.block.setColor(_:property:color:) |
Set the fill color |
engine.block.setFill(_:fill:) |
Give a graphic its fill |
engine.block.setWidth(_:value:) |
Set the width of a block |
engine.block.setHeight(_:value:) |
Set the height of a block |
engine.block.setPositionX(_:value:) |
Set the x position of a block |
engine.block.setPositionY(_:value:) |
Set the y position of a block |
engine.block.appendChild(to:child:) |
Add a block to the page |
engine.block.setDuration(_:duration:) |
Set the duration of a block or group |
engine.block.setKeyframes(_:property:keyframes:mode:) |
Move the ball |
engine.block.group(_:) |
Group blocks into a group |
engine.block.setBool(_:property:value:) |
Turn composition on or off |
engine.block.getBool(_:property:) |
Check whether a group is a composition group |
engine.block.setPlaybackSpeed(_:speed:) |
Set the speed of a group, 0.25 or more |
engine.block.getPlaybackSpeed(_:) |
Read the speed of a group |
engine.block.setTimeOffset(_:offset:) |
Start a group later on the page |
engine.block.setPlaybackTime(_:time:) |
Seek before you export a frame |
engine.block.export(_:mimeType:options:onPreExport:uriResolver:onProgress:) |
Export the frame as PNG |
Properties#
| Property | Type | Description |
|---|---|---|
group/composition |
Bool | Run the children of a group on the group’s own clock. Default off |
playback/speed |
Float | Speed of the composition clock, 0.25 or more. Default 1 |
Next Steps#
- Keyframes: animate the children of a composition group.
- Create Animations: add preset animations to blocks.