├── .eslintrc.json
├── .gitignore
├── .npmignore
├── .prettierrc
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── examples
├── .gitignore
├── README.md
├── index.html
├── package.json
├── src
│ ├── App.css
│ ├── App.jsx
│ ├── assets
│ │ └── favicon.ico
│ ├── components
│ │ ├── AnimatingCounter.css
│ │ ├── AnimatingCounter.jsx
│ │ ├── AnimatingElements.css
│ │ ├── AnimatingElements.jsx
│ │ ├── AnimatingList.css
│ │ ├── AnimatingList.jsx
│ │ └── mind-blown-explosion.gif
│ ├── index.css
│ └── index.jsx
└── vite.config.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── createAnimation
│ └── index.ts
├── createTimeline
│ └── index.ts
└── index.ts
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:@typescript-eslint/recommended",
5 | "plugin:react/recommended",
6 |
7 | "plugin:prettier/recommended",
8 | "prettier/@typescript-eslint"
9 | ],
10 | "plugins": [
11 | "react",
12 | "@typescript-eslint",
13 | "prettier",
14 | "simple-import-sort"
15 | ],
16 | "parser": "@typescript-eslint/parser",
17 | "env": { "browser": true, "node": true, "es6": true, "jest": true },
18 | "parserOptions": {
19 | "sourceType": "module"
20 | },
21 | "settings": {
22 | "react": {
23 | "pragma": "React",
24 | "version": "detect"
25 | }
26 | },
27 | "rules": {
28 | "react-hooks/rules-of-hooks": "error",
29 | "simple-import-sort/sort": "error",
30 | "prettier/prettier": ["error"],
31 | "linebreak-style": 0,
32 | "camelcase": "off",
33 | "endOfLine": 0,
34 | "@typescript-eslint/camelcase": [
35 | "error",
36 | {
37 | "ignoreDestructuring": true
38 | }
39 | ],
40 | "@typescript-eslint/explicit-function-return-type": 0
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Directory for instrumented libs generated by jscoverage/JSCover
10 | lib-cov
11 |
12 | # Coverage directory used by tools like istanbul
13 | coverage
14 | *.lcov
15 |
16 | # nyc test coverage
17 | .nyc_output
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (https://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directories
26 | node_modules/
27 | jspm_packages/
28 |
29 | # TypeScript v1 declaration files
30 | typings/
31 |
32 | # TypeScript cache
33 | *.tsbuildinfo
34 |
35 | # Optional npm cache directory
36 | .npm
37 |
38 | # Optional eslint cache
39 | .eslintcache
40 |
41 | # Output of 'npm pack'
42 | *.tgz
43 |
44 | # Yarn Integrity file
45 | .yarn-integrity
46 |
47 | # generate output
48 | dist
49 | .vscode
50 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | dist
2 | .babelrc
3 | .storybook
4 | .gitignore
5 | .prettierrc
6 | rollup.config.js
7 | tsconfig.json
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "trailingComma": "all",
4 | "singleQuote": true,
5 | "printWidth": 85,
6 | "tabWidth": 4,
7 | "endOfLine": "auto"
8 | }
9 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to motion-hooks
2 |
3 | ## Local Setup
4 |
5 | - Fork [motion-hooks](https://github.com/tanvesh01/motion-hooks)
6 |
7 | ```sh
8 | git clone https://github.com/:github-username/motion-hooks.git
9 | cd motion-hooks
10 | npm install # Installs dependencies for motion-hooks
11 | cd examples # React app to test out changes
12 | npm install # Installs dependencies for example app
13 | npm run dev # To run example on localhost:3000
14 | ```
15 |
16 | ## Creating Pull Request
17 |
18 | - Create a branch with name of feature you are working on. (e.g. `feat-config`, `fix-fails`, etc)
19 | - Make changes in your locally cloned fork
20 | - Send Pull Request from your branch to `main` branch.
21 |
22 | Thank you and if you need any additional help, you can reach out to me on [Twitter @Sarve\_\_\_tanvesh](https://twitter.com/Sarve___tanvesh).
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Tanvesh Sarve
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # motion-signals
2 |
3 | A wrapper over [Motion One](https://motion.dev/), An animation library, built on the Web Animations API for the smallest filesize and the fastest performance. Works with [solid-js](https://www.solidjs.com/)
4 |
5 | [](https://www.npmjs.com/package/motion-signals)  [](https://twitter.com/Sarve___tanvesh)
6 |
7 | [Demo examples at netify](https://motion-signals.netlify.app/)
8 |
9 | ## Installation
10 |
11 | ```
12 | npm install motion-signals motion
13 | ```
14 |
15 | ## Functions
16 |
17 | As of now, motion-signals has 2 Functions that wrap around `animate` and `timeline` of motion one respectively
18 |
19 | - [`createAnimation`](https://github.com/tanvesh01/motion-signals#createAnimation)
20 | - [`createTimeline`](https://github.com/tanvesh01/motion-signals#createTimeline)
21 |
22 | ## Example usage
23 |
24 | **Things You could do with [`createAnimation`](https://github.com/tanvesh01/motion-signals#createanimation)**
25 |
26 |
27 |
28 | 
29 |
30 |
31 |
32 | 
33 |
34 | **Things You could do with [`createTimeline`](https://github.com/tanvesh01/motion-signals#createtimeline)**
35 |
36 |
37 |
38 | 
39 |
40 | ### `createAnimation`
41 |
42 | Returns all the properties returned by [`animate`](https://motion.dev/dom/animate) and some helper functions and state
43 |
44 | > Props returned my [`animate`](https://motion.dev/dom/animate) are `null` initially
45 |
46 |
47 |
48 | ```jsx
49 | function App() {
50 | const { play, getIsFinished, replay } = createAnimation(
51 | '.listItem',
52 | { y: -20, opacity: 1 },
53 | {
54 | delay: stagger(0.3),
55 | duration: 0.5,
56 | easing: [0.22, 0.03, 0.26, 1],
57 | },
58 | );
59 |
60 | // Play the animation on mount of the component
61 | onMount(() => {
62 | play();
63 | });
64 |
65 | return (
66 | // Replay the animation anytime by calling a function, anywhere
67 |
68 |
71 |
72 |
73 | - Item 1
74 | - Item 2
75 | - Item 3
76 |
77 |
78 | );
79 | }
80 | ```
81 |
82 | Instead of passing strings to select elements, you can also pass a `ref` :point_down:
83 |
84 | ```jsx
85 | let boxRef;
86 | const { play, getIsFinished, replay } = createAnimation(
87 | () => boxRef, // Pass a Function that returns the ref
88 | { y: -20, scale: 1.2 },
89 | { duration: 1 },
90 | );
91 |
92 | return BOX
;
93 | ```
94 |
95 | **API**
96 |
97 | ```js
98 | const { play, replay, reset, getIsFinished, getAnimateInstance } = createAnimation(
99 | selector,
100 | keyframes,
101 | options,
102 | events,
103 | );
104 | ```
105 |
106 | `createAnimation` returns:
107 |
108 | - `play`: plays the animation
109 | - `replay`: Resets and plays the animation
110 | - `reset`: resets the element to its original styling
111 | - `getIsFinished`: is `true` when animation has finished playing
112 | - `getAnimateInstance`: Animation Controls. Refer to [motion one docs](https://motion.dev/dom/controls) for more.
113 |
114 | `createAnimation` accepts:
115 |
116 | - `selector` - The target element, can be string or a ref
117 | - `keyframes` - Element will animate from its current style to those defined in the keyframe. Refer to [motion's docs](https://motion.dev/dom/animate#keyframes) for more.
118 | - `options` - Optional parameter. Refer to [motion doc's](https://motion.dev/dom/animate#options) for the values you could pass to this.
119 | - `events` - Pass functions of whatever you want to happen when a event like `onFinish` happens.
120 |
121 | **`events` usage example**
122 |
123 | ```jsx
124 | const { play, getIsFinished, replay } = createAnimation(
125 | '.listItem',
126 | { y: -20, opacity: 1 },
127 | {
128 | delay: stagger(0.3),
129 | duration: 0.5,
130 | },
131 | {
132 | onFinish: () => {
133 | // Whatever you want to do when animation finishes
134 | },
135 | },
136 | );
137 | ```
138 |
139 | ### `createTimeline`
140 |
141 | Create complex sequences of animations across multiple elements.
142 |
143 | returns `getTimelineInstance` (Animation Controls) that are returned by [`timeline`](https://motion.dev/dom/timeline) and some helper functions and state
144 |
145 | > Props returned by [`timeline`](https://motion.dev/dom/timeline) are `null` initially
146 |
147 |
148 |
149 | ```jsx
150 | function App() {
151 | let gifRef;
152 | const { play, getIsFinished, replay } = createTimeline(
153 | [
154 | // You can use Refs too!
155 | [() => gifRef, { scale: [0, 1.2], opacity: 1 }],
156 | ['.heading', { y: [50, 0], opacity: [0, 1] }],
157 | ['.container p', { opacity: 1 }],
158 | ],
159 | { duration: 2 },
160 | );
161 |
162 | onMount(() => {
163 | play();
164 | });
165 |
166 | return (
167 |
168 |
171 |
172 |
173 |

174 |
175 |
Tanvesh
176 |
@sarve__tanvesh
177 |
178 |
179 |
180 | );
181 | }
182 | ```
183 |
184 | **API**
185 |
186 | ```js
187 | const { play, replay, reset, getIsFinished, getTimelineInstance } = createTimeline(
188 | sequence,
189 | options,
190 | events,
191 | );
192 | ```
193 |
194 | `createTimeline` returns:
195 |
196 | - `play`: plays the animation
197 | - `replay`: Resets and plays the animation
198 | - `reset`: resets the element to its original styling
199 | - `getIsFinished`: is `true` when animation has finished playing
200 | - `getTimelineInstance`: Animation Controls. Refer to [motion one docs](https://motion.dev/dom/controls) for more.
201 |
202 | `createTimeline` accepts:
203 |
204 | - `sequence` - `sequence` is an array, defines animations with the same settings as the animate function. In the arrays, Element can be either a string or a ref. You can refer to [sequence docs](https://motion.dev/dom/timeline#sequence) for more on this.
205 | - `options` - Optional parameter. Refer to [motion doc's](https://motion.dev/dom/animate#options) for the values you could pass to this.
206 | - `events` - Pass functions of whatever you want to happen when a event like `onFinish` happens. Exactly same as createAnimation's `onFinish`.
207 |
208 | ---
209 |
210 | ## Local Installation & Contributing
211 |
212 | - Fork [motion-signals](https://github.com/tanvesh01/motion-signals)
213 |
214 | ```sh
215 | git clone https://github.com/:github-username/motion-signals.git
216 | cd motion-signals
217 | npm install # Installs dependencies for motion-signals
218 | cd examples # React app to test out changes
219 | npm install # Installs dependencies for example app
220 | npm run dev # To run example on localhost:3000
221 | ```
222 |
223 | The contributing guidelines along with local setup guide is mentioned in [CONTRIBUTING.md](https://github.com/tanvesh01/motion-signals/blob/main/CONTRIBUTING.md)
224 |
225 | Any Type of feedback is more than welcome! This project is in very early stage and would love to have contributors of any skill/exp level.
226 |
227 | You can contact me on my [Twitter handle @Sarve\_\_\_tanvesh](https://twitter.com/Sarve___tanvesh)
228 |
--------------------------------------------------------------------------------
/examples/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 | .vscode/*
7 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | Examples inspired by the React [motion-hooks](https://github.com/tanvesh01/motion-hooks/tree/main/examples) folder.
2 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | tanvesh01/motion-signals demo in Solid-Start
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "motionone-solidjs-examples",
3 | "version": "0.0.3",
4 | "description": "tanvesh01/motion-signals (Motion One wrapper for SolidJS) demo in Solid-Start",
5 | "license": "MIT",
6 | "contributors": [
7 | "Tanvesh Sarve (https://github.com/tanvesh01)",
8 | "Tom Byrer (https://github.com/tomByrer)"
9 | ],
10 | "keywords": [
11 | "SolidJS",
12 | "animation",
13 | "motionone"
14 | ],
15 | "scripts": {
16 | "start": "vite",
17 | "dev": "vite",
18 | "build": "vite build",
19 | "serve": "vite preview"
20 | },
21 | "devDependencies": {
22 | "vite": "^2.9.9",
23 | "vite-plugin-solid": "^2.2.6"
24 | },
25 | "dependencies": {
26 | "motion": "^10.8.1",
27 | "motion-signals": "^0.0.3",
28 | "solid-js": "^1.4.1"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/examples/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | box-sizing: border-box;
3 | display: flex;
4 | flex-wrap: wrap;
5 | }
6 |
7 | a {
8 | color: #08583d;
9 | }
10 |
11 | .card {
12 | box-sizing: border-box;
13 | min-height: 50vh;
14 | background-color: #18faae;
15 | flex: 1;
16 | min-width: 50%;
17 | border: 5px solid white;
18 | }
19 |
20 | section#about {
21 | display: flex;
22 | flex-direction: column;
23 | justify-content: center;
24 | align-items: center;
25 | /* margin: 4em; */
26 | /* padding: 4em; */
27 | font-size: 1.62em;
28 | height: 50vh;
29 | }
30 |
31 | section#about > div {
32 | margin: 1.62em;
33 | padding: 1.62em;
34 | background-color: #f2f2f2;
35 | }
36 |
37 | @media (max-width: 500px) {
38 | .card {
39 | min-width: 100%;
40 | }
41 | }
42 |
43 | @media (max-width: 800px) {
44 | section#about {
45 | font-size: 1.16em;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/examples/src/App.jsx:
--------------------------------------------------------------------------------
1 | import AnimatingCounter from "./components/AnimatingCounter"
2 | import AnimatingElements from "./components/AnimatingElements"
3 | import AnimatingList from "./components/AnimatingList"
4 |
5 | import './App.css'
6 |
7 | export default function App() {
8 |
9 | return (
10 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/examples/src/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tanvesh01/motion-signals/bdefd024c5202e71b1a8bcfee1580ccc13143a3f/examples/src/assets/favicon.ico
--------------------------------------------------------------------------------
/examples/src/components/AnimatingCounter.css:
--------------------------------------------------------------------------------
1 | .CounterContainer {
2 | /* width: 50%; */
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: center;
6 | align-items: center;
7 | background-color: #252525;
8 | height: 50vh;
9 | font-size: 2.2rem;
10 | }
11 |
12 | .CounterContainer p {
13 | margin: 0;
14 | padding: 0 0 0.3rem 0;
15 | color: #18faae;
16 | font-family: "Inter";
17 | }
18 |
19 | .CounterContainer button {
20 | border: 0;
21 | background-color: #252525;
22 | color: white;
23 | cursor: pointer;
24 | padding: 0;
25 | font-size: 2rem;
26 | }
27 |
28 | .CounterContainer .container {
29 | padding: 0.73rem 0.5rem 0.35rem 0.5rem;
30 | border-radius: 2.2rem;
31 | border: 2px solid white;
32 | text-align: center;
33 | }
34 |
--------------------------------------------------------------------------------
/examples/src/components/AnimatingCounter.jsx:
--------------------------------------------------------------------------------
1 | import { createSignal } from "solid-js";
2 | import { createAnimation } from 'motion-signals'
3 | // import { ChevronUpIcon, ChevronDownIcon } from '@heroicons/react/solid';
4 |
5 | import './AnimatingCounter.css';
6 |
7 | export default function AnimatedCounter() {
8 | let refCounterOutput
9 | const { play } = createAnimation(
10 | // ref on the element inserts animation directly into 'style' attribute
11 | ()=>refCounterOutput,
12 | { y: [5, 20, -10, 0], opacity: [1, 0, 0, 1] },
13 | { duration: 0.5 },
14 | );
15 | const { play: playDecrease } = createAnimation(
16 | ()=>refCounterOutput,
17 | { y: [0, -10, 20, 0], opacity: [1, 0, 0, 1] },
18 | { duration: 0.5 },
19 | );
20 |
21 | const [counter, setCounter] = createSignal(0);
22 |
23 | const increase = () => {
24 | play();
25 | setTimeout(() => {
26 | setCounter(counter() + 1);
27 | }, 100);
28 | };
29 | const descrease = () => {
30 | playDecrease();
31 | setTimeout(() => {
32 | setCounter(counter() - 1);
33 | }, 100);
34 | };
35 |
36 | return (
37 |
38 |
39 |
44 |
45 |
{counter()}
46 |
47 |
52 |
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/examples/src/components/AnimatingElements.css:
--------------------------------------------------------------------------------
1 | .ElementsContainer {
2 | /* width: 50%; */
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: center;
6 | align-items: center;
7 | background-color: #838383;
8 | height: 50vh;
9 | font-family: "Inter";
10 | }
11 |
12 | .ElementsContainer .container {
13 | display: grid;
14 | grid-template-columns: 1fr 1fr;
15 | gap: 2rem;
16 | padding: 2em;
17 | align-items: center;
18 | border-radius: 1em;
19 | background-color: #f2f2f2;
20 | }
21 |
22 | .ElementsContainer .gif {
23 | width: auto;
24 | border-radius: 1rem;
25 | }
26 |
27 | .ElementsContainer .heading {
28 | color: rgb(31, 31, 31);
29 | opacity: 0;
30 | font-weight: 700;
31 | margin: 0;
32 | }
33 |
34 | .ElementsContainer p {
35 | margin: 0;
36 | padding: 0;
37 | opacity: 0;
38 | color: grey;
39 | }
40 |
41 | .ElementsContainer button {
42 | border: 1px solid #e4e4e4;
43 | padding: 5px 15px;
44 | border-radius: 6px;
45 | background: white;
46 | cursor: pointer;
47 | transition: 0.3s all;
48 | }
49 |
50 | .ElementsContainer button:disabled {
51 | cursor: not-allowed;
52 | }
53 |
54 | .ElementsContainer button {
55 | margin-bottom: 3rem;
56 | }
57 |
--------------------------------------------------------------------------------
/examples/src/components/AnimatingElements.jsx:
--------------------------------------------------------------------------------
1 | import { onMount } from 'solid-js';
2 | import { createTimeline } from 'motion-signals'
3 |
4 | import './AnimatingElements.css';
5 | import Image from './mind-blown-explosion.gif';
6 |
7 | export default function AnimatingElements() {
8 | let refGIF
9 | const { play, getIsFinished, replay } = createTimeline(
10 | [
11 | // You can use Refs too!
12 | [()=> refGIF, { scale: [0, 1.2], opacity: 1 }],
13 | ['.heading', { y: [50, 0], opacity: [0, 1] }],
14 | ['.container p', { opacity: 1 }],
15 | ],
16 | { duration: 2 },
17 | );
18 |
19 | // Play the animation on rendering of the component
20 | onMount(() => {
21 | play();
22 | });
23 |
24 | return (
25 |
26 |
29 |
30 |
31 |

37 |
44 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/examples/src/components/AnimatingList.css:
--------------------------------------------------------------------------------
1 | .ListContainer {
2 | /* width: 50%; */
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: center;
6 | align-items: center;
7 | background-color: #f2f2f2;
8 | height: 50vh;
9 | }
10 |
11 | .ListContainer button {
12 | border: 1px solid #e4e4e4;
13 | padding: 5px 15px;
14 | border-radius: 6px;
15 | background: white;
16 | cursor: pointer;
17 | transition: 0.3s all;
18 | }
19 |
20 | .ListContainer button:disabled {
21 | cursor: not-allowed;
22 | }
23 |
24 | .list {
25 | list-style: none;
26 | padding: 0;
27 | }
28 |
29 | .listItem {
30 | opacity: 0;
31 | border: 1px solid #e5e5e5;
32 | margin: 1rem 0;
33 | padding: 14px 15px;
34 | border-radius: 0.3rem;
35 | font-size: 1.62rem;
36 | font-weight: 700;
37 | background-color: #18faae;
38 | box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
39 | }
40 |
41 | .listItem p {
42 | font-size: 14px;
43 | font-weight: 400;
44 | margin: 0;
45 | }
46 |
--------------------------------------------------------------------------------
/examples/src/components/AnimatingList.jsx:
--------------------------------------------------------------------------------
1 | import { onMount } from "solid-js";
2 | import { createAnimation } from 'motion-signals'
3 | import { stagger } from 'motion';
4 |
5 | import "./AnimatingList.css"
6 |
7 | export default function AnimatingList() {
8 | const { getIsFinished, play, replay } = createAnimation(
9 | '.listItem',
10 | { y: -20, opacity: 1 },
11 | {
12 | delay: stagger(0.3),
13 | duration: 0.5,
14 | easing: [0.22, 0.03, 0.26, 1],
15 | },
16 | );
17 |
18 | // Play the animation on mount of the component
19 | onMount(() => {
20 | play();
21 | });
22 |
23 | return (
24 | // Replay the animation anytime by calling a function, anywhere
25 |
26 |
29 |
30 |
31 | - 1
32 | - 2
33 | - 3
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/examples/src/components/mind-blown-explosion.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tanvesh01/motion-signals/bdefd024c5202e71b1a8bcfee1580ccc13143a3f/examples/src/components/mind-blown-explosion.gif
--------------------------------------------------------------------------------
/examples/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/examples/src/index.jsx:
--------------------------------------------------------------------------------
1 | /* @refresh reload */
2 | import { render } from 'solid-js/web';
3 | import App from './App';
4 |
5 | import './index.css';
6 |
7 | render(() => , document.getElementById('root'));
8 |
--------------------------------------------------------------------------------
/examples/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import solidPlugin from 'vite-plugin-solid';
3 |
4 | export default defineConfig({
5 | plugins: [solidPlugin()],
6 | build: {
7 | target: 'esnext',
8 | polyfillDynamicImport: false,
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "motion-signals",
3 | "version": "0.0.3",
4 | "description": "A wrapper over Motion One, An animation library, built on the Web Animations API for the smallest filesize and the fastest performance.",
5 | "author": "tanvesh01",
6 | "license": "MIT",
7 | "repository": "tanvesh01/motion-signals",
8 | "homepage": "https://github.com/tanvesh01/motion-signals",
9 | "bugs": {
10 | "url": "https://github.com/tanvesh01/motion-signals/issues",
11 | "email": "sarvetanvesh01@gmail.com"
12 | },
13 | "main": "dist/index.js",
14 | "module": "dist/index.esm.min.js",
15 | "types": "dist/index.d.ts",
16 | "scripts": {
17 | "build": "rollup -c",
18 | "start": "rollup -c -w",
19 | "test": "jest"
20 | },
21 | "jest": {
22 | "transform": {
23 | ".(ts|tsx)": "ts-jest"
24 | },
25 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
26 | "moduleFileExtensions": [
27 | "ts",
28 | "tsx",
29 | "js"
30 | ],
31 | "modulePathIgnorePatterns": [
32 | "/dist/"
33 | ]
34 | },
35 | "peerDependencies": {
36 | "motion": "~10.6.1",
37 | "solid-js": "~1.3.3"
38 | },
39 | "devDependencies": {
40 | "babel-core": "^6.26.3",
41 | "babel-runtime": "^6.26.0",
42 | "jest": "^26.1.0",
43 | "rollup": "^2.57.0",
44 | "rollup-plugin-sass": "^1.2.2",
45 | "rollup-plugin-terser": "^7.0.2",
46 | "rollup-plugin-typescript2": "^0.30.0",
47 | "ts-jest": "^26.1.3",
48 | "tslib": "^2.3.1",
49 | "typescript": "^4.5.5"
50 | },
51 | "files": [
52 | "dist"
53 | ],
54 | "keywords": [
55 | "typescript",
56 | "npm"
57 | ]
58 | }
59 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from 'rollup-plugin-typescript2';
2 | import { terser } from 'rollup-plugin-terser';
3 |
4 | import pkg from './package.json';
5 |
6 | export default {
7 | input: 'src/index.ts',
8 | output: [
9 | {
10 | file: pkg.module,
11 | format: 'esm',
12 | exports: 'named',
13 | sourcemap: true,
14 | strict: false,
15 | plugins: [terser()],
16 | },
17 | // Disabling CJS output
18 | // {
19 | // file: pkg.main,
20 | // format: 'cjs',
21 | // exports: 'named',
22 | // sourcemap: true,
23 | // strict: false,
24 | // },
25 | ],
26 | plugins: [typescript()],
27 | external: [ 'solid-js', 'motion'],
28 | };
29 |
--------------------------------------------------------------------------------
/src/createAnimation/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AcceptedElements,
3 | MotionKeyframesDefinition,
4 | AnimationOptionsWithOverrides,
5 | } from '@motionone/dom';
6 | import { animate } from 'motion';
7 | import { AnimationControls } from '@motionone/types';
8 |
9 | import { Accessor, createSignal } from 'solid-js';
10 |
11 | export type ModifiedAcceptedElements = AcceptedElements | (() => HTMLElement);
12 |
13 | export interface CreateAnimationEvents {
14 | onFinish: (res: (value?: unknown) => void) => void;
15 | }
16 |
17 | interface CreateAnimationReturn {
18 | play: () => void;
19 | reset: () => void;
20 | replay: () => void;
21 | getIsFinished: Accessor;
22 | getAnimateInstance: Accessor;
23 | }
24 |
25 | /**
26 | * `createAnimation` returns `animateInstance`(Animation Controls) returned by `animate` and some helper functions and state
27 | * for Example: `play`, `reset`, `replay` and `isFinished`
28 | * @param selector - The target element, can be string or a ref
29 | * @param keyframes - Element will animate from its current style to those defined in the keyframe. Refer to [motion's docs](https://motion.dev/dom/animate#keyframes) for more.
30 | * @param options - Optional parameter. Refer to [motion doc's](https://motion.dev/dom/animate#options) for the values you could pass to this.
31 | * @param events - Pass functions of whatever you want to happen when a event like `onFinish` happens.
32 | */
33 | export const createAnimation = (
34 | selector: ModifiedAcceptedElements,
35 | keyframes: MotionKeyframesDefinition,
36 | options?: AnimationOptionsWithOverrides,
37 | events?: CreateAnimationEvents,
38 | ): CreateAnimationReturn => {
39 | const [getAnimateInstance, setAnimateInstance] =
40 | createSignal(null);
41 | const [getIsFinished, setIsFinished] = createSignal(true);
42 | const play = async () => {
43 | if (selector) {
44 | let selectedType = selector;
45 | if (typeof selector === 'function') {
46 | selectedType = selector();
47 | } else {
48 | // if string
49 | selectedType = selector;
50 | }
51 | if (selectedType) {
52 | const currentAnimateInstance = animate(
53 | selectedType,
54 | keyframes,
55 | options,
56 | );
57 | setIsFinished(false);
58 | setAnimateInstance(currentAnimateInstance);
59 | await currentAnimateInstance.finished.then((res) => {
60 | events && events.onFinish(res);
61 | setIsFinished(true);
62 | });
63 | }
64 | }
65 | };
66 |
67 | const reset = () => {
68 | getAnimateInstance() && getAnimateInstance()?.stop();
69 | if (typeof selector === 'function') {
70 | selector().setAttribute('styled', '');
71 | } else if (typeof selector === 'string') {
72 | let selectedElements = document.querySelectorAll(selector);
73 |
74 | selectedElements.forEach((el) => {
75 | el.getAttribute('style') && el.removeAttribute('style');
76 | });
77 | }
78 | };
79 |
80 | const replay = () => {
81 | reset();
82 | getIsFinished() && play();
83 | };
84 |
85 | return {
86 | getAnimateInstance,
87 | play,
88 | reset,
89 | replay,
90 | getIsFinished,
91 | };
92 | };
93 |
--------------------------------------------------------------------------------
/src/createTimeline/index.ts:
--------------------------------------------------------------------------------
1 | import { AcceptedElements, MotionKeyframesDefinition } from '@motionone/dom';
2 | import { AnimationControls } from '@motionone/types';
3 | import { Accessor, createSignal } from 'solid-js';
4 |
5 | import { timeline } from 'motion';
6 | import { TimelineOptions } from '@motionone/dom/types/timeline';
7 | import { TimelineDefinition } from '@motionone/dom/types/timeline/types';
8 |
9 | import { AnimationListOptions } from '@motionone/dom/types/animate/types';
10 |
11 | // TODO: Place all these types/interfaces in another file
12 |
13 | type ModifiedAcceptedElements = AcceptedElements | (() => HTMLElement);
14 |
15 | export type TimelineItem =
16 | | [ModifiedAcceptedElements, MotionKeyframesDefinition]
17 | | [ModifiedAcceptedElements, MotionKeyframesDefinition, AnimationListOptions];
18 |
19 | export type SequenceDefination = TimelineItem[];
20 |
21 | export interface CreateTimelineEvents {
22 | onFinish: (res: (value?: unknown) => void) => void;
23 | }
24 |
25 | interface CreateTimelineReturn {
26 | play: () => void;
27 | reset: () => void;
28 | replay: () => void;
29 | getIsFinished: Accessor;
30 | getTimelineInstance: Accessor;
31 | }
32 |
33 | /**
34 | * `createTimeline` returns `timelineInstance` (Animation Controls) that are returned by `timeline` and some helper functions and state
35 | * for Example: `play`, `reset`, `replay` and `isFinished`
36 | * @param sequence - `sequence` is an array, defines animations with the same settings as the animate function. In the arrays, Element can be either a string or a ref.
37 | * @param options - Optional parameter. Refer to [motion doc's](https://motion.dev/dom/timeline#options) for the values you could pass into this.
38 | * @param events - Pass functions of whatever you want to happen when a event like `onFinish` happens.
39 | */
40 |
41 | const convertRefsFunctionsToElement = (
42 | sequence: SequenceDefination,
43 | ): TimelineDefinition => {
44 | const newArray = [...sequence];
45 | newArray.forEach((array) => {
46 | if (array[0] && typeof array[0] === 'function') {
47 | array[0] = array[0]();
48 | }
49 | });
50 | return newArray as TimelineDefinition;
51 | };
52 |
53 | export const createTimeline = (
54 | sequence: SequenceDefination,
55 | options?: TimelineOptions,
56 | events?: CreateTimelineEvents,
57 | ): CreateTimelineReturn => {
58 | const [getTimelineInstance, setTimelineInstance] =
59 | createSignal(null);
60 | const [getIsFinished, setIsFinished] = createSignal(true);
61 |
62 | const play = async () => {
63 | const currentTimelineInstance = timeline(
64 | convertRefsFunctionsToElement(sequence),
65 | options,
66 | );
67 | setIsFinished(false);
68 | setTimelineInstance(currentTimelineInstance);
69 | await currentTimelineInstance.finished.then((res) => {
70 | events && events.onFinish(res);
71 | setIsFinished(true);
72 | });
73 | };
74 |
75 | const reset = () => {
76 | getTimelineInstance() && getTimelineInstance()?.stop();
77 | sequence.forEach((el) => {
78 | let selector = el[0];
79 | if (typeof selector === 'function') {
80 | selector().setAttribute('style', '');
81 | } else if (typeof selector === 'string') {
82 | let selectedElements: NodeListOf =
83 | document.querySelectorAll(selector);
84 |
85 | selectedElements.forEach((el) => {
86 | el.style && el.removeAttribute('style');
87 | });
88 | }
89 | });
90 | };
91 |
92 | const replay = () => {
93 | reset();
94 | getIsFinished() && play();
95 | };
96 |
97 | return {
98 | getTimelineInstance,
99 | play,
100 | reset,
101 | replay,
102 | getIsFinished,
103 | };
104 | };
105 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { createAnimation } from './createAnimation';
2 | export { createTimeline } from './createTimeline';
3 |
4 | export type {
5 | ModifiedAcceptedElements,
6 | CreateAnimationEvents,
7 | } from './createAnimation';
8 | export type { TimelineItem, CreateTimelineEvents } from './createTimeline';
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "module": "esnext",
5 | "target": "es5",
6 | "lib": ["es6", "dom", "es2016", "es2017"],
7 | "sourceMap": true,
8 | "allowJs": false,
9 | "jsx": "react",
10 | "declaration": true,
11 | "moduleResolution": "node",
12 | "forceConsistentCasingInFileNames": true,
13 | "noImplicitReturns": true,
14 | "noImplicitThis": true,
15 | "noImplicitAny": true,
16 | "strictNullChecks": true,
17 | "suppressImplicitAnyIndexErrors": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "esModuleInterop": true,
21 | "strict": true
22 | },
23 | "include": ["src"],
24 | "exclude": ["node_modules", "dist", "example", "rollup.config.js"]
25 | }
26 |
--------------------------------------------------------------------------------