Skip to content

Commit 5acb158

Browse files
committed
fix edge cases in animation state management util
1 parent 967c080 commit 5acb158

File tree

3 files changed

+29
-68
lines changed

3 files changed

+29
-68
lines changed

internal/animate/state.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,19 @@ export const renderFramesAt = (input: RenderInput): RenderOutput => {
7474

7575
// Use and cache prepared points for current interpolation.
7676
let preparedStartPoints: Point[] | undefined =
77-
renderCache[startKeyframe.id].preparedStartPoints;
78-
let preparedEndPoints: Point[] | undefined = renderCache[endKeyframe.id].preparedEndPoints;
77+
renderCache[startKeyframe.id]?.preparedStartPoints;
78+
let preparedEndPoints: Point[] | undefined = renderCache[endKeyframe.id]?.preparedEndPoints;
7979
if (!preparedStartPoints || !preparedEndPoints) {
8080
[preparedStartPoints, preparedEndPoints] = prepare(
8181
startKeyframe.initialPoints,
8282
endKeyframe.initialPoints,
8383
{rawAngles: false, divideRatio: 1},
8484
);
85+
86+
renderCache[startKeyframe.id] = renderCache[startKeyframe.id] || {};
8587
renderCache[startKeyframe.id].preparedStartPoints = preparedStartPoints;
88+
89+
renderCache[endKeyframe.id] = renderCache[endKeyframe.id] || {};
8690
renderCache[endKeyframe.id].preparedEndPoints = preparedEndPoints;
8791
}
8892

@@ -91,12 +95,15 @@ export const renderFramesAt = (input: RenderInput): RenderOutput => {
9195
(input.timestamp - startKeyframe.timestamp) /
9296
(endKeyframe.timestamp - startKeyframe.timestamp);
9397

98+
// Keep progress withing expected range (ex. division by 0).
99+
const clampedProgress = Math.max(0, Math.min(1, progress));
100+
94101
// Apply timing function of end frame.
95-
const adjustedProgress = endKeyframe.timingFunction(progress);
102+
const adjustedProgress = endKeyframe.timingFunction(clampedProgress);
96103

97104
return {
98105
renderCache,
99-
lastFrameId: startKeyframe.id,
106+
lastFrameId: clampedProgress === 1 ? endKeyframe.id : startKeyframe.id,
100107
points: interpolateBetween(adjustedProgress, preparedStartPoints, preparedEndPoints),
101108
};
102109
};
@@ -115,6 +122,22 @@ export const transitionFrames = <T extends Keyframe>(
115122

116123
// Add current state as initial frame.
117124
const currentState = renderFramesAt(input);
125+
if (currentState.lastFrameId === null) {
126+
// If there is currently no shape being rendered, use a point in the
127+
// center of the next frame as the initial point.
128+
const firstShape = input.shapeGenerator(input.newFrames[0]);
129+
let firstShapeCenterPoint: Point = {
130+
x: 0,
131+
y: 0,
132+
handleIn: {angle: 0, length: 0},
133+
handleOut: {angle: 0, length: 0},
134+
};
135+
for (const point of firstShape) {
136+
firstShapeCenterPoint.x += point.x / firstShape.length;
137+
firstShapeCenterPoint.y += point.y / firstShape.length;
138+
}
139+
currentState.points = [firstShapeCenterPoint, firstShapeCenterPoint, firstShapeCenterPoint];
140+
}
118141
newInternalFrames.push({
119142
id: genId(),
120143
initialPoints: currentState.points,

internal/animate/timing.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const elasticOut = (s: number): TimingFunc => (p) => {
2727
};
2828

2929
// https://www.desmos.com/calculator/fqisoq1kuw
30+
// TODO lower magnitude/amount of bounce.
31+
// TODO Rename in/out to avoid confusion (out is out from point before).
3032
export const timingFunctions = {
3133
linear,
3234
easeIn,

public/animate.ts

Lines changed: 0 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -88,67 +88,3 @@ export const canvasPath = (): CanvasAnimation => {
8888

8989
return {renderFrame, transition};
9090
};
91-
92-
/////////////
93-
// example //
94-
/////////////
95-
96-
// TODO remove
97-
const blobs2Animate = {canvasPath};
98-
99-
const canvas = document.getElementById("canvas") as any;
100-
const ctx = canvas.getContext("2d");
101-
102-
const animation = blobs2Animate.canvasPath();
103-
window.requestAnimationFrame(() => {
104-
ctx.fill(animation.renderFrame());
105-
});
106-
107-
const loop = () => {
108-
animation.transition(
109-
{
110-
duration: 2000,
111-
blobOptions: {
112-
extraPoints: 3,
113-
randomness: 3,
114-
seed: "blob1",
115-
size: 200,
116-
},
117-
},
118-
{
119-
duration: 2000,
120-
callback: loop,
121-
blobOptions: {
122-
extraPoints: 3,
123-
randomness: 3,
124-
seed: "blob2",
125-
size: 200,
126-
},
127-
},
128-
);
129-
};
130-
131-
animation.transition({
132-
duration: 0,
133-
callback: loop,
134-
blobOptions: {
135-
extraPoints: 3,
136-
randomness: 3,
137-
seed: "start",
138-
size: 200,
139-
},
140-
});
141-
142-
const button = document.getElementById("button") as any;
143-
button.onclick(() => {
144-
animation.transition({
145-
duration: 100,
146-
callback: loop,
147-
blobOptions: {
148-
extraPoints: 3,
149-
randomness: 7,
150-
seed: "onClick",
151-
size: 200,
152-
},
153-
});
154-
});

0 commit comments

Comments
 (0)