├── .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 | [![npm version](https://badge.fury.io/js/motion-signals.svg)](https://www.npmjs.com/package/motion-signals) ![npm](https://img.shields.io/npm/dt/motion-signals) [![Twitter Follow](https://img.shields.io/twitter/follow/Sarve___tanvesh?label=Chat)](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 | ![createAnimation List Example](https://media1.giphy.com/media/JNMxjkEipIurs5RaQb/giphy.gif) 29 | 30 | 31 | 32 | ![createAnimation Counter Example](https://media3.giphy.com/media/80wDwOyRlnS1woHcF0/giphy.gif) 33 | 34 | **Things You could do with [`createTimeline`](https://github.com/tanvesh01/motion-signals#createtimeline)** 35 | 36 | 37 | 38 | ![createTimeline Example Usage](https://media1.giphy.com/media/RxCRUxJgi4nuM7b7yv/giphy.gif) 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 | 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 | mind explosion gif 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 |
11 |
12 |
13 |
14 |

motion-signals

15 |

A SolidJS wrapper over Motion One, an animation library built on the Web Animations API for the smallest filesize and the fastest performance.

16 |
17 |
18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 |
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 | mind explosion gif 37 |
38 |

Tanvesh

39 |

@sarve__tanvesh

40 |
41 |

tom byrer

42 |

@tombyrer

43 |
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 | 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 | --------------------------------------------------------------------------------