{"id":13422300,"url":"https://github.com/gilbox/react-spark-scroll","last_synced_at":"2025-04-04T18:10:43.408Z","repository":{"id":29554243,"uuid":"33093445","full_name":"gilbox/react-spark-scroll","owner":"gilbox","description":"Scroll-based actions and animations for react","archived":false,"fork":false,"pushed_at":"2017-07-04T14:16:26.000Z","size":3967,"stargazers_count":358,"open_issues_count":5,"forks_count":48,"subscribers_count":10,"default_branch":"master","last_synced_at":"2024-10-30T06:58:18.673Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gilbox.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-03-29T23:16:18.000Z","updated_at":"2024-09-30T15:23:23.000Z","dependencies_parsed_at":"2022-09-16T18:11:09.250Z","dependency_job_id":null,"html_url":"https://github.com/gilbox/react-spark-scroll","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilbox%2Freact-spark-scroll","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilbox%2Freact-spark-scroll/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilbox%2Freact-spark-scroll/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gilbox%2Freact-spark-scroll/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gilbox","download_url":"https://codeload.github.com/gilbox/react-spark-scroll/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247226215,"owners_count":20904465,"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":[],"created_at":"2024-07-30T23:00:41.286Z","updated_at":"2025-04-04T18:10:43.383Z","avatar_url":"https://github.com/gilbox.png","language":"JavaScript","readme":"# react-spark-scroll\n\nReact port of [spark-scroll](https://github.com/gilbox/spark-scroll/).\n\n## The future!\n\nThis repo has been around for a little while now. However, recently I re-created the demo utilizing a drastically different approach which was inspired by [`react-motion`](https://github.com/chenglou/react-motion). You can find this experimental demo in the `examples/demo-functional` dir. It completely \ndoes away with animators and direct DOM manipulation in favor of pure functional elegance. \nCompatibility considerations and performance implications, etc. have not been explored. ~~Going forward, it's likely that the old way will be deprecated and this new approach will take it's place.~~ ~~*Update:* performance suffers significantly because of repeated dom-diffing, so I will probably break this out into it's own repo instead.~~\n*Update2*: It's been broken out into is own project, **[react-track](https://github.com/gilbox/react-track)**\n\n# install\n\n    # gsap and gsap-animator will be included as a dependency:\n    npm install react-spark-scroll-gsap\n\nStart with the GSAP version of the library, but note that you can use Rekapi or your own animator if you have a preference.\n\nTradeoffs:\n\n* GSAP is much easier to configure. That's because rekapi has some additional configuration necessary (see [#3](https://github.com/gilbox/react-spark-scroll/issues/3)) beyond `npm install spark-scroll-rekapi`. If you're in the quick-and-dirty experimentation stage, use gsap to get up and running faster.\n\n* Although I haven't done any benchmarks I suspect that rekapi is marginally faster than GSAP. That's because rekapi was built around the concept of timeline-based animation and spark-scroll is all about treating the scroll position as a timeline. *Update: I performed some unscientific tests and GSAP actually seems to perform significantly better*\n\n* *GSAP supports animating SVGs*. ~~This is the main deciding factor for me. If I don't need SVG animation I prefer using rekapi although it's not a strong preference.~~\n\n* Rekapi and GSAP have different licenses.\n\nAlternative installations:\n\n    # rekapi will be included as a dependency:\n    npm install react-spark-scroll-rekapi\n\nor\n\n    # in this case you will have to manually setup an animator\n    npm install react-spark-scroll\n\n\n# [demo](http://gilbox.github.io/react-spark-scroll/examples/demo/demo.html)\n\nYou can read all of the documentation below, but first checkout the\n[demo](http://gilbox.github.io/react-spark-scroll/examples/demo/demo.html)\nand the [source code](https://github.com/gilbox/react-spark-scroll/blob/master/examples/demo/app.js).\nIt's so declarative you might not even need documentation ;-)\n\n## build and run the examples\n\n    git clone https://github.com/gilbox/react-spark-scroll.git\n    cd react-spark-scroll/\n    npm i\n    npm run examples\n    open http://localhost:8080/webpack-dev-server/\n\n## why?\n\nI was curious to find out how difficult it would be to create complex animations\nwith React. At first, I thought that React's lack of a direct equivalent to\nangular's attribute-type directive (`restrict: 'A'`) would be a major drawback. However, using\n[higher-order components](https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775)\nto generate variations of the same component turned out to be a remarkably\n[elegant](https://github.com/gilbox/react-spark-scroll/blob/master/src/index.js#L70)\n[solution](https://github.com/gilbox/react-spark-scroll/blob/master/examples/demo/app.js#L25).\nIe., `\u003cSparkScroll.div /\u003e`, `\u003cSparkScroll.span /\u003e`,  `\u003cSparkScroll.h1 /\u003e`, etc...\n\nThe one place where angular might have an advantage is\nthrough it's ability to facilitate more expressive\nsyntax. For example, to [toggle a class in angular](https://github.com/gilbox/spark-scroll/blob/master/demo/index.html#L70):\n\n```html\n\u003c!-- angular: --\u003e\n\u003csection\n  class=\"pin\"\n  spark-trigger=\"pin-cont\"\n  spark-scroll=\"{\n      topTop: { 'downAddClass,upRemoveClass': 'pin-pin' },\n      bottomBottom: {  'downAddClass,upRemoveClass': 'pin-unpin' }\n  }\"\u003e\n```\n\n...vs [in react](https://github.com/gilbox/react-spark-scroll/blob/master/examples/demo/app.js#L131):\n\n```jsx\n\u003cSparkScroll.section\n  className={cx(\"pin\",{\n    'pin-pin':this.state.pinPin,\n    'pin-unpin':this.state.pinUnpin})}\n  proxy=\"pin-cont\"\n  timeline={{\n     topTop: {\n       onDown: () =\u003e this.setState({pinPin:true}),\n       onUp:   () =\u003e this.setState({pinPin:false})\n     },\n     bottomBottom: {\n       onDown: () =\u003e this.setState({pinUnpin:true}),\n       onUp:   () =\u003e this.setState({pinUnpin:false})\n     }\n  }}\u003e\n```\n\nNote that a `proxy` [is used](https://github.com/gilbox/react-spark-scroll/blob/master/examples/demo-gsap/app.js#L129) \nto provide a canonical scroll position. This is\nuseful because it's very common for the *top of the element to change* during\nscrolling.\n\n~~It is *much much much* easier to reason about what is\nactually happening in the react version. All the tricks employed by\nangular to achieve the expressiveness is not worth the confusion it\noften creates for developers, IMO.~~ I no longer have a strong opinion about\nwhich way is better. Also, it's actually possible to achieve the same \nangular syntax in React but I'm not sure if that's a good idea.\n\n\n# setup\n\n```jsx\n// the require statement returns a factory function, which we can call\n// with an options object. `invalidateAutomatically:true` is a very\n// common option.\n//\n// Note: You should normally call this factory only once, so in an application\n// with multiple JS files that need SparkScroll, it should\n// probably live in it's own file (see the examples/demo/app-spark.js)\nvar {SparkScroll, SparkProxy, sparkScrollFactory} =\n  require('react-spark-scroll/spark-scroll-rekapi')({\n    invalidateAutomatically: true\n  });\n\n// (optional)\n// We can wrap any component using the factory methods\n// Assume that `MyClass` is a React class we created\nSparkScroll['MyClass'] = sparkScrollFactory(MyClass);\n\nvar App = React.createClass({\n  render() {\n    return (\n\n      \u003cSparkScroll.h1\n        timeline={{\n          topBottom: {opacity: 0},\n          centerCenter: {opacity: 1}\n        }}\u003efade\u003c/SparkScroll.h1\u003e\n\n      \u003cSparkScroll.MyClass\n        myClassProperty=\"some value that MyClass requires\"\n        timeline={{\n          'topTop+100': {width: '0%', backgroundColor: '#5c832f'},\n          'topTop+250': {width: ['100%', 'easeOutQuart'], backgroundColor: '#382513'}\n        }} /\u003e\n    )\n  }\n});\n```\n\n# usage\n\n\n## Basic Callback Example\n\n```jsx\n\u003cSparkScroll.h1\n  timeline={{\n    120:{ onUp: _ =\u003e console.log('scrolling up past 120!') },\n    121:{ 'onUp,onDown': e =\u003e console.log('going ' + (e==='onUp' ? 'up!':'down!')) }\n  }}\u003e\n  This Title is Sparky\n\u003c/h1\u003e\n```\n\n## Formula Example\n\n```jsx\n\u003cSparkScroll.h1\n  timeline={{\n    topTop:{ onUp: _ =\u003e console.log('scrolling up past element top hit top of viewport!') },\n    'bottomBottom+50':{ 'onUp,onDown': e =\u003e console.log('going ' + (e==='onUp' ? 'up!':'down!')) }\n  }}\u003e\n  This Title is Sparky\n\u003c/h1\u003e\n```\n\n\n## Animated Example (with formulas)\n\n```jsx\n\u003cSparkScroll.h1\n  timeline={{\n    topTop:{ color: '#f00', marginLeft: '50px' },\n    topBottom:{ color: '#000', marginLeft: '0px' }\n  }}\u003e\n  This Title is Spark Animated\n\u003c/h1\u003e\n```\n\n## Animated Less-Basic Example with easing (no formulas)\n\n```jsx\n\u003cSparkScroll.h1\n  timeline={{\n    ease:'easeOutQuad',\n    120:{opacity:'0'},\n    121:{opacity:'0.8', top:'151px', color:'#fff'},\n    140:{opacity:'1.0', top:'0px', color:'#444'}\n  }}\u003e\n  This Title is Sparky\n\u003c/h1\u003e\n```\n\n## Animated Example with Override element-wide easing at a specific keyframe (with formulas)\n\n```jsx\n\u003cSparkScroll.h1\n  timeline={{\n    ease:'easeOutQuad',\n    topTop:{opacity:'0'},\n    centerCenter:{opacity:'0.8', top:'151px', color:'#fff'},\n    bottomBottom:{opacity:'1.0', top:'0px', color:'#444', ease: 'linear'}\n  }}\u003e\n  This Title is Sparky\n\u003c/h1\u003e\n```\n\n## Callback on Scroll Event\n\nThe `callback` property expects a function.\nThe function will be called for every *frame* of scrolling. `react-spark-scroll` internally debounces scroll events\nso the callback will not necessarily be called on all native scroll events.\n\nEvery time the function is called, it is provided one argument, `ratio` which is a decimal value\nbetween 0 and 1 representing the progress of scroll within the limits of the maximum and minimum\nscroll positions of the `timeline` property. The simplest use of the `callback` property\nwould look something like this:\n\n```jsx\n\u003cSparkScroll.div\n  callback={ ratio =\u003e console.log('callback @ ' + ratio) }\n  timeline={{ topBottom:0, topTop:0 }} /\u003e\n```\n\nWhen `react-spark-scroll` calls the callback function, the `ratio` is calculated based on the current scroll position,\nand the `topBottom` and `topTop` formulas.\n\nNote that in the preceding example instead of assigning an object to the keyframes (`topBottom` and `topTop`), we simply\nassign `0`. However, if we wanted to use a callback while at the same time taking advantage of *action* and\n*animation* properties we could do something like this:\n\n```jsx\n\u003cSparkScroll.h1\n  callback={ ratio =\u003e console.log('callback @ ' + ratio) }\n  timeline={{\n    topTop:{ opacity: 0 },\n    topCenter:{ opacity: 0.3 },\n    topBottom:{ opacity: 1, onUp: _ =\u003e console.log('scrolling up') }\n  }}\u003e\n  This Title is Spark\n\u003c/h1\u003e\n```\n\nNote that in this example, the `callback`'s `ratio` argument\nis calculated using the `topTop` and `topBottom` formulas because they are at the extremes of the\nkeyframe range for this element.\n\n## actions\n\nActions are triggered only when hitting a keyframe. An action can cause something to\nhappen when scrolling up past the keyframe, down past the keyframe, or both.\nThere are currently only two built-in actions: `onUp` and `onDown` which simply\ntrigger a callback function.\n\n## custom actions\n\nCustom actions may be added via the options object of the react-spark-scroll factory\nfunction, utilizing the `actions` property. For example, we could create a `log` action\nthat simply logs a message to the console whenever it's activated:\n\n```jsx\nvar sparkScroll = require('react-spark-scroll/spark-scroll-rekapi')({\n  actions: {\n    log: {\n      down(o) {\n        console.log(`spark: hit keyframe [ ${o.formula} ] scrolling down. value: ${o.val}`);\n      }\n      up(o) {\n        console.log(`spark: hit keyframe [ ${o.formula} ] scrolling up. value: ${o.val}`);\n      }\n    }\n  }\n}\n```\n\nAnd putting the new action to use might look like this:\n\n```jsx\n\u003cSparkScroll.h1\n  timeline={{\n    topBottom: {opacity: 0, log: 'foo'},\n    centerCenter: {opacity: 1, log: 'bar'}\n  }}\u003efade\u003c/SparkScroll.h1\u003e\n```\n\nWhen scrolling up and down we'd see in the console:\n\n    spark: hit keyframe [ centerCenter ] scrolling down. value: bar\n    spark: hit keyframe [ topBottom ] scrolling down. value: foo\n    spark: hit keyframe [ topBottom ] scrolling up. value: foo\n    spark: hit keyframe [ centerCenter ] scrolling up. value: bar\n\n## formulas\n\nFormulas are dynamically calculated keyframes. They usually require that you implement\nsome form of invalidation, the simplest of which is setting the `invalidateAutomatically`\noption to `true`.\n\nHere are all of the formulas that ship with react-spark-scroll:\n\n```jsx\nconst _sparkFormulas = {\n\n  // top of the element hits the top of the viewport\n  topTop(element, container, rect, containerRect, offset) {\n    return ~~(rect.top - containerRect.top + offset);\n  },\n\n  // top of the element hits the center of the viewport\n  topCenter(element, container, rect, containerRect, offset) {\n    return ~~(rect.top - containerRect.top - container.clientHeight / 2 + offset);\n  },\n\n  // top of the element hits the bottom of the viewport\n  topBottom(element, container, rect, containerRect, offset) {\n    return ~~(rect.top - containerRect.top - container.clientHeight + offset);\n  },\n\n  // center of the element hits the top of the viewport\n  centerTop(element, container, rect, containerRect, offset) {\n    return ~~(rect.top + rect.height / 2 - containerRect.top + offset);\n  },\n\n  // center of the element hits the center of the viewport\n  centerCenter(element, container, rect, containerRect, offset) {\n    return ~~(rect.top + rect.height / 2 - containerRect.top - container.clientHeight / 2 + offset);\n  },\n\n  // center of the element hits the bottom of the viewport\n  centerBottom(element, container, rect, containerRect, offset) {\n    return ~~(rect.top + rect.height / 2 - containerRect.top - container.clientHeight + offset);\n  },\n\n  // bottom of the element hits the top of the viewport\n  bottomTop(element, container, rect, containerRect, offset) {\n    return ~~(rect.bottom - containerRect.top + offset);\n  },\n\n  // bottom of the element hits the bottom of the viewport\n  bottomBottom(element, container, rect, containerRect, offset) {\n    return ~~(rect.bottom - containerRect.top - container.clientHeight + offset);\n  },\n\n  // bottom of the element hits the center of the viewport\n  bottomCenter(element, container, rect, containerRect, offset) {\n    return ~~(rect.bottom - containerRect.top - container.clientHeight / 2 + offset);\n  }\n};\n```\n\n## custom formulas\n\nFormulas allow you to add keyframes to the timeline that are dynamically calculated based\non any of the following objects:\n\n- element: DOM element\n- container: Body DOM element\n- rect: element's bounding rect\n- containerRect: container's bounding rect\n- offset: offset passed into the formula\n\nCustom formulas can be added via the options object of the react-spark-scroll factory function,\nutilizing the `formulas` property. For example:\n\n```jsx\nvar sparkScroll = require('react-spark-scroll/spark-scroll-rekapi')({\n  invalidateAutomatically: true\n  formulas: {\n\n    //similar to the built-in topBottom formula, except that offset\n    // is calculated as a percentage of the viewport height\n\n    topBottomPct: (element, container, rect, containerRect, offset) =\u003e\n      ~~(rect.bottom - containerRect.top + offset*containerRect.clientHeight/100)\n  }\n});\n```\n\n## Custom Animation Engine\n\n\nThe factory method returned by `require('react-spark-scroll')` expects an options object\nwhere only one option is *required*: `animator`. `animator` should be an object with the property\n`instance` of type `function`. Invoking `animator.instance()` returns an instance of a Spark Scroll-compatible\nanimator. Included with `react-spark-scroll` are two different animators: Rekapi and GSAP. Here\nis an example of how the GSAP animator can be used to bootstrap the factory method:\n\n```jsx\nconst _factory = require('react-spark-scroll');\n\nfunction factory(options) {\n  return _factory(assign({\n    animator: {\n      instance: () =\u003e new GSAPAnimator()\n    }\n  }, options));\n}\n```\n\nNote that we've created another factory method to wrap the `react-spark-scroll` factory method\nso that additional options may be passed in.\n\nAs mentioned, `react-spark-scroll` already ships with options for two different animation\nengines, which you can include by manually installed the dependencies you need or simply:\n\n    require('react-spark-scroll-rekapi');\n\n    // OR:\n\n    require('react-spark-scroll-gsap');\n\n\nIf you wish to use a custom animation engine, your `Animator` class must support\nthe following [Rekapi](http://rekapi.com)-like interface:\n\n    const animator = new Animator(/* optional args */);\n    const actor = animator.addActor({ context: \u003cdom element\u003e })  // works just like rekapi.addActor(...)\n    actor.keyframe(...)\n    actor.moveKeyframe(...)\n    actor.removeAllKeyframes()\n    animator.update(...)       // works just like rekapi.update(...)\n\nSee below and the [Rekapi docs](http://rekapi.com/dist/doc/) for implementation details.\n\n### `actor.keyframe(scrollY, animations, ease)`\n\nCreates a new keyframe. A keyframe should support the following properties...\n\n  - `scrollY` The vertical scroll position (the library will treat this as time)\n\n  - `animations` Simple object with css properties and values, for example:\n\n    - `{marginLeft: \"0px\", opacity: 1}`\n    - `{borderRight: \"5px\", opacity: 0}`\n\n  - `ease` Simple object with property for each property in `animations` object (see above)\n\n    - `{marginLeft: \"easeOutSine\", opacity: \"bouncePast\"}`\n    - `{borderRight: \"linear\", opacity: \"easeinSine\"}`\n\n\n### `actor.finishedAddingKeyframes`\n\nactors can *optionally* expose this function which will be called when parsing has completed\n\n\n### `actor.moveKeyframe(from, to)`\n\nMoves a keyframe to a different time (scroll) value.\n\n  - `from` Source keyframe\n\n  - `to` Destination keyframe\n\n\n### `animator.update(scrollY)`\n\nUpdates the animation to a specific keyframe.\n\n - `scrollY` The vertical scroll position (the library will treat this as time)\n\n## TweenMax/TweenLite (GSAP)\n\n\nThe syntax when using TweenMax will differ slightly\nbecause TweenMax has some differences in the animation properties it supports. For example,\nwhile Rekapi supports the `rotate` property which takes a string value like `360deg`, TweenMax\ninstead supports `rotation` which takes a numeric value like `360`. TweenMax also supports\na rather different set of [easing](http://greensock.com/roughease) equations than [Rekapi](http://rekapi.com/ease.html).\n\n**[spark-scroll TweenMax demo](http://gilbox.github.io/react-spark-scroll/examples/demo-gsap/demo.html)**\n\n\u003e Note: I suspect that Rekapi is slightly faster than GSAP for scroll-based animation\nbecause it was built specifically for keyframe\nanimations. However, **if you are interested in animating SVG then use the GSAP animator\nbecause GSAP supports SVG animations but Rekapi does not.**\n\nAs mentioned, the easiest way to use GSAP is via:\n\n    require('react-spark-scroll-gsap');\n\nHowever, this will include TweenMax. To customize your build instead of the above, use:\n\n    require('react-spark-scroll');\n\nNow you can include a subset of TweenMax since TweenMax isn't specified as a dependency of `react-spark-scroll`.\n`TweenLite.js`, `CSSPlugin.js`, and `TimelineLite.js` are the minimum subset of files\nrequired by `GSAPAnimator`. Load those files in however you wish, and then copy\n`node_modules/react-spark-scroll/src/spark-scroll-gsap.js` into your project and remove the `require('gsap')` line.\n\n\n# status\n\n### Completed:\n\n- Keyframe animations w/Rekapi\n- Formulas\n- Actions (only supports `onUp` and `onDown` with different callback semantics than `spark-scroll`)\n- onScroll `callback` prop (previously in angular was `spark-scroll-callback` attribute)\n- Custom formulas, actionProps\n- sparkSetup\n- `SparkProxy` (in angular called `sparkTrigger`)\n- publish to npm\n- Demo\n- Support for GSAP\n- README\n- Invalidation\n    * Manual invalidation mechanism\n    * Invalidation interval\n    * Automatic invalidation on window resize\n\n### Todo:\n\n- Test on various browsers\n- Re-parsing of data when changed\n\n### Probably Won't do:\n\n- [spark-scroll-ease](https://github.com/gilbox/spark-scroll/blob/master/src/spark-scroll.coffee#L213)\n  attribute (Not really liking this feature)\n  \n\n## Contributing\n\n### Publishing to NPM\n\n- First make sure to bump the version number in `package.json` in accordance with semantic versioning practices. If you think a major version bump is warranted, go for it!\n\n        # preparation\n        npm run build-npm-all\n        \n        # actually publish to npm !!! VERY IMPORTANT Do NOT run `npm publish`, !!!\n        npm run publish\n\n- Create a git tag and publish it\n\n        git tag vVERSION.NUMBER.WHATEVER\n        git push origin vVERSION.NUMBER.WHATEVER\n","funding_links":[],"categories":["UI Animation","Uncategorized","Ferramentas"],"sub_categories":["Form Components","Uncategorized","GraphQL"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgilbox%2Freact-spark-scroll","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgilbox%2Freact-spark-scroll","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgilbox%2Freact-spark-scroll/lists"}