55 | stop: () => void
56 | clean: () => void
57 | }
58 |
--------------------------------------------------------------------------------
/packages/core/src/utils/color.ts:
--------------------------------------------------------------------------------
1 | // TODO hsl ?
2 |
3 | import { substringMatch } from './string'
4 |
5 | // benchmarks https://jsbench.me/cql3n9zjhp/1
6 |
7 | export function parseColor(str: string) {
8 | let r: number, g: number, b: number, a: number | undefined, tmp: number | string[]
9 | if (str[0] === '#') {
10 | if ((str = str.substring(1)).length === 3) {
11 | str = str[0] + str[0] + str[1] + str[1] + str[2] + str[2] + 'FF'
12 | } else if (str.length === 6) {
13 | str += 'FF'
14 | }
15 | tmp = parseInt(str, 16)
16 |
17 | r = (tmp & 0xff000000) >>> 24
18 | g = (tmp & 0x00ff0000) >>> 16
19 | b = (tmp & 0x0000ff00) >>> 8
20 | a = (tmp & 0x000000ff) / 255
21 | } else {
22 | tmp = substringMatch(str, '(', ')').split(',')
23 | r = parseInt(tmp[0], 10)
24 | g = parseInt(tmp[1], 10)
25 | b = parseInt(tmp[2], 10)
26 |
27 | a = tmp.length > 3 ? parseFloat(tmp[3]) : 1
28 | }
29 |
30 | return [r, g, b, a]
31 | }
32 |
--------------------------------------------------------------------------------
/packages/core/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './math'
2 | export * from './object'
3 | export * from './string'
4 | export * from './color'
5 | export * from './interpolate'
6 |
--------------------------------------------------------------------------------
/packages/core/src/utils/interpolate.ts:
--------------------------------------------------------------------------------
1 | // mostly stolen from https://github.com/d3/d3-interpolate/blob/main/src/string.js
2 |
3 | const reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g
4 |
5 | export function parseNumbers(v: string) {
6 | return v.match(reA)!.map((k) => +k)
7 | }
8 |
9 | export function interpolate(b: string) {
10 | let bi = (reA.lastIndex = 0), // scan index for next number in b
11 | bm, // current match in b
12 | bs, // string preceding current number in b, if any
13 | i = -1, // index in s
14 | s: (string | null)[] = [], // string constants and placeholders
15 | q: number[] = [] // number interpolators
16 |
17 | // Coerce inputs to strings.
18 | b = b + ''
19 |
20 | // Interpolate pairs of numbers in a & b.
21 | while ((bm = reA.exec(b))) {
22 | if ((bs = bm.index) > bi) {
23 | // a string precedes the next number in b
24 | bs = b.slice(bi, bs)
25 | if (s[i]) s[i] += bs // coalesce with previous string
26 | else s[++i] = bs
27 | }
28 | // interpolate non-matching numbers
29 | s[++i] = null
30 | q.push(+bm)
31 | bi = reA.lastIndex
32 | }
33 |
34 | // Add remains of b.
35 | if (bi < b.length) {
36 | bs = b.slice(bi)
37 | if (s[i]) s[i] += bs // coalesce with previous string
38 | else s[++i] = bs
39 | }
40 |
41 | return {
42 | values: q,
43 | compute(values: number[]) {
44 | i = 0
45 | return s.map((k) => (k === null ? values[i++] : k)).join('')
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/core/src/utils/math.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/mattdesl/lerp
2 |
3 | export function lerp(v0: number, v1: number, t: number) {
4 | return v0 * (1 - t) + v1 * t
5 | }
6 |
7 | export function clamp(value: number, min: number, max: number) {
8 | return Math.max(min, Math.min(max, value))
9 | }
10 |
11 | function rubberband(distance: number, dimension: number, constant: number) {
12 | if (dimension === 0 || Math.abs(dimension) === Infinity) return Math.pow(distance, constant * 5)
13 | return (distance * dimension * constant) / (dimension + constant * distance)
14 | }
15 |
16 | export function rubberbandIfOutOfBounds(position: number, min: number, max: number, constant = 0.15) {
17 | if (constant === 0) return clamp(position, min, max)
18 | if (position < min) return -rubberband(min - position, max - min, constant) + min
19 | if (position > max) return +rubberband(position - max, max - min, constant) + max
20 | return position
21 | }
22 |
--------------------------------------------------------------------------------
/packages/core/src/utils/object.ts:
--------------------------------------------------------------------------------
1 | export function equal(v0: P, v1: P) {
2 | if (Array.isArray(v0)) {
3 | return v0.every((val, index) => val === (v1 as any)[index])
4 | }
5 | return v0 === v1
6 | }
7 |
8 | export function each
(array: P[], iterator: (v: P, i: number) => void) {
9 | if (Array.isArray(array)) {
10 | for (let i = 0; i < array.length; i++) iterator(array[i], i)
11 | } else {
12 | iterator(array, -1)
13 | }
14 | }
15 |
16 | export function map
(obj: P | P[] | Record, iterator: (v: P, key: string | number) => K) {
17 | if (typeof obj === 'object') {
18 | if (Array.isArray(obj)) {
19 | return obj.map(iterator)
20 | }
21 | return Object.entries(obj).map(([key, value]) => iterator(value, key))
22 | }
23 | return iterator(obj, -1) as any
24 | }
25 |
--------------------------------------------------------------------------------
/packages/core/src/utils/string.ts:
--------------------------------------------------------------------------------
1 | export function parseUnitValue(value: number | string): [number] | [number, string] {
2 | if (typeof value === 'number') return [value]
3 | const _value = parseFloat(value)
4 | const unit: string = value.substring(('' + _value).length)
5 | return [_value, unit]
6 | }
7 |
8 | export function substringMatch(str: string, from: string, to?: string) {
9 | const pos = str.indexOf(from)
10 | if (pos !== -1) {
11 | if (to) {
12 | return str.substring(pos + from.length, str.indexOf(to))
13 | }
14 | return str.substring(0, pos)
15 | }
16 |
17 | return ''
18 | }
19 |
--------------------------------------------------------------------------------
/packages/dom/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @animini/dom
2 |
3 | ## 0.3.0
4 |
5 | ### Patch Changes
6 |
7 | - Updated dependencies [2d867a9]
8 | - @animini/core@0.3.0
9 | - @animini/target-dom@0.3.0
10 |
11 | ## 0.2.6
12 |
13 | ### Patch Changes
14 |
15 | - Updated dependencies [c838bf8]
16 | - @animini/core@0.2.6
17 | - @animini/target-dom@0.2.6
18 |
19 | ## 0.2.5
20 |
21 | ### Patch Changes
22 |
23 | - Updated dependencies [0885116]
24 | - @animini/core@0.2.5
25 | - @animini/target-dom@0.2.5
26 |
27 | ## 0.2.4
28 |
29 | ### Patch Changes
30 |
31 | - a0f7cdb: Adapter inside Animated
32 | - Updated dependencies [a0f7cdb]
33 | - @animini/core@0.2.4
34 | - @animini/target-dom@0.2.4
35 |
36 | ## 0.2.3
37 |
38 | ### Patch Changes
39 |
40 | - Updated dependencies [fd31840]
41 | - @animini/core@0.2.3
42 | - @animini/target-dom@0.2.3
43 |
44 | ## 0.2.2
45 |
46 | ### Patch Changes
47 |
48 | - 6c367d3: add syncCachedValues param to buildAnimate
49 | - Updated dependencies [6c367d3]
50 | - @animini/core@0.2.2
51 | - @animini/target-dom@0.2.2
52 |
53 | ## 0.2.1
54 |
55 | ### Patch Changes
56 |
57 | - Updated dependencies [bf2fd1b]
58 | - @animini/core@0.2.1
59 | - @animini/target-dom@0.2.1
60 |
61 | ## 0.2.0
62 |
63 | ### Minor Changes
64 |
65 | - 10ba638: Refactor package
66 |
67 | ### Patch Changes
68 |
69 | - Updated dependencies [10ba638]
70 | - @animini/core@0.2.0
71 | - @animini/target-dom@0.2.0
72 |
--------------------------------------------------------------------------------
/packages/dom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@animini/dom",
3 | "version": "0.3.0",
4 | "description": "animini for the dom",
5 | "keywords": [],
6 | "main": "dist/animini-dom.cjs.js",
7 | "module": "dist/animini-dom.esm.js",
8 | "sideEffects": false,
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/dbismut/animini.git",
12 | "directory": "packages/dom"
13 | },
14 | "bugs": "https://github.com/dbismut/animini/issues",
15 | "dependencies": {
16 | "@animini/core": "0.3.0",
17 | "@animini/target-dom": "0.3.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/dom/src/index.ts:
--------------------------------------------------------------------------------
1 | import { buildAnimate } from '@animini/core'
2 | import { dom } from '@animini/target-dom'
3 | export * from '@animini/core/algorithms'
4 |
5 | export const animate = buildAnimate(dom)
6 |
--------------------------------------------------------------------------------
/packages/react-dom/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @animini/react-dom
2 |
3 | ## 0.3.0
4 |
5 | ### Minor Changes
6 |
7 | - 2d867a9: Package refactor, introducing vanilla api
8 |
9 | ### Patch Changes
10 |
11 | - Updated dependencies [2d867a9]
12 | - @animini/core@0.3.0
13 | - @animini/core-react@0.3.0
14 | - @animini/target-dom@0.3.0
15 |
16 | ## 0.2.6
17 |
18 | ### Patch Changes
19 |
20 | - Updated dependencies [c838bf8]
21 | - @animini/core@0.2.6
22 | - @animini/target-dom@0.2.6
23 | - @animini/core-react@0.2.6
24 |
25 | ## 0.2.5
26 |
27 | ### Patch Changes
28 |
29 | - Updated dependencies [0885116]
30 | - @animini/core@0.2.5
31 | - @animini/target-dom@0.2.5
32 | - @animini/core-react@0.2.5
33 |
34 | ## 0.2.4
35 |
36 | ### Patch Changes
37 |
38 | - Updated dependencies [a0f7cdb]
39 | - @animini/core@0.2.4
40 | - @animini/target-dom@0.2.4
41 | - @animini/core-react@0.2.4
42 |
43 | ## 0.2.3
44 |
45 | ### Patch Changes
46 |
47 | - Updated dependencies [fd31840]
48 | - @animini/core@0.2.3
49 | - @animini/core-react@0.2.3
50 | - @animini/target-dom@0.2.3
51 |
52 | ## 0.2.2
53 |
54 | ### Patch Changes
55 |
56 | - Updated dependencies [6c367d3]
57 | - @animini/core@0.2.2
58 | - @animini/core-react@0.2.2
59 | - @animini/target-dom@0.2.2
60 |
61 | ## 0.2.1
62 |
63 | ### Patch Changes
64 |
65 | - bf2fd1b: Internal refactoring
66 | - Updated dependencies [bf2fd1b]
67 | - @animini/core@0.2.1
68 | - @animini/core-react@0.2.1
69 | - @animini/target-dom@0.2.1
70 |
71 | ## 0.2.0
72 |
73 | ### Minor Changes
74 |
75 | - 10ba638: Refactor package
76 |
77 | ### Patch Changes
78 |
79 | - Updated dependencies [10ba638]
80 | - @animini/core@0.2.0
81 | - @animini/target-dom@0.2.0
82 | - @animini/core-react@0.2.0
83 |
84 | ## 0.1.4
85 |
86 | ### Patch Changes
87 |
88 | - a7b464c: dom: immediate transition to NaN strings
89 | - Updated dependencies [a7b464c]
90 | - @animini/core@0.1.4
91 |
92 | ## 0.1.3
93 |
94 | ### Patch Changes
95 |
96 | - Updated dependencies [bb94d14]
97 | - @animini/core@0.1.3
98 |
99 | ## 0.1.2
100 |
101 | ### Patch Changes
102 |
103 | - Updated dependencies [6b9d542]
104 | - @animini/core@0.1.2
105 |
106 | ## 0.1.1
107 |
108 | ### Patch Changes
109 |
110 | - Updated dependencies [944cfc7]
111 | - @animini/core@0.1.1
112 |
113 | ## 0.1.0
114 |
115 | ### Minor Changes
116 |
117 | - b442912: - typescript
118 | - promise-based
119 | - added api.stop
120 | - ability to change algorithm per animation call
121 | - added ease algorithm
122 | - added inertia algorithm
123 | - improved color parsing for dom
124 | - improved color parsing for three
125 | - unit support for dom
126 |
127 | ### Patch Changes
128 |
129 | - Updated dependencies [b442912]
130 | - @animini/core@0.1.0
131 |
132 | ## 0.0.8
133 |
134 | ### Patch Changes
135 |
136 | - 15ad827: fix px
137 |
138 | ## 0.0.7
139 |
140 | ### Patch Changes
141 |
142 | - a1fccbc: fix opacity
143 |
144 | ## 0.0.6
145 |
146 | ### Patch Changes
147 |
148 | - Updated dependencies [891dbf7]
149 | - @animini/core@0.0.6
150 |
151 | ## 0.0.5
152 |
153 | ### Patch Changes
154 |
155 | - 098cac2: feat: one controller for all simultaneous values
156 | - Updated dependencies [098cac2]
157 | - @animini/core@0.0.5
158 |
159 | ## 0.0.4
160 |
161 | ### Patch Changes
162 |
163 | - Updated dependencies [748a414]
164 | - @animini/core@0.0.4
165 |
166 | ## 0.0.3
167 |
168 | ### Patch Changes
169 |
170 | - Updated dependencies [9c2cd35]
171 | - @animini/core@0.0.3
172 |
173 | ## 0.0.2
174 |
175 | ### Patch Changes
176 |
177 | - Updated dependencies [a8301ed]
178 | - @animini/core@0.0.2
179 |
--------------------------------------------------------------------------------
/packages/react-dom/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/@animini/dom) [](https://bundlephobia.com/result?p=@animini/dom)
2 |
3 | ## Demo
4 |
5 | https://animini.vercel.app/
6 |
7 | ## Installation
8 |
9 | ### For the DOM
10 |
11 | ```bash
12 | yarn add @animini/dom
13 | ```
14 |
15 | ### For Three
16 |
17 | ```bash
18 | yarn add @animini/three
19 | ```
20 |
21 | ### Instructions
22 |
23 | ```js
24 | import { useDrag } from '@use-gesture/react'
25 | import { useAnimate, spring } from '@animini/dom'
26 |
27 | const easing = spring()
28 |
29 | export default function App() {
30 | const [ref, api] = useAnimate()
31 |
32 | useDrag(
33 | ({ active, movement: [x, y] }) => {
34 | api.start({ scale: active ? 1.2 : 1, x: active ? x : 0, y: active ? y : 0 }, (k) => ({
35 | immediate: k !== 'scale' && active,
36 | easing
37 | }))
38 | },
39 | { target: ref }
40 | )
41 |
42 | return
43 | }
44 | ```
45 |
46 | ## Easings
47 |
48 | ### Lerp
49 |
50 | Lerp is the lightest, fastest and default easing algorithm for Animini. It supports a `factor` attribute that will change the momentum of the lerp.
51 |
52 | ```js
53 | import { useAnimate, lerp } from '@animini/dom'
54 |
55 | const easing = lerp({ factor: 0.05 })
56 | api.start({ x: 100 }, { easing })
57 | ```
58 |
59 | ### Spring
60 |
61 | ```js
62 | import { useAnimate, spring } from '@animini/dom'
63 |
64 | const easing = spring({
65 | tension: 170, // spring tension
66 | friction: 26, // spring friction
67 | mass: 1, // target mass
68 | velocity // initial velocity
69 | })
70 |
71 | api.start({ x: 100 }, { easing })
72 | ```
73 |
74 | ### Ease (Bezier)
75 |
76 | ```js
77 | import { useAnimate, ease } from '@animini/dom'
78 |
79 | const easing = ease(
80 | 300, // duration of the ease in ms
81 | [0.25, 0.1, 0.25, 1] // coordinates of the bezier curve
82 | )
83 |
84 | api.start({ x: 100 }, { easing })
85 | ```
86 |
87 | ### Inertia
88 |
89 | Inertia aims at emulating a thrown object. Inertia will not reach its destination and only works if the value is already moving or if the easing is given an initial velocity.
90 |
91 | Inertia supports `min` and `max` bounds which the element will bounce against as a rubberband bouncing on a wall.
92 |
93 | ```js
94 | import { useAnimate, inertia } from '@animini/dom'
95 |
96 | const easing = inertia({
97 | momentum: 0.998, // momentum of the inertia
98 | velocity: undefined, // initial velocity (leave it undefined to use the current velocity of the value)
99 | min: -100, // min bound
100 | max: 100, // max bound
101 | rubberband = 0.15 // elasticity factor when reaching bounds defined by min / max
102 | })
103 |
104 | api.start({ x: 100 }, { easing })
105 | ```
106 |
--------------------------------------------------------------------------------
/packages/react-dom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@animini/react-dom",
3 | "version": "0.3.0",
4 | "description": "animini hook for React dom",
5 | "keywords": [],
6 | "main": "dist/animini-react-dom.cjs.js",
7 | "module": "dist/animini-react-dom.esm.js",
8 | "sideEffects": false,
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/dbismut/animini.git",
12 | "directory": "packages/react-dom"
13 | },
14 | "bugs": "https://github.com/dbismut/animini/issues",
15 | "peerDependencies": {
16 | "react": ">=16.8.0"
17 | },
18 | "dependencies": {
19 | "@animini/core": "0.3.0",
20 | "@animini/core-react": "0.3.0",
21 | "@animini/target-dom": "0.3.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/react-dom/src/index.ts:
--------------------------------------------------------------------------------
1 | export { useAnimate } from './useAnimateDom'
2 | export * from '@animini/core/algorithms'
3 |
--------------------------------------------------------------------------------
/packages/react-dom/src/useAnimateDom.ts:
--------------------------------------------------------------------------------
1 | import { ConfigWithOptionalEl } from '@animini/core'
2 | import { buildReactHook } from '@animini/core-react'
3 | import { dom } from '@animini/target-dom'
4 |
5 | export const useAnimateDom = buildReactHook(dom)
6 |
7 | export function useAnimate<
8 | Element extends HTMLElement | Window,
9 | C extends ConfigWithOptionalEl = ConfigWithOptionalEl
10 | >(masterConfig?: C) {
11 | return useAnimateDom(masterConfig)
12 | }
13 |
--------------------------------------------------------------------------------
/packages/react-three/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @animini/three
2 |
3 | ## 0.3.0
4 |
5 | ### Minor Changes
6 |
7 | - 2d867a9: Package refactor, introducing vanilla api
8 |
9 | ### Patch Changes
10 |
11 | - Updated dependencies [2d867a9]
12 | - @animini/core@0.3.0
13 | - @animini/core-react@0.3.0
14 | - @animini/target-three@0.3.0
15 |
16 | ## 0.2.6
17 |
18 | ### Patch Changes
19 |
20 | - Updated dependencies [c838bf8]
21 | - @animini/core@0.2.6
22 | - @animini/core-react@0.2.6
23 | - @animini/target-three@0.2.6
24 |
25 | ## 0.2.5
26 |
27 | ### Patch Changes
28 |
29 | - Updated dependencies [0885116]
30 | - @animini/core@0.2.5
31 | - @animini/target-three@0.2.5
32 | - @animini/core-react@0.2.5
33 |
34 | ## 0.2.4
35 |
36 | ### Patch Changes
37 |
38 | - Updated dependencies [a0f7cdb]
39 | - @animini/core@0.2.4
40 | - @animini/target-three@0.2.4
41 | - @animini/core-react@0.2.4
42 |
43 | ## 0.2.3
44 |
45 | ### Patch Changes
46 |
47 | - Updated dependencies [fd31840]
48 | - @animini/core@0.2.3
49 | - @animini/core-react@0.2.3
50 | - @animini/target-three@0.2.3
51 |
52 | ## 0.2.2
53 |
54 | ### Patch Changes
55 |
56 | - Updated dependencies [6c367d3]
57 | - @animini/core@0.2.2
58 | - @animini/core-react@0.2.2
59 | - @animini/target-three@0.2.2
60 |
61 | ## 0.2.1
62 |
63 | ### Patch Changes
64 |
65 | - bf2fd1b: Internal refactoring
66 | - Updated dependencies [bf2fd1b]
67 | - @animini/core@0.2.1
68 | - @animini/core-react@0.2.1
69 | - @animini/target-three@0.2.1
70 |
71 | ## 0.2.0
72 |
73 | ### Minor Changes
74 |
75 | - 10ba638: Refactor package
76 |
77 | ### Patch Changes
78 |
79 | - Updated dependencies [10ba638]
80 | - @animini/core@0.2.0
81 | - @animini/target-three@0.2.0
82 | - @animini/core-react@0.2.0
83 |
84 | ## 0.1.4
85 |
86 | ### Patch Changes
87 |
88 | - Updated dependencies [a7b464c]
89 | - @animini/core@0.1.4
90 |
91 | ## 0.1.3
92 |
93 | ### Patch Changes
94 |
95 | - Updated dependencies [bb94d14]
96 | - @animini/core@0.1.3
97 |
98 | ## 0.1.2
99 |
100 | ### Patch Changes
101 |
102 | - 6b9d542: fix global loop in three
103 | - Updated dependencies [6b9d542]
104 | - @animini/core@0.1.2
105 |
106 | ## 0.1.1
107 |
108 | ### Patch Changes
109 |
110 | - Updated dependencies [944cfc7]
111 | - @animini/core@0.1.1
112 |
113 | ## 0.1.0
114 |
115 | ### Minor Changes
116 |
117 | - b442912: - typescript
118 | - promise-based
119 | - added api.stop
120 | - ability to change algorithm per animation call
121 | - added ease algorithm
122 | - added inertia algorithm
123 | - improved color parsing for dom
124 | - improved color parsing for three
125 | - unit support for dom
126 |
127 | ### Patch Changes
128 |
129 | - Updated dependencies [b442912]
130 | - @animini/core@0.1.0
131 |
132 | ## 0.0.6
133 |
134 | ### Patch Changes
135 |
136 | - Updated dependencies [891dbf7]
137 | - @animini/core@0.0.6
138 |
139 | ## 0.0.5
140 |
141 | ### Patch Changes
142 |
143 | - 098cac2: feat: one controller for all simultaneous values
144 | - Updated dependencies [098cac2]
145 | - @animini/core@0.0.5
146 |
147 | ## 0.0.4
148 |
149 | ### Patch Changes
150 |
151 | - Updated dependencies [748a414]
152 | - @animini/core@0.0.4
153 |
154 | ## 0.0.3
155 |
156 | ### Patch Changes
157 |
158 | - Updated dependencies [9c2cd35]
159 | - @animini/core@0.0.3
160 |
161 | ## 0.0.2
162 |
163 | ### Patch Changes
164 |
165 | - Updated dependencies [a8301ed]
166 | - @animini/core@0.0.2
167 |
--------------------------------------------------------------------------------
/packages/react-three/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/@animini/dom) [](https://bundlephobia.com/result?p=@animini/dom)
2 |
3 | ## Demo
4 |
5 | https://animini.vercel.app/
6 |
7 | ## Installation
8 |
9 | ### For the DOM
10 |
11 | ```bash
12 | yarn add @animini/dom
13 | ```
14 |
15 | ### For Three
16 |
17 | ```bash
18 | yarn add @animini/three
19 | ```
20 |
21 | ### Instructions
22 |
23 | ```js
24 | import { useDrag } from '@use-gesture/react'
25 | import { useAnimate, spring } from '@animini/dom'
26 |
27 | const easing = spring()
28 |
29 | export default function App() {
30 | const [ref, api] = useAnimate()
31 |
32 | useDrag(
33 | ({ active, movement: [x, y] }) => {
34 | api.start({ scale: active ? 1.2 : 1, x: active ? x : 0, y: active ? y : 0 }, (k) => ({
35 | immediate: k !== 'scale' && active,
36 | easing
37 | }))
38 | },
39 | { target: ref }
40 | )
41 |
42 | return
43 | }
44 | ```
45 |
46 | ## Easings
47 |
48 | ### Lerp
49 |
50 | Lerp is the lightest, fastest and default easing algorithm for Animini. It supports a `factor` attribute that will change the momentum of the lerp.
51 |
52 | ```js
53 | import { useAnimate, lerp } from '@animini/dom'
54 |
55 | const easing = lerp({ factor: 0.05 })
56 | api.start({ x: 100 }, { easing })
57 | ```
58 |
59 | ### Spring
60 |
61 | ```js
62 | import { useAnimate, spring } from '@animini/dom'
63 |
64 | const easing = spring({
65 | tension: 170, // spring tension
66 | friction: 26, // spring friction
67 | mass: 1, // target mass
68 | velocity // initial velocity
69 | })
70 |
71 | api.start({ x: 100 }, { easing })
72 | ```
73 |
74 | ### Ease (Bezier)
75 |
76 | ```js
77 | import { useAnimate, ease } from '@animini/dom'
78 |
79 | const easing = ease(
80 | 300, // duration of the ease in ms
81 | [0.25, 0.1, 0.25, 1] // coordinates of the bezier curve
82 | )
83 |
84 | api.start({ x: 100 }, { easing })
85 | ```
86 |
87 | ### Inertia
88 |
89 | Inertia aims at emulating a thrown object. Inertia will not reach its destination and only works if the value is already moving or if the easing is given an initial velocity.
90 |
91 | Inertia supports `min` and `max` bounds which the element will bounce against as a rubberband bouncing on a wall.
92 |
93 | ```js
94 | import { useAnimate, inertia } from '@animini/dom'
95 |
96 | const easing = inertia({
97 | momentum: 0.998, // momentum of the inertia
98 | velocity: undefined, // initial velocity (leave it undefined to use the current velocity of the value)
99 | min: -100, // min bound
100 | max: 100, // max bound
101 | rubberband = 0.15 // elasticity factor when reaching bounds defined by min / max
102 | })
103 |
104 | api.start({ x: 100 }, { easing })
105 | ```
106 |
--------------------------------------------------------------------------------
/packages/react-three/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@animini/react-three",
3 | "version": "0.3.0",
4 | "description": "animini hook for React Three Fiber",
5 | "keywords": [],
6 | "main": "dist/animini-react-three.cjs.js",
7 | "module": "dist/animini-react-three.esm.js",
8 | "sideEffects": false,
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/dbismut/animini.git",
12 | "directory": "packages/react-three"
13 | },
14 | "bugs": "https://github.com/dbismut/animini/issues",
15 | "peerDependencies": {
16 | "@react-three/fiber": "^8.0.19",
17 | "react": ">=16.8.0",
18 | "three": ">=0.140.0"
19 | },
20 | "dependencies": {
21 | "@animini/core": "0.3.0",
22 | "@animini/core-react": "0.3.0",
23 | "@animini/target-three": "0.3.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/react-three/src/index.ts:
--------------------------------------------------------------------------------
1 | export { useAnimate } from './useAnimateThree'
2 | export * from '@animini/core/algorithms'
3 |
--------------------------------------------------------------------------------
/packages/react-three/src/useAnimateThree.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import { ConfigWithOptionalEl, GlobalLoop } from '@animini/core'
3 | import { buildReactHook } from '@animini/core-react'
4 | import { three, ThreeElementType, ThreeValues } from '@animini/target-three'
5 | import { addEffect } from '@react-three/fiber'
6 |
7 | let count = 0
8 |
9 | function setGlobalLoopOnDemand() {
10 | let unsub: () => void | undefined
11 | if (count++ === 0) {
12 | GlobalLoop.onDemand = true
13 | unsub = addEffect(() => {
14 | GlobalLoop.update()
15 | })
16 | }
17 | return () => {
18 | if (--count === 0) {
19 | GlobalLoop.onDemand = false
20 | unsub?.()
21 | }
22 | }
23 | }
24 |
25 | export const useAnimateThree = buildReactHook(three)
26 |
27 | export function useAnimate<
28 | Element extends ThreeElementType,
29 | C extends ConfigWithOptionalEl = ConfigWithOptionalEl
30 | >(masterConfig?: C) {
31 | useEffect(() => {
32 | return setGlobalLoopOnDemand()
33 | }, [])
34 |
35 | return useAnimateThree>(masterConfig)
36 | }
37 |
--------------------------------------------------------------------------------
/packages/three/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @animini/three
2 |
3 | ## 0.3.0
4 |
5 | ### Minor Changes
6 |
7 | - 2d867a9: Package refactor, introducing vanilla api
8 |
9 | ### Patch Changes
10 |
11 | - Updated dependencies [2d867a9]
12 | - @animini/core@0.3.0
13 | - @animini/target-three@0.3.0
14 |
15 | ## 0.2.6
16 |
17 | ### Patch Changes
18 |
19 | - Updated dependencies [c838bf8]
20 | - @animini/core@0.2.6
21 | - @animini/target-three@0.2.6
22 |
23 | ## 0.2.5
24 |
25 | ### Patch Changes
26 |
27 | - Updated dependencies [0885116]
28 | - @animini/core@0.2.5
29 | - @animini/target-three@0.2.5
30 |
31 | ## 0.2.4
32 |
33 | ### Patch Changes
34 |
35 | - a0f7cdb: Adapter inside Animated
36 | - Updated dependencies [a0f7cdb]
37 | - @animini/core@0.2.4
38 | - @animini/target-three@0.2.4
39 |
40 | ## 0.2.3
41 |
42 | ### Patch Changes
43 |
44 | - Updated dependencies [fd31840]
45 | - @animini/core@0.2.3
46 | - @animini/target-three@0.2.3
47 |
48 | ## 0.2.2
49 |
50 | ### Patch Changes
51 |
52 | - 6c367d3: add syncCachedValues param to buildAnimate
53 | - Updated dependencies [6c367d3]
54 | - @animini/core@0.2.2
55 | - @animini/target-three@0.2.2
56 |
57 | ## 0.2.1
58 |
59 | ### Patch Changes
60 |
61 | - Updated dependencies [bf2fd1b]
62 | - @animini/core@0.2.1
63 | - @animini/target-three@0.2.1
64 |
65 | ## 0.2.0
66 |
67 | ### Patch Changes
68 |
69 | - Updated dependencies [10ba638]
70 | - @animini/core@0.2.0
71 | - @animini/target-three@0.2.0
72 |
--------------------------------------------------------------------------------
/packages/three/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@animini/three",
3 | "version": "0.3.0",
4 | "description": "animini for Three",
5 | "keywords": [],
6 | "main": "dist/animini-three.cjs.js",
7 | "module": "dist/animini-three.esm.js",
8 | "sideEffects": false,
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/dbismut/animini.git",
12 | "directory": "packages/dom"
13 | },
14 | "bugs": "https://github.com/dbismut/animini/issues",
15 | "peerDependencies": {
16 | "@react-three/fiber": "^8.0.19",
17 | "react": ">=16.8.0",
18 | "three": ">=0.140.0"
19 | },
20 | "dependencies": {
21 | "@animini/core": "0.3.0",
22 | "@animini/target-three": "0.3.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/three/src/index.ts:
--------------------------------------------------------------------------------
1 | import { buildAnimate, ConfigWithEl } from '@animini/core'
2 | import { three, ThreeElementType, ThreeValues } from '@animini/target-three'
3 | export * from '@animini/core/algorithms'
4 |
5 | const animateThree = buildAnimate(three)
6 |
7 | export function animate(
8 | masterConfigWithEl: ConfigWithEl,
9 | globalTo?: Partial>
10 | ) {
11 | return animateThree(masterConfigWithEl, globalTo)
12 | }
13 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | # all packages in subdirs of packages/
3 | - 'packages/*'
4 | - 'targets/*'
5 | - 'demo'
6 | - 'test-perf'
7 |
--------------------------------------------------------------------------------
/targets/target-dom/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @animini/target-dom
2 |
3 | ## 0.3.0
4 |
5 | ### Minor Changes
6 |
7 | - 2d867a9: Package refactor, introducing vanilla api
8 |
9 | ### Patch Changes
10 |
11 | - Updated dependencies [2d867a9]
12 | - @animini/core@0.3.0
13 |
14 | ## 0.2.6
15 |
16 | ### Patch Changes
17 |
18 | - c838bf8: Support for scrollTop / scrollLeft
19 | - Updated dependencies [c838bf8]
20 | - @animini/core@0.2.6
21 |
22 | ## 0.2.5
23 |
24 | ### Patch Changes
25 |
26 | - 0885116: Add string interpolation
27 | - Updated dependencies [0885116]
28 | - @animini/core@0.2.5
29 |
30 | ## 0.2.4
31 |
32 | ### Patch Changes
33 |
34 | - a0f7cdb: Adapter inside Animated
35 | - Updated dependencies [a0f7cdb]
36 | - @animini/core@0.2.4
37 |
38 | ## 0.2.3
39 |
40 | ### Patch Changes
41 |
42 | - fd31840: Remove cached values
43 | - Updated dependencies [fd31840]
44 | - @animini/core@0.2.3
45 |
46 | ## 0.2.2
47 |
48 | ### Patch Changes
49 |
50 | - Updated dependencies [6c367d3]
51 | - @animini/core@0.2.2
52 |
53 | ## 0.2.1
54 |
55 | ### Patch Changes
56 |
57 | - bf2fd1b: Internal refactoring
58 | - Updated dependencies [bf2fd1b]
59 | - @animini/core@0.2.1
60 |
61 | ## 0.2.0
62 |
63 | ### Minor Changes
64 |
65 | - 10ba638: Refactor package
66 |
67 | ### Patch Changes
68 |
69 | - Updated dependencies [10ba638]
70 | - @animini/core@0.2.0
71 |
--------------------------------------------------------------------------------
/targets/target-dom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@animini/target-dom",
3 | "version": "0.3.0",
4 | "description": "animini target for the dom",
5 | "keywords": [],
6 | "main": "dist/animini-target-dom.cjs.js",
7 | "module": "dist/animini-target-dom.esm.js",
8 | "sideEffects": false,
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/dbismut/animini.git",
12 | "directory": "packages/target-dom"
13 | },
14 | "bugs": "https://github.com/dbismut/animini/issues",
15 | "dependencies": {
16 | "@animini/core": "0.3.0"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/targets/target-dom/src/adapters/color.ts:
--------------------------------------------------------------------------------
1 | import { parseColor } from '@animini/core'
2 | import { DomAdapter } from '../types'
3 |
4 | export const color: DomAdapter = {
5 | parse: parseColor,
6 | format(c: number[]) {
7 | return `rgba(${~~c[0]}, ${~~c[1]}, ${~~c[2]}, ${c[3]})`
8 | },
9 | parseInitial: parseColor
10 | }
11 |
--------------------------------------------------------------------------------
/targets/target-dom/src/adapters/generic.ts:
--------------------------------------------------------------------------------
1 | import { parseUnitValue } from '@animini/core'
2 | import { DomAdapter } from '../types'
3 | import { SCROLL_KEYS } from '../utils'
4 |
5 | const parse: DomAdapter['parse'] = (value, animated) => {
6 | let [_value, unit] = parseUnitValue(value)
7 | if (isNaN(_value)) return value
8 | switch (unit) {
9 | case '%':
10 | const parent = (animated.el as HTMLElement)?.offsetParent
11 | // @ts-expect-error
12 | const size = (key === 'top' ? parent?.offsetHeight : parent?.offsetWidth) || 0
13 | return (_value * size) / 100
14 | case 'vw':
15 | return (_value * window.innerWidth) / 100
16 | case 'vh':
17 | return (_value * window.innerHeight) / 100
18 | }
19 | return _value
20 | }
21 |
22 | export const generic: DomAdapter = {
23 | parse,
24 | format(value: number, animated) {
25 | if (!isNaN(value as any) && !SCROLL_KEYS.includes(animated.key!)) return value + 'px'
26 | return value
27 | },
28 | parseInitial: parse
29 | }
30 |
--------------------------------------------------------------------------------
/targets/target-dom/src/adapters/index.ts:
--------------------------------------------------------------------------------
1 | export * from './color'
2 | export * from './generic'
3 | export * from './transform'
4 | export * from './string'
5 |
--------------------------------------------------------------------------------
/targets/target-dom/src/adapters/string.ts:
--------------------------------------------------------------------------------
1 | import { interpolate, Animated, parseNumbers } from '@animini/core'
2 | import { DomAdapter } from '../types'
3 | import { getSidesValues, SIDES_KEYS } from '../utils'
4 |
5 | interface AnimatedWithInterpolator extends Animated {
6 | i: ReturnType
7 | }
8 |
9 | export const string: DomAdapter = {
10 | onInit(a: AnimatedWithInterpolator) {
11 | a.i = interpolate(a.value)
12 | },
13 | parse(value, a) {
14 | if (SIDES_KEYS.includes(a.key!)) {
15 | value = getSidesValues(value)
16 | }
17 | return parseNumbers(value)!
18 | },
19 | parseInitial(_value, a: AnimatedWithInterpolator) {
20 | return a.i.values
21 | },
22 | format(value, a: AnimatedWithInterpolator) {
23 | return a.i.compute(value)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/targets/target-dom/src/adapters/transform.ts:
--------------------------------------------------------------------------------
1 | import { parseUnitValue } from '@animini/core'
2 | import { DomAdapter } from '../types'
3 |
4 | export const transform: DomAdapter = {
5 | parse(value, animated) {
6 | const [_value, unit] = parseUnitValue(value)
7 | switch (unit) {
8 | case '%':
9 | return (
10 | (_value *
11 | parseFloat(getComputedStyle(animated.el as HTMLElement)[animated.key === 'x' ? 'width' : 'height'])) /
12 | 100
13 | )
14 | case 'vw':
15 | return (_value * window.innerWidth) / 100
16 | case 'vh':
17 | return (_value * window.innerHeight) / 100
18 | }
19 | return _value
20 | }
21 | }
22 |
23 | export const rotate: DomAdapter = {
24 | parse(value) {
25 | const [_value, unit] = parseUnitValue(value)
26 | switch (unit) {
27 | case 'rad':
28 | return (_value / 180) * Math.PI
29 | case 'turn':
30 | return _value * 360
31 | }
32 | return _value
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/targets/target-dom/src/index.ts:
--------------------------------------------------------------------------------
1 | export { dom } from './targetDom'
2 | export * from './types'
3 |
--------------------------------------------------------------------------------
/targets/target-dom/src/targetDom.ts:
--------------------------------------------------------------------------------
1 | import { color, generic, transform, string, rotate } from './adapters'
2 | import { DomAdapter, Styles, Transform } from './types'
3 | import { Target } from '@animini/core'
4 | import {
5 | getSidesValues,
6 | getTransformStyle,
7 | getTransformValues,
8 | SCROLL_KEYS,
9 | SIDES_KEYS,
10 | someDefined,
11 | TRANSFORM_KEYS
12 | } from './utils'
13 |
14 | const ADAPTERS: Partial> = {
15 | color,
16 | backgroundColor: color,
17 | borderColor: color,
18 | borderTopColor: color,
19 | borderLeftColor: color,
20 | borderBottomColor: color,
21 | borderRightColor: color,
22 | fill: color,
23 | stroke: color,
24 | textDecorationColor: color,
25 | x: transform,
26 | y: transform,
27 | rotate,
28 | clipPath: string,
29 | boxShadow: string,
30 | padding: string,
31 | margin: string,
32 | inset: string,
33 | opacity: undefined,
34 | scale: undefined
35 | }
36 |
37 | const IDENTITY = 'matrix(1, 0, 0, 1, 0, 0)'
38 |
39 | export const dom: Target = {
40 | getElement(element) {
41 | if (typeof element !== string) return element
42 | return document.querySelector(element)
43 | },
44 | setValues(values, el, initial, idle) {
45 | const _el = el as HTMLElement
46 |
47 | const { x, y, zIndex, scale, scaleX, scaleY, skew, skewX, skewY, rotate, scrollTop, scrollLeft, ...rest } = values
48 |
49 | if (scrollLeft !== void 0 || scrollTop !== void 0) {
50 | const fallbackLeft = el === window ? el.scrollX : _el.scrollLeft
51 | const fallbackTop = el === window ? el.scrollY : _el.scrollTop
52 | el.scrollTo((scrollLeft as number) ?? fallbackLeft, (scrollTop as number) ?? fallbackTop)
53 | }
54 |
55 | if (el !== window) {
56 | for (let key in rest) {
57 | // @ts-expect-error
58 | _el.style[key] = rest[key]
59 | }
60 |
61 | const t = { x, y, scale, scaleX, scaleY, skew, skewX, skewY, rotate }
62 | if (!someDefined(Object.values(t))) return
63 | _el.style.transform = getTransformStyle(t as Transform, initial.transform)
64 |
65 | // TODO can potentially be optimized 👇
66 | if (idle && getComputedStyle(_el).transform === IDENTITY) _el.style.transform = 'none'
67 | }
68 | },
69 |
70 | getInitialValueAndAdapter(el, key, initial) {
71 | // element is the window
72 | if (el === window) {
73 | if (key === 'scrollTop') return [el.scrollY, generic]
74 | else if (key === 'scrollLeft') return [el.scrollX, generic]
75 | // TODO return type doesn't make any sense
76 | return [0, undefined]
77 | }
78 |
79 | // element is an HTMLElement
80 | const style = getComputedStyle(el as HTMLElement)
81 | const adapter = key in ADAPTERS ? ADAPTERS[key] : generic
82 |
83 | // @ts-expect-error
84 | if (SCROLL_KEYS.includes(key as string)) return [el[key], adapter]
85 |
86 | if (TRANSFORM_KEYS.includes(key as string)) {
87 | initial.transform ||= getTransformValues(style)
88 | return [initial.transform[key as string], adapter]
89 | }
90 |
91 | // @ts-expect-error
92 | if (SIDES_KEYS.includes(key as string)) return [getSidesValues(style[key]), adapter]
93 |
94 | // @ts-expect-error
95 | return [style[key], adapter]
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/targets/target-dom/src/types.ts:
--------------------------------------------------------------------------------
1 | import { Adapter } from '@animini/core'
2 |
3 | // this needs some love
4 | export type Styles = Record & {
5 | x: number | string
6 | y: number | string
7 | scale: number
8 | scaleX: number
9 | scaleY: number
10 | skewX: number
11 | skewY: number
12 | skew: number
13 | rotate: number
14 | scrollTop: number | string
15 | scrollLeft: number | string
16 | }
17 |
18 | export type Transform = {
19 | scale: number
20 | scaleX: number
21 | scaleY: number
22 | // skewX: number
23 | // skewY: number
24 | // skew: number
25 | rotate: number
26 | x: number
27 | y: number
28 | }
29 |
30 | export type DomAdapter = Adapter
31 |
--------------------------------------------------------------------------------
/targets/target-dom/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { Transform } from './types'
2 |
3 | export const TRANSFORM_KEYS = ['x', 'y', 'scale', 'rotate', 'scaleX', 'scaleY', 'skew', 'skewX', 'skewY']
4 | export const SIDES_KEYS = ['inset', 'margin', 'padding']
5 | export const SCROLL_KEYS = ['scrollLeft', 'scrollTop']
6 |
7 | const RAD_TO_DEG = 180 / Math.PI
8 |
9 | function round(n: number, d = 0) {
10 | const e = 10 ** d
11 | return Math.round(n * 1 * e) / e
12 | }
13 |
14 | export function someDefined(...args: any[]) {
15 | return args.some((v) => v !== void 0)
16 | }
17 |
18 | export function getSidesValues(value: string) {
19 | const n = value.split(' ')
20 | switch (n.length) {
21 | case 3:
22 | return value + ' ' + n[2]
23 | case 2:
24 | return value + ' ' + value
25 | case 1:
26 | return value + ' ' + value + ' ' + value + ' ' + value
27 | }
28 | return value
29 | }
30 |
31 | export function getTransformValues(style: CSSStyleDeclaration): Transform {
32 | const matrix = new DOMMatrixReadOnly(style.transform)
33 | const scaleX = round(Math.sqrt(matrix.a ** 2 + matrix.b ** 2), 3)
34 |
35 | // const skewX = Math.atan2(matrix.d, matrix.c) * RAD_TO_DEG - 90
36 | // const skewY = Math.atan2(matrix.b, matrix.a) * RAD_TO_DEG
37 |
38 | // console.log(
39 | // {
40 | // x: matrix.e,
41 | // y: matrix.f,
42 | // scale: scaleX,
43 | // scaleX,
44 | // scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
45 | // skew: skewX,
46 | // skewX,
47 | // skewY,
48 | // rotate: Math.atan2(matrix.b, matrix.a) * RAD_TO_DEG
49 | // },
50 | // matrix
51 | // )
52 |
53 | return {
54 | x: matrix.e,
55 | y: matrix.f,
56 | scale: scaleX,
57 | scaleX,
58 | scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
59 | // skew: skewX,
60 | // skewX,
61 | // skewY,
62 | rotate: Math.atan2(matrix.b, matrix.a) * RAD_TO_DEG
63 | }
64 | }
65 |
66 | export function getTransformStyle(t: Transform, i: Transform) {
67 | let s = ''
68 | if (someDefined(t.x, t.y, i.x, i.y)) s += `translate(${t.x ?? i.x}px, ${t.y ?? i.y}px)`
69 | if (someDefined(t.scale)) s += ` scale(${t.scale})`
70 | else if (someDefined(t.scaleX, t.scaleY, i.scaleX, i.scaleY))
71 | s += ` scale(${t.scaleX ?? i.scaleX}, ${t.scaleY ?? i.scaleY})`
72 | if (someDefined(t.rotate, i.rotate)) s += ` rotate(${t.rotate ?? i.rotate}deg)`
73 | // if (someDefined(t.skew, i.skew)) s += ` skew(${t.skew ?? i.skew}deg)`
74 | return s
75 | }
76 |
--------------------------------------------------------------------------------
/targets/target-three/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @animini/target-three
2 |
3 | ## 0.3.0
4 |
5 | ### Minor Changes
6 |
7 | - 2d867a9: Package refactor, introducing vanilla api
8 |
9 | ### Patch Changes
10 |
11 | - Updated dependencies [2d867a9]
12 | - @animini/core@0.3.0
13 |
14 | ## 0.2.6
15 |
16 | ### Patch Changes
17 |
18 | - Updated dependencies [c838bf8]
19 | - @animini/core@0.2.6
20 |
21 | ## 0.2.5
22 |
23 | ### Patch Changes
24 |
25 | - 0885116: Add string interpolation
26 | - Updated dependencies [0885116]
27 | - @animini/core@0.2.5
28 |
29 | ## 0.2.4
30 |
31 | ### Patch Changes
32 |
33 | - a0f7cdb: Adapter inside Animated
34 | - Updated dependencies [a0f7cdb]
35 | - @animini/core@0.2.4
36 |
37 | ## 0.2.3
38 |
39 | ### Patch Changes
40 |
41 | - Updated dependencies [fd31840]
42 | - @animini/core@0.2.3
43 |
44 | ## 0.2.2
45 |
46 | ### Patch Changes
47 |
48 | - Updated dependencies [6c367d3]
49 | - @animini/core@0.2.2
50 |
51 | ## 0.2.1
52 |
53 | ### Patch Changes
54 |
55 | - bf2fd1b: Internal refactoring
56 | - Updated dependencies [bf2fd1b]
57 | - @animini/core@0.2.1
58 |
59 | ## 0.2.0
60 |
61 | ### Minor Changes
62 |
63 | - 10ba638: Refactor package
64 |
65 | ### Patch Changes
66 |
67 | - Updated dependencies [10ba638]
68 | - @animini/core@0.2.0
69 |
--------------------------------------------------------------------------------
/targets/target-three/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@animini/target-three",
3 | "version": "0.3.0",
4 | "description": "animini target for Three",
5 | "keywords": [],
6 | "main": "dist/animini-target-three.cjs.js",
7 | "module": "dist/animini-target-three.esm.js",
8 | "sideEffects": false,
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/dbismut/animini.git",
12 | "directory": "packages/target-three"
13 | },
14 | "bugs": "https://github.com/dbismut/animini/issues",
15 | "peerDependencies": {
16 | "three": ">=0.140.0"
17 | },
18 | "dependencies": {
19 | "@animini/core": "0.3.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/targets/target-three/src/adapters/color.ts:
--------------------------------------------------------------------------------
1 | import { ThreeAdapter } from '../types'
2 | import { Color } from 'three'
3 |
4 | const kk = new Color()
5 |
6 | export const color: ThreeAdapter = {
7 | parse(str: string) {
8 | return kk.set(str) as unknown as Record
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/targets/target-three/src/adapters/euler.ts:
--------------------------------------------------------------------------------
1 | import { Euler, Vector3 } from 'three'
2 | import { ThreeAdapter } from '../types'
3 |
4 | export const euler: ThreeAdapter = {
5 | parseInitial(euler: Euler) {
6 | const v = new Vector3()
7 | return v.setFromEuler(euler) as any as Record
8 | },
9 | onUpdate(animated) {
10 | // @ts-ignore
11 | animated.el[animated.key].setFromVector3(animated.value)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/targets/target-three/src/adapters/index.ts:
--------------------------------------------------------------------------------
1 | export * from './color'
2 | export * from './euler'
3 |
--------------------------------------------------------------------------------
/targets/target-three/src/index.ts:
--------------------------------------------------------------------------------
1 | export { three } from './targetThree'
2 | export * from './types'
3 |
--------------------------------------------------------------------------------
/targets/target-three/src/targetThree.ts:
--------------------------------------------------------------------------------
1 | import { Target } from '@animini/core'
2 | import { Color, Euler } from 'three'
3 | import { color, euler } from './adapters'
4 | import { ThreeElementType, ThreeAdapter, ThreeValues } from './types'
5 |
6 | const ADAPTERS = new Map([
7 | [Color, color],
8 | [Euler, euler]
9 | ])
10 |
11 | export const three: Target> = {
12 | getInitialValueAndAdapter(element, key) {
13 | const value = element[key]
14 | const constructor = value.__proto__.constructor
15 | const adapter = ADAPTERS.get(constructor)
16 | return [value, adapter]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/targets/target-three/src/types.ts:
--------------------------------------------------------------------------------
1 | import { Adapter } from '@animini/core'
2 | import type { Object3D, Material } from 'three'
3 |
4 | export type ThreeElementType = Object3D | Material
5 |
6 | // TODO this also needs some love
7 | export type ThreeValues = Record<
8 | keyof Element,
9 | string | number | Record | number[]
10 | >
11 |
12 | export type ThreeAdapter = Adapter
13 |
--------------------------------------------------------------------------------
/test-perf/.gitignore:
--------------------------------------------------------------------------------
1 | *.log.*
--------------------------------------------------------------------------------
/test-perf/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-perf",
3 | "version": "0.0.0",
4 | "keywords": [],
5 | "private": true,
6 | "dependencies": {
7 | "@animini/core-latest": "https://gitpkg.now.sh/dbismut/animini/packages/core?main&v=0.2.6",
8 | "@animini/target-dom-latest": "https://gitpkg.now.sh/dbismut/animini/targets/target-dom?main&v=0.2.6",
9 | "benchmark": "^2.1.4",
10 | "console-table-printer": "^2.11.0",
11 | "fs-extra": "^10.0.1",
12 | "microtime": "^3.1.0",
13 | "systeminformation": "^5.11.20"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test-perf/tests/bench.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment node
3 | */
4 |
5 | import fs from 'fs-extra'
6 | import path from 'path'
7 | import { Table } from 'console-table-printer'
8 | import Benchmark from 'benchmark'
9 | import si from 'systeminformation'
10 | import { round, animatedBench } from './utils'
11 |
12 | let fullResults = {}
13 |
14 | beforeAll(() => {
15 | fullResults = {}
16 | })
17 |
18 | async function bench(label, cb) {
19 | await it(`${label} should run faster than $ms`, async () => {
20 | const results = await new Promise((resolve) => {
21 | const suite = new Benchmark.Suite()
22 | suite
23 | .add('source', () => void cb(true))
24 | .add('latest', () => void cb(false))
25 | .on('cycle', function (event) {
26 | // eslint-disable-next-line no-console
27 | console.log(String(event.target))
28 | })
29 | .on('complete', function () {
30 | resolve(this)
31 | })
32 | .run()
33 | })
34 |
35 | Object.assign(fullResults, {
36 | [label]: {
37 | latest: results['1'].hz,
38 | source: results['0'].hz,
39 | vs: 1 - results['0'].hz / results['1'].hz
40 | }
41 | })
42 | expect(0).toBeLessThan(1000)
43 | })
44 | }
45 |
46 | bench('lerp int (10 itr.)', (useSource) => animatedBench(useSource, { limit: 10 }))
47 | bench('spring int (10 itr.)', (useSource) => animatedBench(useSource, { motion: 'spring', limit: 10 }))
48 | bench('spring array (10 itr.)', (useSource) =>
49 | animatedBench(useSource, { motion: 'spring', limit: 10, from: [0, 0, 0], to: [100, 200, 300] })
50 | )
51 | bench('spring color (10 itr.)', (useSource) =>
52 | animatedBench(useSource, { motion: 'spring', limit: 10, from: '#ff0000', to: '#000eac', adapter: 'color' })
53 | )
54 |
55 | function formatResults(results) {
56 | const r = {}
57 | r['latest'] = Benchmark.formatNumber(~~results.latest) + 'ops/s'
58 | r['source'] = Benchmark.formatNumber(~~results.source) + 'ops/s'
59 | r['vs'] = round(results.vs * 100, 2) + '%'
60 | return r
61 | }
62 |
63 | afterAll(async () => {
64 | const perfPath = path.resolve(__dirname, `logs/bench-${new Date().toISOString()}.log.json`)
65 | const cpu = await si.cpu()
66 | const p = new Table()
67 |
68 | Object.entries(fullResults).forEach(([key, source]) => {
69 | const results = formatResults(source)
70 | p.addRow({ test: key, ...results }, { color: source.vs > 0.1 ? 'red' : source.vs < -0.1 ? 'green' : undefined })
71 | })
72 |
73 | p.printTable()
74 | fs.ensureFileSync(perfPath)
75 | fs.writeJSONSync(perfPath, { cpu, results: fullResults }, { spaces: ' ' })
76 | })
77 |
--------------------------------------------------------------------------------
/test-perf/tests/perf.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment node
3 | */
4 |
5 | import fs from 'fs-extra'
6 | import path from 'path'
7 | import microtime from 'microtime'
8 | import { Table } from 'console-table-printer'
9 | import si from 'systeminformation'
10 | import { animatedBench, round, kFormat } from './utils'
11 |
12 | let sourceResults = {}
13 | let latestResults = {}
14 |
15 | beforeAll(() => {
16 | sourceResults = {}
17 | latestResults = {}
18 | })
19 |
20 | function run(label, cb, runs = 100) {
21 | const source = { time: 0, iterations: 0, runs }
22 | const latest = { time: 0, iterations: 0, runs }
23 |
24 | for (let i = 0; i < runs; i++) {
25 | const useSourceFirst = Math.random() < 0.5
26 | ;[useSourceFirst, !useSourceFirst].forEach((useSource) => {
27 | const start = microtime.now()
28 | const iterations = cb(useSource)
29 | const time = microtime.now() - start
30 | const obj = useSource ? source : latest
31 | obj.iterations += iterations
32 | obj.time += time
33 | })
34 | }
35 |
36 | source.timePerItr = source.time / source.iterations
37 | latest.timePerItr = latest.time / latest.iterations
38 |
39 | const ratio = source.timePerItr / latest.timePerItr
40 | source.vs = ratio - 1
41 |
42 | Object.assign(sourceResults, { [label]: source })
43 | Object.assign(latestResults, { [label]: latest })
44 | return source.time
45 | }
46 |
47 | function bench(label, limit, runs, cb) {
48 | it(`${label} should run faster than ${limit}ms`, () => {
49 | expect(run(label, cb, runs)).toBeLessThan(limit * 1000)
50 | })
51 | }
52 |
53 | bench('lerp int (10 itr.)', 600, 5000, (useSource) => animatedBench(useSource, { limit: 10 }))
54 | bench('spring int (10 itr.)', 600, 5000, (useSource) => animatedBench(useSource, { motion: 'spring', limit: 10 }))
55 | bench('spring array (10 itr.)', 600, 5000, (useSource) =>
56 | animatedBench(useSource, { motion: 'spring', limit: 10, from: [0, 0, 0], to: [100, 200, 300] })
57 | )
58 | bench('spring color (10 itr.)', 600, 5000, (useSource) =>
59 | animatedBench(useSource, { motion: 'spring', limit: 10, from: '#ff0000', to: '#000eac', adapter: 'color' })
60 | )
61 | bench('lerp int', 1500, 5000, (useSource) => animatedBench(useSource, { to: 10 }))
62 | bench('spring int', 600, 5000, (useSource) => animatedBench(useSource, { motion: 'spring', to: 10 }))
63 | bench('spring array', 1800, 5000, (useSource) =>
64 | animatedBench(useSource, { motion: 'spring', from: [0, 0, 0], to: [100, 200, 300] })
65 | )
66 | bench('spring color', 1800, 5000, (useSource) =>
67 | animatedBench(useSource, { motion: 'spring', from: '#ff0000', to: '#000eac', adapter: 'color' })
68 | )
69 |
70 | function formatResults(results) {
71 | const r = {}
72 | r['runs'] = kFormat(results.runs)
73 | r['iterations'] = kFormat(results.iterations)
74 | r['time (ms)'] = round(results.time / 1000, 1)
75 | r['t/itr (ns)'] = round(results.time / results.iterations)
76 | r['vs latest'] = round(results.vs * 100, 2) + (results.vs < 0 ? '% faster' : '% slower')
77 | return r
78 | }
79 |
80 | afterAll(async () => {
81 | const perfPath = path.resolve(__dirname, `logs/perf-${new Date().toISOString()}.log.json`)
82 | const cpu = await si.cpu()
83 | const p = new Table()
84 |
85 | Object.entries(sourceResults).forEach(([key, source]) => {
86 | const results = formatResults(source)
87 |
88 | p.addRow({ test: key, ...results }, { color: source.vs > 0.1 ? 'red' : source.vs < -0.1 ? 'green' : undefined })
89 | })
90 |
91 | p.printTable()
92 | fs.ensureFileSync(perfPath)
93 | fs.writeJSONSync(perfPath, { cpu, results: sourceResults }, { spaces: ' ' })
94 | })
95 |
--------------------------------------------------------------------------------
/test-perf/tests/utils.js:
--------------------------------------------------------------------------------
1 | import { Animated } from '../../packages/core/src/animated/Animated'
2 | import { spring, lerp } from '../../packages/core/src/algorithms'
3 | import { color } from '../../targets/target-dom/src/adapters'
4 |
5 | import { Animated as AnimatedLatest } from '@animini/core-latest/src/animated/Animated'
6 | import { lerp as lerpLatest, spring as springLatest } from '@animini/core-latest/src/algorithms'
7 | import { color as colorLatest } from '@animini/target-dom-latest/src/adapters'
8 |
9 | const AdaptersSource = { color }
10 | const AdaptersLatest = { color: colorLatest }
11 |
12 | export function animateLatest({ motion, limit, from, to, config, adapter }) {
13 | const loop = { time: { elapsed: 0, delta: 16 } }
14 | const _motion = { easing: motion === 'spring' ? springLatest(config) : lerpLatest(config) }
15 | const animated = new AnimatedLatest({ value: from, adapter: AdaptersLatest[adapter] }, loop)
16 | animated.start(to, _motion)
17 | let iterations = 0
18 | while (!animated.idle && iterations < limit) {
19 | iterations++
20 | loop.time.elapsed += loop.time.delta
21 | animated.update()
22 | }
23 |
24 | // console.log('SOURCE', animated.value)
25 |
26 | return iterations
27 | }
28 |
29 | export function animateSource({ motion, limit, from, to, config, adapter }) {
30 | const loop = { time: { elapsed: 0, delta: 16 } }
31 | const _motion = { easing: motion === 'spring' ? spring(config) : lerp(config) }
32 | const animated = new Animated({ value: from, adapter: AdaptersSource[adapter] }, loop)
33 | animated.start(to, _motion)
34 | let iterations = 0
35 | while (!animated.idle && iterations < limit) {
36 | iterations++
37 | loop.time.elapsed += loop.time.delta
38 | animated.update()
39 | }
40 |
41 | // console.log('SOURCE', animated.value)
42 |
43 | return iterations
44 | }
45 |
46 | export function animatedBench(
47 | useSource,
48 | { motion = 'lerp', limit = Infinity, from = 0, to = 1 + Math.random() * 1000, config = {}, adapter } = {}
49 | ) {
50 | if (useSource) return animateSource({ motion, limit, from, to, config, adapter })
51 | return animateLatest({ motion, limit, from, to, config, adapter })
52 | }
53 |
54 | export function round(number, dec = 3) {
55 | return Number(number.toFixed(dec))
56 | }
57 |
58 | export function kFormat(num) {
59 | return Math.abs(num) > 999 ? Math.sign(num) * (Math.abs(num) / 1000).toFixed(1) + 'K' : Math.sign(num) * Math.abs(num)
60 | }
61 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | // prettier-ignore
2 | {
3 | "include": ["packages/*/src/**/*", "targets/*/src/**/*"],
4 | "compilerOptions": {
5 | "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
6 | "module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
7 | "lib": ["DOM", "ESNext"] /* Specify library files to be included in the compilation. */,
8 | // "allowJs": true, /* Allow javascript files to be compiled. */
9 | // "checkJs": true, /* Report errors in .js files. */
10 | "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
13 | // "sourceMap": true, /* Generates corresponding '.map' file. */
14 | // "outFile": "./", /* Concatenate and emit output to single file. */
15 | // "outDir": "./", /* Redirect output structure to the directory. */
16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
17 | // "composite": true, /* Enable project compilation */
18 | // "incremental": true, /* Enable incremental compilation */
19 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
20 | // "removeComments": true, /* Do not emit comments to output. */
21 | "noEmit": true, /* Do not emit outputs. */
22 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
23 | "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. We use it for the Bezier returned value. */
24 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
25 | /* Strict Type-Checking Options */
26 | "strict": true /* Enable all strict type-checking options. */,
27 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
28 | // "strictNullChecks": true, /* Enable strict null checks. */
29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
34 |
35 | /* Additional Checks */
36 | "noUnusedLocals": true, /* Report errors on unused locals. */
37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
40 |
41 | /* Module Resolution Options */
42 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
43 | "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
46 | // "typeRoots": [], /* List of folders to include type definitions from. */
47 | // "types": [], /* Type declaration files to be included in compilation. */
48 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
50 | // "preserveSymlinks": true /* Do not resolve the real path of symlinks. */,
51 | "skipLibCheck": true,
52 | /* Source Map Options */
53 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
57 | /* Experimental Options */
58 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
59 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
60 | // error out if import and file system have a casing mismatch. Recommended by TS
61 | "forceConsistentCasingInFileNames": true,
62 | }
63 | }
64 |
--------------------------------------------------------------------------------