animare
Version:
Advanced animation library for modern JavaScript.
108 lines (79 loc) • 3.14 kB
text/typescript
/** - Converts a path string to two dimensional array `[[M], ...[C]]` */
function parsePointsFromPathString(path: string): number[][] {
const pathData = path.match(/-?[0-9.]+/g)?.map(parseFloat);
const points: number[][] = [];
if (!pathData) return points;
points.push([pathData[0], pathData[1]]); // M points
// C points
for (let i = 2; i < pathData.length; i += 6) {
points.push([pathData[i], pathData[i + 1], pathData[i + 2], pathData[i + 3], pathData[i + 4], pathData[i + 5]]);
}
return points;
}
/**
* - Convert to relative value ->
* - Flip the Y points ->
* - Store only Cubic curves with starting, control, and ending points (without the first M command).
*/
function preparePointsForAnimation(array: number[][]): number[][] {
const results: number[][] = [];
let x = 0;
let y = 0;
for (let i = 0; i < array.length; i++) {
const curve = array[i];
const c1x = curve[0];
const c1y = 1 - curve[1];
if (!i) {
x = c1x;
y = c1y;
continue;
}
const c2x = curve[2];
const c2y = 1 - curve[3];
const px = curve[4];
const py = 1 - curve[5];
results.push([x, y, c1x, c1y, c2x, c2y, px, py]);
x = px;
y = py;
}
return results;
}
export function generateEasingFunctionFromString(path: string) {
const points = parsePointsFromPathString(path);
const curves = preparePointsForAnimation(points);
const epsilon = 1e-6; // Desired precision on the computation.
return (t: number) => {
// Special case start and end.
if (t === 0) return curves[0][1]; // The Y-coordinate of the first point of the first curve
if (t === 1) return curves[curves.length - 1][7]; // The Y-coordinate of the end point of the last curve
let from = 0;
for (let i = 0; i < curves.length; i++) {
const [p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y] = curves[i];
if (t < from || t > p1x) continue; // t is outside the range of the current curve
from = p1x;
// A binary search algorithm is used to determine the Y-coordinate value
// corresponding to a specified position on the X-coordinate.
let start = 0,
end = 1,
target = (start + end) / 2,
times = 0;
while (target >= start && target <= 1) {
// Compute the point (x, y) on the curve for a given time `target` [0 to 1]
const mt = 1 - target,
mt2 = mt * mt,
mt3 = mt2 * mt;
const x = p0x * mt3 + c0x * 3 * mt2 * target + c1x * 3 * mt * target * target + p1x * target ** 3;
const y = p0y * mt3 + c0y * 3 * mt2 * target + c1y * 3 * mt * target * target + p1y * target ** 3;
// If the point cannot be found within 50 attempts, a safe loop break will be triggered.
if (++times > 50) return y;
// Return the located Y-coordinate value.
if (Math.abs(x - t) <= epsilon) return y;
if (x >= t) end = target;
else start = target;
target = (start + end) / 2;
}
return 0;
}
return 0;
};
}