Skip to content

Commit e6ef6b7

Browse files
committed
refactor: dropped web worker
1 parent 2e2a51a commit e6ef6b7

File tree

40 files changed

+463
-625
lines changed

40 files changed

+463
-625
lines changed

src/defaults/index.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,26 @@ export const props: Props = {
66
css: false,
77
appear: false,
88
tag: undefined,
9-
enterFromClass: undefined,
10-
enterActiveClass: undefined,
11-
enterToClass: undefined,
12-
appearFromClass: undefined,
13-
appearActiveClass: undefined,
14-
appearToClass: undefined,
15-
leaveFromClass: undefined,
16-
leaveActiveClass: undefined,
17-
leaveToClass: undefined,
9+
// enterFromClass: undefined,
10+
// enterActiveClass: undefined,
11+
// enterToClass: undefined,
12+
// appearFromClass: undefined,
13+
// appearActiveClass: undefined,
14+
// appearToClass: undefined,
15+
// leaveFromClass: undefined,
16+
// leaveActiveClass: undefined,
17+
// leaveToClass: undefined,
1818
config: "fade",
1919
delay: undefined,
2020
duration: undefined,
2121
ease: "linear",
2222
group: false,
2323
mode: "out-in",
24-
spring: "wobbly",
24+
spring: undefined,
2525
retainFinalStyle: false,
2626
};
2727

2828
export const options: Options = {
2929
componentName: "UiTransition",
30-
globals: ["sleep", "getSpring"],
30+
globals: ["getSpring"],
3131
};

src/getSpring/calcSpring/index.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { DynamicObject } from "src/types";
2+
import { CalcSpring } from "./type";
3+
4+
const saved: DynamicObject<number[]> = {};
5+
6+
const calcSpring: CalcSpring = (arg = {}) => {
7+
const {
8+
tension = 320,
9+
friction = 25,
10+
mass = 1,
11+
precision = 0.01,
12+
velocity = 0,
13+
stopAttempt = 20,
14+
} = arg;
15+
16+
// path to save this `createSpring` call, to avoid repetition
17+
const savePath = `${tension}~${friction}~${mass}~${precision}~${stopAttempt}~${velocity}`;
18+
19+
if (saved?.[savePath]) {
20+
return Promise.resolve(saved[savePath]);
21+
}
22+
23+
// get springs from 0 to 1;
24+
// then interpolate it with any other value(s)
25+
let current = 0;
26+
27+
const to = 1;
28+
29+
// initial velocity
30+
let _velocity = velocity;
31+
32+
// frames created; on every change in the `current` position, a new frame is created
33+
let frames = 0;
34+
35+
// `halt` holds how many times the difference between the current spring and previous spring is <= the `precision`.
36+
// This means having a higher precision (closer to 1) will result in a very short spring. While having a lower precision (closer to 0) will result in a smoother, but longer spring.
37+
let halt = 0;
38+
39+
// `stoppingAttempt` is how many times the spring should halt (check `halt` above), before it comes to a stop. This means if you need the final moments of the spring to go back and forth a few times, use a smaller number (> 0). But if you need a fine tuned spring ending, use a higher `stoppingAttempt`.
40+
const stoppingAttempt = Math.max(Math.abs(stopAttempt), 1) || 5;
41+
42+
// `positions` holds an array of spring values. Each item in this array is considered a frame.
43+
const positions: number[] = [];
44+
45+
// to achieve 60fps. This shouldn't be changed. However, reducing the frames to say 1/30 will result in a choppy looking spring (not smooth). While increasing it eg (1/120) will make the spring unnecessarily long with no visible difference from the 1/60 frames.
46+
const FPS = 1 / 60;
47+
48+
const maxFrames = Math.floor(FPS * 1000 * 1000);
49+
50+
for (let step = 0; step <= maxFrames; step += 1) {
51+
const springForce = -tension * (current - to);
52+
53+
const frictionForce = -friction * _velocity;
54+
55+
const acceleration = (springForce + frictionForce) / mass;
56+
57+
_velocity += acceleration * FPS;
58+
59+
const nextValue = current + _velocity * FPS;
60+
61+
const stopping = Math.abs(nextValue - current) < Math.abs(precision);
62+
63+
if (stopping) {
64+
halt += 1;
65+
} else halt = 0;
66+
67+
if (halt >= stoppingAttempt) {
68+
positions.push(to);
69+
frames = step + 1;
70+
break;
71+
}
72+
73+
current = nextValue;
74+
75+
if (step == 0) {
76+
positions.push(0);
77+
}
78+
79+
positions.push(current);
80+
}
81+
82+
if (frames == 0) {
83+
frames = 1000;
84+
positions.push(to);
85+
}
86+
87+
// save results to avoid repetition
88+
return Promise.resolve((saved[savePath] = positions));
89+
};
90+
91+
export default calcSpring;

