Keyframes
Animate item position, size, rotation, opacity, masks, blur, counters, and text with the keyframe engine
Keyframes animate the properties of an item over time. Each item can have a keyframes field. The field holds one list of keyframes for each animated property. Between two keyframes, the editor blends the value with an ease.
The editor player, the browser export, and the cloud renderer all read the same keyframes.
Data model
import type {
Keyframe,
ItemKeyframes,
KeyframeProperty,
} from '@reactvideoeditor/react-video-editor/types';/** One list for each animated property, sorted by frame. */
type ItemKeyframes = Partial<Record<KeyframeProperty, Keyframe[]>>;
interface Keyframe {
id: string;
/** The frame from the start of the item, not from the start of the video. */
frame: number;
/** The value at the frame. A `text` keyframe has the dummy value 0. */
value: number;
/** The text of a `text` keyframe. */
text?: string;
/** How the value moves INTO this keyframe. Default: 'linear'. */
ease?: AnimationEaseName | 'hold';
/** A cubic bezier [x1, y1, x2, y2] for the move INTO this keyframe. It replaces `ease`. */
bezier?: [number, number, number, number];
}These rules apply:
- Before the first keyframe, the property has the value of the first keyframe.
- After the last keyframe, the property has the value of the last keyframe.
- If a property has no keyframes, the item uses its resting value, for example
leftorstyles.opacity. - If a property does not apply to the item type, the editor ignores its keyframes.
Properties
The table shows each KeyframeProperty. The Resting value is the value when the property has no keyframes. The K column shows if the K shortcut and the Add keyframe button key the property.
| Property | Label | Unit | Resting value | Applies to | K |
|---|---|---|---|---|---|
left | X | px | item.left | All items | Yes |
top | Y | px | item.top | All items | Yes |
width | Width | px | item.width | All items | Yes |
height | Height | px | item.height | All items | Yes |
rotation | Rotation | ° | item.rotation | All items | Yes |
opacity | Opacity | 0–1 | styles.opacity, else 1 | All items | Yes |
scale | Scale | × | 1 (no resting field) | All items | Yes |
scaleX | Stretch X | × | 1 | Visual items | No |
scaleY | Stretch Y | × | 1 | Visual items | No |
count | Count | — | counter.value, else 0 | Text items with counter | Yes |
maskCircleRadius | Circle R | % | 150 | Visual items | No |
maskCircleX | Circle X | % | 50 | Visual items | No |
maskCircleY | Circle Y | % | 50 | Visual items | No |
maskInsetTop | Inset T | % | 0 | Visual items | No |
maskInsetRight | Inset R | % | 0 | Visual items | No |
maskInsetBottom | Inset B | % | 0 | Visual items | No |
maskInsetLeft | Inset L | % | 0 | Visual items | No |
maskInsetRadius | Corner | % | 0 | Visual items | No |
skewX | Skew X | ° | 0 | Visual items | No |
skewY | Skew Y | ° | 0 | Visual items | No |
draw | Draw | % (0–100) | styles.draw, else 100 | Arc and line shapes | No |
brightness | Brightness | % (−100 to 100) | 0 | Images and videos | No |
blur | Blur | px | 0 | Text and shapes | No |
progress | Progress | % (0–100) | particles.progress, else 100 | Particles shapes | No |
shimmer | Shimmer | % (0–100) | particles.shimmer, else 0 | Particles shapes | No |
text | Text | — | content | Text items with no counter | No |
"Visual items" are all items except audio and zoom items.
Scale, stretch, and origin
scale multiplies the size of the item on the two axes. scaleX and scaleY multiply scale on one axis. Scale, stretch, and rotation turn around the origin point. Set the origin with originX and originY on the item, in % of the item box. The default is 50 (the centre).
Masks
The mask properties clip the item. All mask values are in %.
- Circle mask —
maskCircleXandmaskCircleYset the centre, in % of the item size.maskCircleRadiussets the radius. At150, the circle covers the full item from any centre in the item. - Inset mask —
maskInsetTop,maskInsetRight,maskInsetBottom, andmaskInsetLeftcut each side, in % of the item size. A side can go down to −50 %, so the mask can show more than the item box.maskInsetRadiussets the corner radius, in % of the shorter side.
Animate a mask to make a reveal. For example, move maskInsetRight from 100 to 0 to wipe the item in from the left.
Skew, brightness, and blur
skewXandskewYslant the item, in degrees.brightnessputs a black shade (below 0) or a white shade (above 0) over an image or a video. The shade is a layer, so the browser export shows the same result as the player.blurblurs text and shapes, in px.
Blur on images and videos
The browser export does not draw a blur on images and videos. For this reason, the blur property applies only to text and shapes.
Eases
The ease of a keyframe controls the move from the previous keyframe into this keyframe.
| Ease | Motion |
|---|---|
linear | The same speed from start to end. This is the default. |
smooth | A slow start and a slow end. |
out | A fast start and a slow end. |
whip | A very slow start, a fast middle, and a very slow end. |
glide | A fast start and a long, soft end. |
breathe | A soft sine curve. |
back | Goes past the end value, then comes back. |
spring | Goes past the end value and bounces a small amount before it stops. |
hold | No blend. The value jumps at the keyframe. |
Keyframe values are not clamped, so back and spring can go past the end value.
For a custom curve, set bezier to [x1, y1, x2, y2], the same as CSS cubic-bezier(). If bezier is set, it replaces ease. The editor UI does not set bezier. Set it in your data. If a user changes the ease of the keyframe in the editor, the editor removes bezier.
Text keyframes
The text property changes the content of a text item over time. A text keyframe holds its text in the text field. Its value is always 0.
- Text does not blend. Each text holds until the next keyframe.
- Text keyframes always hold, so they have no ease.
- The text box uses the longest text for its size, so the box does not change size when the text changes.
- If the item has a
counter, the editor ignores itstextkeyframes.
"keyframes": {
"text": [
{ "id": "t1", "frame": 0, "value": 0, "text": "Ready" },
{ "id": "t2", "frame": 30, "value": 0, "text": "Set" },
{ "id": "t3", "frame": 60, "value": 0, "text": "Go!" }
]
}When a text item has text keyframes, the text field of the item panel edits the keyframe that holds at the playhead.
Count number
A text item with a counter shows a number in place of its content. The count keyframes animate the number.
interface TextCounter {
/** The number when the item has no `count` keyframes. */
value: number;
decimals?: number;
prefix?: string;
suffix?: string;
/** The thousands separator, for example ','. An empty string or no value gives none. */
separator?: string;
/** Shows the value as seconds in the mm:ss format. */
clock?: boolean;
}decimalscan go from 0 to 6.- If
separatoris'.', the decimal mark is','. - A minus sign goes before the prefix, for example
-$1,200.50. - If
clockistrue, the editor rounds the value to whole seconds and shows it asmm:ss. The minutes can go past 59.
In the editor, the Count number switch in the Animate tab of a text item turns on the counter. The switch adds two count keyframes: 0 at the item start and 100 at 1 second (or at the last frame of a shorter item). The section also has Start, End, Decimals, Prefix, Suffix, Separator, and Clock controls.
Editor UI
Markers on item bars
The timeline shows a small triangle on the item bar at each frame where the item has a keyframe.
- Click a marker to select the keyframes at that frame. The playhead moves to the frame, and the Animate tab opens.
- Shift-click a marker to add its keyframes to the selection.
- Drag a marker to move the selected keyframes together. The editor does not do a move that takes a keyframe out of the item or onto another keyframe of the same property.
To hide the markers, turn off Keyframe markers in the Settings panel. The setting is on by default.
Animate tab
Each item panel has an Animate tab. The tab shows the enter and exit animations, then the Keyframes section:
- Add keyframe keys the full item at the playhead, the same as K. The playhead must be on the item.
- The list shows the time of each keyframe. A click selects the keyframes at that time and moves the playhead to it. Shift-click adds to the selection.
- Ease sets the ease of the selected keyframes.
- Delete removes the selected keyframes.
- Advanced shows one row for each property that applies to the item. Each row shows the value at the playhead and has buttons to go to the previous keyframe, add or remove a keyframe, go to the next keyframe, and clear all keyframes of the property. For a text item, the Text row comes first.
Canvas edits write keyframes
If a property has keyframes, a canvas edit writes a keyframe at the playhead. This applies to a drag, a resize, a rotation, and panel edits such as opacity.
- If a keyframe is at the playhead, the edit changes its value. The keyframe keeps its ID and ease.
- If no keyframe is at the playhead, the edit adds a keyframe.
- If a property has no keyframes, the edit changes the resting value.
- If the playhead is not on the item, an edit to a property with keyframes has no effect.
Shortcuts
| Keys | Action |
|---|---|
K | Add a keyframe for the selected item at the playhead |
Backspace / Delete | Delete the selected keyframes. If no keyframes are selected, delete the selected items |
Alt + ← / Alt + → | Go to the previous or next keyframe of the selected item |
Escape | Clear the keyframe selection. If no keyframes are selected, clear the item selection |
See Editing and shortcuts for all shortcuts.
The disableVideoKeyframes prop
ReactVideoEditor accepts a disableVideoKeyframes prop. In the current version, this prop has no effect. The editor stores the value, but no part of the editor reads it. The prop does not turn off keyframes. It does not turn off the keyframe markers.
Example
This text item moves from the left, fades in, and grows with a custom curve:
{
"id": "title-1",
"type": "text",
"from": 0,
"durationInFrames": 90,
"left": 100,
"top": 450,
"width": 600,
"height": 180,
"rotation": 0,
"content": "Hello",
"styles": {
"fontSize": "4rem",
"fontWeight": "700",
"color": "#ffffff",
"backgroundColor": "transparent",
"fontFamily": "Inter",
"fontStyle": "normal",
"textDecoration": "none"
},
"keyframes": {
"left": [
{ "id": "k1", "frame": 0, "value": 100 },
{ "id": "k2", "frame": 30, "value": 660, "ease": "out" }
],
"opacity": [
{ "id": "k3", "frame": 0, "value": 0 },
{ "id": "k4", "frame": 20, "value": 1, "ease": "smooth" }
],
"scale": [
{ "id": "k5", "frame": 0, "value": 0.8 },
{ "id": "k6", "frame": 30, "value": 1, "bezier": [0.16, 1, 0.3, 1] }
]
}
}This text item counts from 0 to 1,250 in 2 seconds at 30 fps:
{
"type": "text",
"content": "",
"counter": { "value": 0, "decimals": 0, "prefix": "$", "separator": "," },
"keyframes": {
"count": [
{ "id": "c1", "frame": 0, "value": 0 },
{ "id": "c2", "frame": 60, "value": 1250, "ease": "out" }
]
}
}Custom compositions
If you render with your own composition, wrap each item in LayerBox. If you do not use LayerBox, keyframes do not move the items. See Custom compositions.