{"id":13527647,"url":"https://github.com/skevy/wobble","last_synced_at":"2025-05-15T18:03:11.268Z","repository":{"id":23609526,"uuid":"99440539","full_name":"skevy/wobble","owner":"skevy","description":"A tiny (~1.7 KB gzipped) spring physics micro-library that models a damped harmonic oscillator.","archived":false,"fork":false,"pushed_at":"2023-02-10T02:28:49.000Z","size":1244,"stargazers_count":823,"open_issues_count":29,"forks_count":21,"subscribers_count":9,"default_branch":"develop","last_synced_at":"2024-05-14T05:20:43.834Z","etag":null,"topics":["animation","javascript","physics-simulation"],"latest_commit_sha":null,"homepage":"https://wobble-demos.now.sh","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/skevy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2017-08-05T18:21:58.000Z","updated_at":"2024-04-23T04:12:30.000Z","dependencies_parsed_at":"2023-01-14T07:45:49.759Z","dependency_job_id":null,"html_url":"https://github.com/skevy/wobble","commit_stats":{"total_commits":95,"total_committers":6,"mean_commits":"15.833333333333334","dds":"0.43157894736842106","last_synced_commit":"142d649f6351cae53c0eaa113bab7bac3e7e83fc"},"previous_names":[],"tags_count":140,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skevy%2Fwobble","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skevy%2Fwobble/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skevy%2Fwobble/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skevy%2Fwobble/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skevy","download_url":"https://codeload.github.com/skevy/wobble/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247737788,"owners_count":20987721,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animation","javascript","physics-simulation"],"created_at":"2024-08-01T06:01:55.406Z","updated_at":"2025-04-07T22:10:54.293Z","avatar_url":"https://github.com/skevy.png","language":"TypeScript","readme":"![repo-banner](./assets/wobble-logo.png)\n\n[![Current version:](https://img.shields.io/badge/v1.5.1:-222222.svg?logo=npm)](https://www.npmjs.com/package/wobble/v/1.5.1)\n[![Test status](https://img.shields.io/circleci/project/github/skevy/wobble/stable.svg?logo=circleci\u0026label=Tests)](https://circleci.com/gh/skevy/wobble/133)\n[![Code coverage](https://img.shields.io/codecov/c/github/skevy/wobble/stable.svg?logo=codecov\u0026logoColor=white\u0026label=Coverage)](https://codecov.io/gh/skevy/wobble/tree/81d48aa583e633fef9da5d0050002b7438a1eb4d/src)\u003cbr /\u003e\n[![HEAD:](https://img.shields.io/badge/HEAD:-222222.svg?logo=github\u0026logoColor=white)](https://github.com/skevy/wobble)\n[![Test status](https://img.shields.io/circleci/project/github/skevy/wobble/develop.svg?logo=circleci\u0026label=Tests)](https://circleci.com/gh/skevy/wobble/tree/develop)\n[![Code coverage](https://img.shields.io/codecov/c/github/skevy/wobble/develop.svg?logo=codecov\u0026logoColor=white\u0026label=Coverage)](https://codecov.io/gh/skevy/wobble/branch/develop)\n\nA tiny (~1.7 KB gzipped) spring physics micro-library that models a [damped harmonic oscillator](https://en.wikipedia.org/wiki/Harmonic_oscillator#Damped_harmonic_oscillator).\n\n# Why Would I Use This?\n\nPerhaps, you just really like to dance.\n\nUse _wobble_ if you need a very small and accurate damped harmonic spring simulation in your animation library or application. _wobble_ intentionally **only** provides a way to animate a scalar value according to equations governing damped harmonic motion. That's all this library will ever do -- any other functionality (integration with [insert ui library here], multi-dimensional springs, a nice API around chaining springs together, etc.) is left to the reader to implement.\n\n# Background\n\nUsing spring physics in UI design is a common way to express natural motion, and there are several ways to model the physics behind springs.\n\nThere are two main ways that spring physics is typically implemented: numerical integration (using something like the [Runge-Kutta 4th order](https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta_methods) numerical integration method) or by using a closed-form (exact) solution. Numerical integration is an approximation of the exact solution, and is generally easier to derive. The numerical integration technique can be applied to basically any ordinary differential equation. Though there are several different numerical integration methods, it's common to leverage RK4 when accuracy of the approximation is required, even though it's slightly slower. RK4 is commonly used in other animation libraries, such as [Rebound](https://github.com/facebook/rebound) and [Pop](https://github.com/facebook/pop/).\n\nThe original goal of the algorithm used in _wobble_ was to replicate `CASpringAnimation` from Apple's Core Animation library (used to power animations on macOS and iOS) in order to mimic iOS animations in React Native. After doing a [little spelunking](https://github.com/facebook/react-native/pull/15322) inside `QuartzCore.framework`, it became clear that Apple was using the closed-form solution for damped harmonic oscillation to power `CASpringAnimation`. _wobble_ leverages the same equations as `CASpringAnimation` in order to be able to match Apple animations precisely.\n\nThe closed-form solution lets us calculate position (_x_) and velocity (_v_) from time _t_, and thus it turns out that using the closed-form solution provides a couple advantages over RK4:\n\n- Easier to generate keyframes and build continuous/interruptible gestures and animations, due to the fact that _x_ and _v_ are pure functions of _t_.\n- Less code.\n- It's faster.\n\n### Math!\n\nThe ODE for damped harmonic motion is:\n\n![DHO ODE](./assets/ode.gif)\n\nSolving this ODE yields:\n\n![solution](./assets/general-solution.gif)\n\nAnd from this general solution, we're able to easily derive the solutions for under-damped, critically-damped, and over-damped damped harmonic oscillation.\n\nThe full proof can be found [in this PDF](http://planetmath.org/sites/default/files/texpdf/39745.pdf) from [planetmath.org](http://planetmath.org).\n\n# Demos\n\nWobble demos are located here: [https://wobble-demos.now.sh/](https://wobble-demos.now.sh/). Send PRs to add more!\n\n... and of course, [Our FAVORITE Demo](https://m.youtube.com/watch?v=rFdeskwbhAM)\n\n# Getting Started\n\n```bash\nyarn add wobble\n# or\nnpm install --save wobble\n```\n\n# Usage\n\n```jsx\nimport { Spring } from 'wobble';\n\n// Create a new spring\nconst spring = new Spring({\n  toValue: 100,\n  stiffness: 1000,\n  damping: 500,\n  mass: 3,\n});\n\n// Set listeners for spring events, start the spring.\nspring\n  .onStart(() =\u003e {\n    console.log('Spring started!');\n  })\n  .onUpdate((s) =\u003e {\n    console.log(`Spring's current value: ` + s.currentValue);\n    console.log(`Spring's current velocity: ` + s.currentVelocity);\n  })\n  .onStop(() =\u003e {\n    console.log('Spring is at rest!');\n  })\n  .start();\n```\n\n# API\n\n#### `new Spring(config: SpringConfig)`\n\nInitialize a new spring with a given spring configuration.\n\n## Configuration\n\n#### `fromValue: number`\n\nStarting value of the animation. Defaults to `0`.\n\n#### `toValue: number`\n\nEnding value of the animation. Defaults to `1`.\n\n#### `stiffness: number`\n\nThe spring stiffness coefficient. Defaults to `100`.\n\n#### `damping: number`\n\nDefines how the spring’s motion should be damped due to the forces of friction. Defaults to `10`.\n\n#### `mass: number`\n\nThe mass of the object attached to the end of the spring. Defaults to `1`.\n\n#### `initialVelocity: number`\n\nThe initial velocity (in units/ms) of the object attached to the spring. Defaults to `0`.\n\n#### `allowsOverdamping: boolean`\n\nWhether or not the spring allows \"overdamping\" (a damping ratio \u003e 1). Defaults to `false`.\n\n#### `overshootClamping: boolean`\n\nFalse when overshooting is allowed, true when it is not. Defaults to `false`.\n\n#### `restVelocityThreshold: number`\n\nWhen spring's velocity is below `restVelocityThreshold`, it is at rest. Defaults to `.001`.\n\n#### `restDisplacementThreshold: number`\n\nWhen the spring's displacement (current value) is below `restDisplacementThreshold`, it is at rest. Defaults to `.001`.\n\n## Methods\n\n#### `start(): Spring`\n\nIf `fromValue` differs from `toValue`, or `initialVelocity` is non-zero, start the simulation and call the `onStart` listeners.\n\n#### `stop(): Spring`\n\nIf a simulation is in progress, stop it and call the `onStop` listeners.\n\n#### `updateConfig(updatedConfig: PartialSpringConfig): Spring`\n\nUpdates the spring config with the given values. Values not explicitly supplied will be reused from the existing config.\n\n#### `onStart(listener: SpringListenerFn): Spring`\n\nThe provided callback will be invoked when the simulation begins.\n\n#### `onUpdate(listener: SpringListenerFn): Spring`\n\nThe provided callback will be invoked on each frame while the simulation is running.\n\n#### `onStop(listener: SpringListenerFn): Spring`\n\nThe provided callback will be invoked when the simulation ends.\n\n#### `removeListener(listenerFn: SpringListenerFn): Spring`\n\nRemove a single listener from this spring.\n\n#### `removeAllListeners(): Spring`\n\nRemoves all listeners from this spring.\n\n## Properties\n\n#### `currentValue: number`\n\nThe spring's current displacement.\n\n#### `currentVelocity: number`\n\nThe spring's current velocity in units / ms.\n\n#### `isAtRest: boolean`\n\nIf the spring has reached its `toValue`, or if its velocity is below the `restVelocityThreshold`, it is considered at rest. If `stop()` is called during a simulation, both `isAnimating` and `isAtRest` will be false.\n\n#### `isAnimating: boolean`\n\nWhether or not the spring is currently emitting values. Note: this is distinct from whether or not it is at rest. See also `isAtRest`.\n\n# Credits\n\nBrenton Simpson ([@appsforartists](https://twitter.com/appsforartists)) - For his assistance in creating and testing this library.\n\nDevine Lu Linvega ([@neauoire](https://twitter.com/neauoire)) - The awesome logo!\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskevy%2Fwobble","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskevy%2Fwobble","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskevy%2Fwobble/lists"}