src/getSpring/calcSpring/type.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { SpringObject } from "src/props/types";
2+
3+
export type CalcSpring = (arg?: SpringObject) => Promise<number[]>;

src/getSpring/index.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { DynamicObject } from "src/types";
2+
import calcSpring from "./calcSpring";
3+
import interpolate from "./interpolate";
4+
import { GetSpring, GetSpringOutput } from "./type";
5+
6+
const saved: DynamicObject<GetSpringOutput> = {};
7+
8+
// calculates spring values and returns cssText, and duration
9+
const getSpring: GetSpring = async function (
10+
frame,
11+
config = {},
12+
phase,
13+
keyframeName
14+
) {
15+
const savePath = `${
16+
Array.isArray(frame) ? frame.join() : frame
17+
}~${JSON.stringify(config)}~${phase}~${keyframeName}`;
18+
19+
if (saved[savePath]) {
20+
return saved[savePath];
21+
}
22+
23+
const spring = await calcSpring(config);
24+
25+
const interpolatedValues = interpolate(spring, frame, phase, keyframeName);
26+
27+
return (saved[savePath] = interpolatedValues);
28+
};
29+
30+
export default getSpring;

src/getSpring/inject/index.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import extractSpring from "../../utils/extractSpring";
2+
import calcSpring from "../calcSpring";
3+
import calculateSteps from "../interpolate/calculateSteps";
4+
import { InjectGetSpring } from "./type";
5+
6+
const injectGetSpring: InjectGetSpring = async (frame, config) => {
7+
if (Array.isArray(frame) && frame.length) {
8+
const springConfig = config
9+
? extractSpring(config, config, "leave")
10+
: undefined;
11+
12+
const springValues = await calcSpring(springConfig);
13+
14+
// parse non 2d array frames to 2d arrays;
15+
16+
// check to see if the first value isn't an array;
17+
const is2d = Array.isArray(frame[0]);
18+
19+
// create 2d array
20+
const frameAs2d = is2d
21+
? (frame as number[][])
22+
: [[frame[0] || 0, frame[1] || 0]];
23+
24+
// store interpolated values. If a 2d array was passed, return 2d of springs, else return [...springs]
25+
const output: (number[] | number[][])[] = [];
26+
27+
frameAs2d.forEach((row) => {
28+
const values: number[] = [];
29+
30+
springValues.forEach((spring) => {
31+
values.push(calculateSteps(row[0], row[1], spring) as number);
32+
});
33+
34+
if (is2d) {
35+
output.push([values]);
36+
} else {
37+
output.push(values);
38+
}
39+
});
40+
41+
return output;
42+
}
43+
44+
return [];
45+
};
46+
47+
export default injectGetSpring;

