diff --git a/demo/content.ts b/demo/content.ts index 0270001..e6e4c49 100644 --- a/demo/content.ts +++ b/demo/content.ts @@ -878,10 +878,11 @@ addCanvas( animation.transition({ points: pointy, duration: 1000, - callback: () => animation.transition({ - points: wobblyGerm, - duration: 4000, - }), + callback: () => + animation.transition({ + points: wobblyGerm, + duration: 4000, + }), }); animate(() => drawClosed(ctx, animation.renderPoints(), true)); diff --git a/demo/example.ts b/demo/example.ts index fe81ebe..1c60185 100644 --- a/demo/example.ts +++ b/demo/example.ts @@ -1,5 +1,6 @@ import {CanvasKeyframe, canvasPath, wigglePreset} from "../public/animate"; -import {drawDebugClosed, drawPoint} from "./internal/canvas"; +import {drawHandles, drawPoint} from "./internal/canvas"; +import {isDebug} from "./internal/debug"; import {colors} from "./internal/layout"; // Fetch reference to example container. @@ -32,22 +33,12 @@ const renderFrame = () => { ctx.fillStyle = colors.highlight; ctx.strokeStyle = colors.highlight; - // Debug - if (false) { - const p = (animation as any).renderPoints(); - - drawDebugClosed(ctx, p, 60); - if (p.length) drawPoint(ctx, p[0], 400); - - const handleLength = p[0]?.handleIn.length; - if (handleLength < 100) { - console.log("shorty detected: ", handleLength); - return; + if (isDebug()) { + const points = animation.renderPoints(); + for (const point of points) { + drawPoint(ctx, point, 2); + drawHandles(ctx, point, 1); } - - const fps = 20; - setTimeout(() => requestAnimationFrame(renderFrame), 1000 / fps); - return; } ctx.fill(animation.renderFrame()); @@ -68,7 +59,7 @@ const genWiggle = (transition: number) => { size, }, {}, - {speed: 2, initialTransition: transition, initialTimingFunction: "ease"}, + {speed: 2, initialTransition: transition}, ); }; @@ -93,7 +84,7 @@ const genFrame = (overrides: any = {}): CanvasKeyframe => { // Callback for every frame which starts transition to a new frame. const loopAnimation = (): void => { extraPoints = 0; - genWiggle(4000); + genWiggle(2000); }; // Quickly animate to a new frame when canvas is clicked. diff --git a/demo/internal/debug.ts b/demo/internal/debug.ts index 47696b5..302dfaa 100644 --- a/demo/internal/debug.ts +++ b/demo/internal/debug.ts @@ -1,5 +1,5 @@ // If debug is initially set to false it will not be toggleable. -let debug = true && location.hostname === "localhost"; +let debug = window.location.search.includes("debug") && location.hostname === "localhost"; export const isDebug = () => debug; const debugListeners: ((debug: boolean) => void)[] = []; diff --git a/internal/animate/state.ts b/internal/animate/state.ts index f7741d3..594b72f 100644 --- a/internal/animate/state.ts +++ b/internal/animate/state.ts @@ -55,7 +55,7 @@ export const statefulAnimationGenerator = ( // Invoke callback if defined and the first time the frame is reached. if (renderOutput.lastFrameId && frameCallbackStore[renderOutput.lastFrameId]) { - frameCallbackStore[renderOutput.lastFrameId](); + setTimeout(frameCallbackStore[renderOutput.lastFrameId]); delete frameCallbackStore[renderOutput.lastFrameId]; } diff --git a/public/animate.ts b/public/animate.ts index 261c9d8..1afa56b 100644 --- a/public/animate.ts +++ b/public/animate.ts @@ -11,6 +11,8 @@ import { } from "../internal/check"; import {BlobOptions, CanvasOptions} from "./blobs"; import {noise} from "../internal/rand"; +import {interpolateBetween} from "../internal/animate/interpolate"; +import {prepare} from "../internal/animate/prepare"; interface Keyframe { // Duration of the keyframe animation in milliseconds. @@ -82,15 +84,9 @@ export interface TimestampProvider { export interface WiggleOptions { // Speed of the wiggle movement. Higher is faster. speed: number; - // Delay before the first wiggle frame. - // Default: 0 - initialDelay?: number; // Length of the transition from the current state to the wiggle blob. // Default: 0 initialTransition?: number; - // Interpolation function. - // Default: linear - initialTimingFunction?: Keyframe["timingFunction"]; } const canvasPointGenerator = (keyframe: CanvasKeyframe | CanvasCustomKeyframe): Point[] => { @@ -147,27 +143,61 @@ export const wigglePreset = ( canvasOptions: CanvasOptions, wiggleOptions: WiggleOptions, ) => { - const leapSize = 0.01 * wiggleOptions.speed; - // Interval at which a new sample is taken. // Multiple of 16 to do work every N frames. const intervalMs = 16 * 5; - + const leapSize = 0.01 * wiggleOptions.speed; const noiseField = noise(String(blobOptions.seed)); + const transitionFrameCount = 1 + Math.min((wiggleOptions.initialTransition || 0) / intervalMs); + let transitionStartFrame = animation.renderPoints(); + let count = 0; - const loopAnimation = (first?: boolean, delay?: number) => { + const loopAnimation = () => { count++; - animation.transition({ - duration: first ? wiggleOptions.initialTransition || 0 : intervalMs, - delay: delay || 0, - timingFunction: (first && wiggleOptions.initialTimingFunction) || "linear", - canvasOptions, - points: genFromOptions(blobOptions, (index) => { - return noiseField(leapSize * count, index); - }), - callback: loopAnimation, + + // Constantly changing blob. + const noiseBlob = genFromOptions(blobOptions, (index) => { + return noiseField(leapSize * count, index); }); + + if (count < transitionFrameCount) { + // Create intermediate frame between the current state and the + // moving noiseBlob target. + const [preparedStartPoints, preparedEndPoints] = prepare( + transitionStartFrame, + noiseBlob, + { + rawAngles: true, + divideRatio: 1, + }, + ); + const progress = 1 / (transitionFrameCount - count); + const targetPoints = interpolateBetween( + progress, + preparedStartPoints, + preparedEndPoints, + ); + transitionStartFrame = targetPoints; + + animation.transition({ + duration: intervalMs, + delay: 0, + timingFunction: "linear", + canvasOptions, + points: targetPoints, + callback: loopAnimation, + }); + } else { + animation.transition({ + duration: intervalMs, + delay: 0, + timingFunction: "linear", + canvasOptions, + points: noiseBlob, + callback: loopAnimation, + }); + } }; - loopAnimation(true, wiggleOptions.initialDelay); + loopAnimation(); };