| name | Sequence implementation | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| overview | implement a new Sequence class that allows controling playback of mutiple AnimationGroups, and integrate it with Motion and Interact libraries. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| todos |
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isProject | false |
This plan implements the Sequence feature as specified in sequences-spec.md. The feature enables managing multiple Effects as a coordinated timeline with staggered delays.
classDiagram
class AnimationGroup {
+animations: Animation[]
+options: AnimationGroupOptions
+ready: Promise
+play()
+pause()
+reverse()
+cancel()
+onFinish()
}
class Sequence {
+animationGroups: AnimationGroup[]
+delay: number
+offset: number
+offsetEasing: function
+play()
+pause()
+reverse()
+cancel()
+onFinish()
-calculateOffsets()
}
Sequence --|> AnimationGroup : extends
Sequence "1" --> "*" AnimationGroup : manages
Create new file packages/motion/src/Sequence.ts:
- Extend
AnimationGroupto inherit the playback control API - Store
animations: AnimationGroup[]instead ofanimations: Animation[] - Add properties:
delay,offset,offsetEasing - Implement
calculateOffsets()method using the formula from spec:
const last = indices.at(-1);
indices.map((n) => (easing(n / last) * last * offset) | 0);- Override playback methods/properties where needed to delegate to child
AnimationGroupinstances - Apply calculated delay offsets to each effect's animation timing
Update packages/motion/src/types.ts:
export type SequenceOptions = {
delay?: number; // default 0
offset?: number; // default 0
offsetEasing?: string | ((p: number) => number);
};Update packages/motion/src/index.ts to export:
SequenceclassSequenceOptionstype
- Create this function in
packages/motion/src/motion.ts - Export it via
packages/motion/src/index.ts - It should have the following signature:
type AnimationGroupArgs = {
target: HTMLElement | HTMLElement[] | string | null;
options: AnimationOptions;
context?: Record<string, any>;
};
type getSequence = (
options: SequenceOptions,
animations: AnimationGroupArgs | AnimationGroupArgs[],
) => Sequence;The getSequence() funciton is passed animations: AnimationGroupArgs[] it creates a Sequence from a each effect definition in the array.
If an Effect in the array resolves to multiple elements, each resulting instance becomes an effect in the array.
Update packages/interact/src/types.ts:
// New SequenceOptions type
export type SequenceOptionsConfig = {
delay?: number; // default 0
offset?: number; // default 0
offsetEasing?: string | ((p: number) => number); // default linear
sequenceId?: string; // for referencing a reusable sequence declaration
};
// New SequenceConfig type
export type SequenceConfig = SequenceOptionsConfig & {
effects: (Effect | EffectRef)[];
};
// New SequenceConfigRef type
export type SequenceConfigRef = {
sequenceId: string;
} & {
delay?: number; // default 0
offset?: number; // default 0
offsetEasing?: string | ((p: number) => number); // default linear
};
// Update InteractConfig
export type InteractConfig = {
effects: Record<string, Effect>;
sequences?: Record<string, SequenceConfig>; // NEW: reusable sequences
conditions?: Record<string, Condition>;
interactions: Interaction[];
};
// Update Interaction - use mutually exclusive branches for proper type narrowing
export type Interaction = InteractionTrigger &
(
| {
effects: ((Effect | EffectRef) & { interactionId?: string })[];
sequences?: never; // effects-only: explicitly exclude sequences
}
| {
effects?: never; // sequences-only: explicitly exclude effects
sequences: (SequenceConfig | SequenceConfigRef)[];
}
| {
effects: ((Effect | EffectRef) & { interactionId?: string })[];
sequences: (SequenceConfig | SequenceConfigRef)[];
}
);Add sequences to the cache structure in packages/interact/src/types.ts:
export type InteractCache = {
effects: { [effectId: string]: Effect };
sequences: { [sequenceId: string]: SequenceConfig }; // NEW
conditions: { [conditionId: string]: Condition };
interactions: {
[path: string]: {
triggers: Interaction[];
effects: Record<string, (InteractionTrigger & { effect: Effect | EffectRef })[]>;
sequences: Record<string, (InteractionTrigger & { sequence: SequenceConfig })[]>;
interactionIds: Set<string>;
selectors: Set<string>;
};
};
};Modify packages/interact/src/core/Interact.ts:
- Parse
config.sequencesinto cache (similar toconfig.effects) - Process
interaction.sequencesarray:
- Resolve
sequenceIdreferences fromconfig.sequences - Process each effect within the sequence
- Generate unique IDs for sequence effects
- Track sequence membership for effects (needed for delay calculation)
Modify packages/interact/src/core/add.ts:
- When adding interactions, check if effects belong to a sequence
- Create
Sequenceinstance from@wix/motionfor grouped effects - Apply calculated delay offsets based on effect index in sequence
- Handle sequence removal (when conditions change or elements removed)
- Cache created
Sequenceinstances on a static propertyInteract.sequenceCache - Add endpoint on
Interactclass to get cachedSequenceinstances - Add a new endpoint on
Interact.getEffect()class that wrapsgetAnimation()andgetSequence()of@wix/motionand, depending on the provided arguments, either:- Returns a cached
Sequenceif there's one, or - Creates a new
Sequence, or - Returns an
AnimationGroup
- Returns a cached
Update relevant trigger handlers (e.g., viewEnter.ts, click.ts) to:
- Accept
Sequenceinstances in addition to individualAnimationGroup - Properly manage sequence playback (play, pause, cancel)
The offset calculation follows this algorithm:
function calculateOffsets(
count: number,
offset: number,
easingFn: (t: number) => number,
): number[] {
if (count <= 1) return [0];
const last = count - 1;
return Array.from({ length: count }, (_, i) => (easingFn(i / last) * last * offset) | 0);
}The calculated offsets are added to each effect's existing delay property.
- Initial Scope: Only
keyframeEffectandnamedEffecttypes (notcustomEffect) - Skip
alignProperty: Per spec, do not implement thealignproperty yet - Effect Removal: When an effect is removed (e.g., condition no longer matches), recalculate delays for remaining effects
- Sequence Removal: Optimize to avoid recalculating when entire sequence is removed
- No Element Target:
Sequencehas nokeyproperty - targeting is per-effect
- Unit tests for
Sequenceclass offset calculations - Unit tests for easing function integration
- Integration tests for sequence parsing in Interact
- E2E tests for staggered animations with various easing functions