src/getSpring/inject/type.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { Spring } from "src/props/types";
2+
3+
export type InjectGetSpring = (
4+
frame: number[] | number[][],
5+
config?: Spring
6+
) => Promise<(number[] | number[][])[]>;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { DynamicObject } from "src/types";
2+
3+
export const stepper = (from: number, to: number, frame: number) =>
4+
(from - to) * frame + to;
5+
6+
const saved: DynamicObject<number | number[]> = {};
7+
8+
export default function calculateSteps(
9+
from: number | number[],
10+
to: number | number[],
11+
frame: number
12+
): number | number[] {
13+
const savePath = `${from}~${to}~${frame}`;
14+
15+
if (saved[savePath]) {
16+
return saved[savePath];
17+
}
18+
19+
const fromArray = Array.isArray(from);
20+
const toArray = Array.isArray(to);
21+
22+
if (fromArray || toArray) {
23+
// check if they are same length; else bail
24+
if (fromArray && toArray) {
25+
if (from.length !== to.length) {
26+
return [];
27+
}
28+
29+
const steps = [];
30+
31+
for (let index = 0; index < from.length; index++) {
32+
steps.push(stepper(to[index], from[index], frame));
33+
}
34+
35+
return (saved[savePath] = steps);
36+
}
37+
38+
return [];
39+
} else {
40+
return (saved[savePath] = stepper(to, from, frame));
41+
}
42+
}

src/getSpring/interpolate/index.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { DynamicObject } from "../../types";
2+
import { kebabCase } from "../../utils";
3+
import { GetSpringOutput } from "../type";
4+
import calculateSteps from "./calculateSteps";
5+
import { Interpolate } from "./type";
6+
7+
const saved: DynamicObject<GetSpringOutput> = {};
8+
9+
// interpolate values from frames to create CSSKeyframes;
10+
const interpolate: Interpolate = (springValues, frame, phase, keyframeName) => {
11+
const savePath = `${springValues}~${
12+
Array.isArray(frame) ? frame.join() : frame
13+
}~${phase}~${keyframeName}`;
14+
15+
if (saved[savePath]) {
16+
return saved[savePath] as GetSpringOutput;
17+
}
18+
19+
const springs: DynamicObject<string | number>[] = springValues
20+
.map((val) => {
21+
if (Array.isArray(frame)) {
22+
const output: DynamicObject<string | number> = {};
23+
24+
frame.forEach((item) => {
25+
Object.assign(
26+
output,
27+
item(
28+
(from: number | number[], to: number | number[]) =>
29+
calculateSteps(from, to, val),
30+
phase
31+
)
32+
);
33+
});
34+
35+
return output;
36+
} else {
37+
const output = frame(
38+
(from: number | number[], to: number | number[]) =>
39+
calculateSteps(from, to, val),
40+
phase
41+
);
42+
43+
return output;
44+
}
45+
})
46+
.filter((x) => Object.keys(x).length);
47+
48+
let cssText = "";
49+
50+
let duration = 0;
51+
52+
if (springs.length) {
53+
duration = (springs.length / 60) * 1000;
54+
55+
cssText = `@keyframes ${keyframeName}{`;
56+
57+
// helper to get the current frame index (keyframe %)
58+
const frameIndex = (length: number, index: number): number =>
59+
(100 / length) * index;
60+
61+
for (let index = 0; index < springs.length; index++) {
62+
const getFrameIndex = `${frameIndex(springs.length - 1, index)}%`;
63+
64+
cssText += `${getFrameIndex}{`;
65+
66+
for (const key in springs[index]) {
67+
cssText += `${kebabCase(key)}:${springs[index][key]};`;
68+
}
69+
70+
cssText += `}`;
71+
}
72+
73+
cssText += "}";
74+
}
75+
76+
return {
77+
cssText,
78+
duration,
79+
};
80+
};
81+
82+
export default interpolate;

src/getSpring/interpolate/type.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { AnimPhase, Frame } from "src/types";
2+
import { GetSpringOutput } from "../type";
3+
4+
export type Interpolate = (
5+
spring: number[],
6+
frame: Frame | Frame[],
7+
phase: AnimPhase,
8+
keyframeName: string
9+
) => GetSpringOutput;

src/getSpring/type.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { SpringObject } from "src/props/types";
2+
import { AnimPhase, Frame } from "src/types";
3+
4+
export interface GetSpringOutput {
5+
cssText: string;
6+
duration: number;
7+
}
8+
9+
export type GetSpring = (
10+
frame: Frame | Frame[],
11+
config: SpringObject,
12+
phase: AnimPhase,
13+
keyframeName: string
14+
) => Promise<GetSpringOutput>;

0 commit comments

Comments
 (0)