Repository: Popmotion/popmotion Branch: master Commit: adf681efd856 Files: 536 Total size: 1.1 MB Directory structure: gitextract_kjzyuham/ ├── .babelrc ├── .circleci/ │ └── config.yml ├── .eslintrc ├── .github/ │ └── ISSUE_TEMPLATE/ │ ├── Feature_request.md │ └── bug-report.md ├── .gitignore ├── .prettierignore ├── .storybook/ │ └── config.js ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── README.md ├── lerna.json ├── package.json ├── packages/ │ ├── blend-tweens/ │ │ ├── blend-tweens.js │ │ └── cross-fade.js │ ├── framesync/ │ │ ├── CHANGELOG.md │ │ ├── LICENSE.md │ │ ├── README.md │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src/ │ │ │ ├── _tests/ │ │ │ │ └── test.ts │ │ │ ├── create-render-step.ts │ │ │ ├── index.ts │ │ │ ├── on-next-frame.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── popcorn/ │ │ └── docs/ │ │ └── api/ │ │ └── utilities/ │ │ ├── angle.md │ │ ├── apply-offset.md │ │ ├── clamp.md │ │ ├── conditional.md │ │ ├── degreesToRadians.md │ │ ├── distance.md │ │ ├── interpolate.md │ │ ├── is-point-3d.md │ │ ├── is-point.md │ │ ├── mix-array.md │ │ ├── mix-color.md │ │ ├── mix-complex.md │ │ ├── mix.md │ │ ├── pipe.md │ │ ├── point-from-vector.md │ │ ├── progress.md │ │ ├── radians-to-degrees.md │ │ ├── smooth-frame.md │ │ ├── smooth.md │ │ ├── snap.md │ │ ├── to-decimal.md │ │ ├── velocity-per-frame.md │ │ ├── velocity-per-second.md │ │ └── wrap.md │ ├── popmotion/ │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── CHANGELOG.md │ │ ├── LICENSE.md │ │ ├── README.md │ │ ├── docs/ │ │ │ ├── api/ │ │ │ │ ├── action.md │ │ │ │ ├── animation/ │ │ │ │ │ ├── decay.md │ │ │ │ │ ├── every-frame.md │ │ │ │ │ ├── inertia.md │ │ │ │ │ ├── keyframes.md │ │ │ │ │ ├── physics.md │ │ │ │ │ ├── spring.md │ │ │ │ │ ├── timeline.md │ │ │ │ │ └── tween.md │ │ │ │ ├── calc.md │ │ │ │ ├── compositors/ │ │ │ │ │ ├── chain.md │ │ │ │ │ ├── composite.md │ │ │ │ │ ├── crossfade.md │ │ │ │ │ ├── delay.md │ │ │ │ │ ├── merge.md │ │ │ │ │ ├── parallel.md │ │ │ │ │ ├── schedule.md │ │ │ │ │ └── stagger.md │ │ │ │ ├── easing.md │ │ │ │ ├── faqs.md │ │ │ │ ├── framesync.md │ │ │ │ ├── input/ │ │ │ │ │ ├── listen.md │ │ │ │ │ ├── multitouch.md │ │ │ │ │ └── pointer.md │ │ │ │ ├── reactions/ │ │ │ │ │ ├── multicast.md │ │ │ │ │ └── value.md │ │ │ │ └── transformers.md │ │ │ ├── blog/ │ │ │ │ ├── 20170703-choosing-a-default-easing-for-popmotion.md │ │ │ │ ├── 20170704-manually-set-scroll-while-ios-momentum-scroll-bounces.md │ │ │ │ ├── 20170710-mobile-web-is-awful-and-were-all-to-blame.md │ │ │ │ ├── 20170803-coding-style-dont-be-a-dick.md │ │ │ │ ├── 20171210-popmotion-8-upgrade-guide.md │ │ │ │ ├── 20171211-introducing-popmotion-8.md │ │ │ │ ├── 20180104-when-ios-throttles-requestanimationframe.md │ │ │ │ ├── 20180521-pose-2-migration-guide.md │ │ │ │ ├── 20180820-introducing-pose-3.md │ │ │ │ ├── 20180829-pose-3-1-animate-between-anything.md │ │ │ │ ├── 20180904-introducing-pose-for-vue.md │ │ │ │ ├── 20181101-react-pose-4-forwardref.md │ │ │ │ ├── 20190123-introducing-inertia-animation-for-popmotion-pure.md │ │ │ │ ├── 20190704-upgrade-to-pure-9.md │ │ │ │ ├── 20200115-pose-is-deprecated.md │ │ │ │ ├── draft-60fps-hi-res-image-scaling-with-translatez-and-maths.md │ │ │ │ ├── draft-css-vs-js-animation.md │ │ │ │ └── draft-the-emerging-reality.md │ │ │ └── learn/ │ │ │ ├── advanced/ │ │ │ │ └── value-pipelines.md │ │ │ ├── basics/ │ │ │ │ ├── action-reaction.md │ │ │ │ ├── get-started.md │ │ │ │ ├── input-tracking.md │ │ │ │ ├── install.md │ │ │ │ └── velocity-and-physics.md │ │ │ ├── how-to/ │ │ │ │ ├── circular-motion.md │ │ │ │ ├── constrain-motion.md │ │ │ │ ├── input-smoothing.md │ │ │ │ ├── morph-svg.md │ │ │ │ └── rounded-values.md │ │ │ └── projects/ │ │ │ └── spring-loaded-characters-remaining.md │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src/ │ │ │ ├── animations/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── animate.test.ts │ │ │ │ │ ├── inertia.test.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── generators/ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ ├── decay.test.ts │ │ │ │ │ │ ├── keyframes.test.ts │ │ │ │ │ │ ├── spring.test.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── decay.ts │ │ │ │ │ ├── keyframes.ts │ │ │ │ │ └── spring.ts │ │ │ │ ├── index.ts │ │ │ │ ├── inertia.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── detect-animation-from-config.test.ts │ │ │ │ │ ├── elapsed.test.ts │ │ │ │ │ └── find-spring.test.ts │ │ │ │ ├── detect-animation-from-options.ts │ │ │ │ ├── elapsed.ts │ │ │ │ └── find-spring.ts │ │ │ ├── easing/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── cubic-bezier.test.ts │ │ │ │ │ ├── index.test.ts │ │ │ │ │ ├── steps.test.ts │ │ │ │ │ └── utils.test.ts │ │ │ │ ├── cubic-bezier.ts │ │ │ │ ├── index.ts │ │ │ │ ├── steps.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── utils/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── angle.test.ts │ │ │ │ │ ├── apply-offset.test.ts │ │ │ │ │ ├── attract.test.ts │ │ │ │ │ ├── clamp.test.ts │ │ │ │ │ ├── degrees-to-radians.test.ts │ │ │ │ │ ├── distance.test.ts │ │ │ │ │ ├── hsla-to-rgba.test.ts │ │ │ │ │ ├── interpolate.test.ts │ │ │ │ │ ├── is-point-3d.test.ts │ │ │ │ │ ├── is-point.test.ts │ │ │ │ │ ├── mix-array.test.ts │ │ │ │ │ ├── mix-color.test.ts │ │ │ │ │ ├── mix-complex.test.ts │ │ │ │ │ ├── mix-object.test.ts │ │ │ │ │ ├── mix.test.ts │ │ │ │ │ ├── point-from-vector.test.ts │ │ │ │ │ ├── progress.test.ts │ │ │ │ │ ├── radians-to-degrees.test.ts │ │ │ │ │ ├── smooth-frame.test.ts │ │ │ │ │ ├── smooth.test.ts │ │ │ │ │ ├── snap.test.ts │ │ │ │ │ ├── to-decimal.test.ts │ │ │ │ │ ├── velocity-per-frame.test.ts │ │ │ │ │ ├── velocity-per-second.test.ts │ │ │ │ │ └── wrap.test.ts │ │ │ │ ├── angle.ts │ │ │ │ ├── apply-offset.ts │ │ │ │ ├── attract.ts │ │ │ │ ├── clamp.ts │ │ │ │ ├── degrees-to-radians.ts │ │ │ │ ├── distance.ts │ │ │ │ ├── hsla-to-rgba.ts │ │ │ │ ├── inc.ts │ │ │ │ ├── interpolate.ts │ │ │ │ ├── is-point-3d.ts │ │ │ │ ├── is-point.ts │ │ │ │ ├── mix-color.ts │ │ │ │ ├── mix-complex.ts │ │ │ │ ├── mix.ts │ │ │ │ ├── pipe.ts │ │ │ │ ├── point-from-vector.ts │ │ │ │ ├── progress.ts │ │ │ │ ├── radians-to-degrees.ts │ │ │ │ ├── smooth-frame.ts │ │ │ │ ├── smooth.ts │ │ │ │ ├── snap.ts │ │ │ │ ├── to-decimal.ts │ │ │ │ ├── velocity-per-frame.ts │ │ │ │ ├── velocity-per-second.ts │ │ │ │ └── wrap.ts │ │ │ └── worklet/ │ │ │ ├── animate.ts.wip │ │ │ ├── custom-properties.ts │ │ │ ├── index.ts.wip │ │ │ └── load-worklet.ts │ │ ├── tsconfig.json │ │ └── tslint.json │ ├── popmotion-pose/ │ │ └── docs/ │ │ ├── api/ │ │ │ ├── dom/ │ │ │ │ ├── config.md │ │ │ │ └── dom-pose.md │ │ │ ├── faqs.md │ │ │ ├── react/ │ │ │ │ ├── posed.md │ │ │ │ ├── posegroup.md │ │ │ │ ├── react-config.md │ │ │ │ ├── react-pose-text.md │ │ │ │ └── supported-values.md │ │ │ ├── react-native/ │ │ │ │ ├── native-config.md │ │ │ │ ├── native-posed.md │ │ │ │ ├── native-supported-values.md │ │ │ │ └── native-transition.md │ │ │ └── vue/ │ │ │ ├── vue-config.md │ │ │ ├── vue-posed.md │ │ │ ├── vue-posetransition.md │ │ │ └── vue-supported-values.md │ │ ├── examples/ │ │ │ ├── react/ │ │ │ │ ├── accordion.md │ │ │ │ ├── css-variables.md │ │ │ │ ├── enter-exit.md │ │ │ │ ├── event-drag-boundaries.md │ │ │ │ ├── event-drag.md │ │ │ │ ├── event-focus.md │ │ │ │ ├── event-hover.md │ │ │ │ ├── event-press.md │ │ │ │ ├── filter.md │ │ │ │ ├── outline.md │ │ │ │ ├── posegroup-reordering.md │ │ │ │ ├── react-medium-style-image-zoom.md │ │ │ │ ├── route-transitions-reach-router.md │ │ │ │ ├── route-transitions-react-router.md │ │ │ │ ├── splittext-as-children.md │ │ │ │ ├── splittext-custom-animations.md │ │ │ │ ├── splittext-ui-events.md │ │ │ │ ├── splittext.md │ │ │ │ └── svg-morphing.md │ │ │ └── vue/ │ │ │ ├── vue-animating-children.md │ │ │ ├── vue-enter-exit.md │ │ │ ├── vue-flip.md │ │ │ ├── vue-magic-animations.md │ │ │ ├── vue-medium-style-image-zoom.md │ │ │ ├── vue-passive-values.md │ │ │ ├── vue-ui-events-dragging.md │ │ │ ├── vue-ui-events-focus.md │ │ │ ├── vue-ui-events-hover.md │ │ │ └── vue-ui-events-press.md │ │ └── learn/ │ │ ├── get-started.md │ │ ├── how-to/ │ │ │ ├── css-variables.md │ │ │ └── framer-x.md │ │ ├── react/ │ │ │ ├── animating-children.md │ │ │ ├── custom-transitions.md │ │ │ ├── dynamic-props.md │ │ │ ├── flip.md │ │ │ ├── install.md │ │ │ ├── passive.md │ │ │ ├── popmotion-get-started.md │ │ │ ├── react-exit-enter-transitions.md │ │ │ ├── react-tutorial-medium-style-image-zoom.md │ │ │ ├── route-transitions-reach-router.md │ │ │ ├── route-transitions-react-router.md │ │ │ └── ui-events.md │ │ ├── react-native/ │ │ │ ├── native-animating-children.md │ │ │ ├── native-custom-transitions.md │ │ │ ├── native-dragging.md │ │ │ ├── native-get-started.md │ │ │ └── native-passive.md │ │ └── vue/ │ │ ├── vue-animating-children.md │ │ ├── vue-custom-transitions.md │ │ ├── vue-dynamic-props.md │ │ ├── vue-enter-exit-transitions.md │ │ ├── vue-flip.md │ │ ├── vue-get-started.md │ │ ├── vue-install.md │ │ ├── vue-passive.md │ │ ├── vue-tutorial-medium-style-image-zoom.md │ │ └── vue-ui-events.md │ ├── projection/ │ │ ├── CHANGELOG.md │ │ ├── LICENSE.md │ │ ├── README.md │ │ ├── cypress/ │ │ │ ├── fixtures/ │ │ │ │ └── example.json │ │ │ ├── integration/ │ │ │ │ └── index.spec.js │ │ │ ├── plugins/ │ │ │ │ └── index.js │ │ │ ├── support/ │ │ │ │ ├── commands.js │ │ │ │ └── index.js │ │ │ └── tests/ │ │ │ └── index.html │ │ ├── cypress.json │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src/ │ │ │ ├── __tests__/ │ │ │ │ └── index.test.ts │ │ │ ├── dom/ │ │ │ │ ├── correct-border-radius.ts │ │ │ │ ├── correct-box-shadow.ts │ │ │ │ └── style.ts │ │ │ ├── geometry/ │ │ │ │ ├── __tests__/ │ │ │ │ │ ├── apply.test.ts │ │ │ │ │ └── calc.test.ts │ │ │ │ ├── apply.ts │ │ │ │ ├── calc.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── index.ts │ │ │ ├── node.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── site/ │ │ ├── .babelrc │ │ ├── .gitignore │ │ ├── components/ │ │ │ ├── examples/ │ │ │ │ ├── Ball.js │ │ │ │ ├── CodePen.js │ │ │ │ ├── CodeSandbox.js │ │ │ │ ├── Counter.js │ │ │ │ ├── Example.js │ │ │ │ ├── Swatch.js │ │ │ │ └── templates.js │ │ │ ├── icons/ │ │ │ │ ├── BrandGradientDef.js │ │ │ │ ├── DropDownArrow.js │ │ │ │ ├── FramesyncLogo.js │ │ │ │ ├── GitHub.js │ │ │ │ ├── Logo.js │ │ │ │ ├── PopcornLogo.js │ │ │ │ ├── PopmotionIcon.js │ │ │ │ ├── PopmotionPure.js │ │ │ │ ├── PoseLogo.js │ │ │ │ ├── StylefireLogo.js │ │ │ │ └── Twitter.js │ │ │ ├── layout/ │ │ │ │ ├── Content.js │ │ │ │ ├── SiteLink.js │ │ │ │ └── grid.js │ │ │ └── template.js │ │ ├── data/ │ │ │ ├── authors.json │ │ │ ├── category-names.json │ │ │ ├── route-paths.json │ │ │ ├── section-names.json │ │ │ ├── settings.json │ │ │ └── site-names.json │ │ ├── docs/ │ │ │ ├── popcorn/ │ │ │ │ └── index.md │ │ │ └── stylefire/ │ │ │ └── index.md │ │ ├── package.json │ │ ├── pages/ │ │ │ ├── _document.js │ │ │ ├── _error.js │ │ │ ├── api.js │ │ │ ├── blog.js │ │ │ ├── index.js │ │ │ ├── popcorn/ │ │ │ │ └── index.js │ │ │ ├── pose/ │ │ │ │ ├── api.js │ │ │ │ ├── examples.js │ │ │ │ └── index.js │ │ │ ├── pure/ │ │ │ │ ├── api/ │ │ │ │ │ ├── action.js │ │ │ │ │ ├── calc.js │ │ │ │ │ ├── chain.js │ │ │ │ │ ├── composite.js │ │ │ │ │ ├── crossfade.js │ │ │ │ │ ├── css.js │ │ │ │ │ ├── decay.js │ │ │ │ │ ├── delay.js │ │ │ │ │ ├── easing.js │ │ │ │ │ ├── every-frame.js │ │ │ │ │ ├── framesync.js │ │ │ │ │ ├── keyframes.js │ │ │ │ │ ├── listen.js │ │ │ │ │ ├── merge.js │ │ │ │ │ ├── multicast.js │ │ │ │ │ ├── multitouch.js │ │ │ │ │ ├── parallel.js │ │ │ │ │ ├── physics.js │ │ │ │ │ ├── pointer.js │ │ │ │ │ ├── schedule.js │ │ │ │ │ ├── scroll.js │ │ │ │ │ ├── spring.js │ │ │ │ │ ├── stagger.js │ │ │ │ │ ├── stylefire.js │ │ │ │ │ ├── svg.js │ │ │ │ │ ├── timeline.js │ │ │ │ │ ├── transformers.js │ │ │ │ │ ├── tween.js │ │ │ │ │ ├── value-types.js │ │ │ │ │ └── value.js │ │ │ │ ├── blog/ │ │ │ │ │ ├── 20170703-choosing-a-default-easing-for-popmotion.js │ │ │ │ │ ├── 20170704-manually-set-scroll-while-ios-momentum-scroll-bounces.js │ │ │ │ │ ├── 20170710-mobile-web-is-awful-and-were-all-to-blame.js │ │ │ │ │ ├── 20170803-coding-style-dont-be-a-dick.js │ │ │ │ │ ├── 20171210-popmotion-8-upgrade-guide.js │ │ │ │ │ ├── 20171211-introducing-popmotion-8.js │ │ │ │ │ ├── 20180104-when-ios-throttles-requestanimationframe.js │ │ │ │ │ └── 20180521-pose-2-migration-guide.js │ │ │ │ ├── index.js │ │ │ │ └── learn/ │ │ │ │ ├── action-reaction.js │ │ │ │ ├── circular-motion.js │ │ │ │ ├── constrain-motion.js │ │ │ │ ├── get-started.js │ │ │ │ ├── input-smoothing.js │ │ │ │ ├── input-tracking.js │ │ │ │ ├── install.js │ │ │ │ ├── morph-svg.js │ │ │ │ ├── rounded-values.js │ │ │ │ ├── spring-loaded-characters-remaining.js │ │ │ │ ├── value-pipelines.js │ │ │ │ └── velocity-and-physics.js │ │ │ ├── stylefire/ │ │ │ │ ├── api/ │ │ │ │ │ ├── create-styler-factory.js │ │ │ │ │ ├── html.js │ │ │ │ │ ├── styler.js │ │ │ │ │ ├── svg.js │ │ │ │ │ └── viewport.js │ │ │ │ ├── api.js │ │ │ │ └── index.js │ │ │ └── support.js │ │ ├── public/ │ │ │ └── manifest.json │ │ ├── scripts/ │ │ │ ├── build-next-config.js │ │ │ ├── convert-date-format.js │ │ │ ├── filename-operations.js │ │ │ ├── generate-content-page.js │ │ │ ├── generate-content.js │ │ │ └── generate-menus.js │ │ ├── styles/ │ │ │ ├── fonts.js │ │ │ ├── nprogress.js │ │ │ ├── reset.js │ │ │ ├── syntax-highlighting.js │ │ │ ├── themes.js │ │ │ └── vars.js │ │ ├── templates/ │ │ │ ├── Popcorn/ │ │ │ │ └── index.js │ │ │ ├── Popmotion/ │ │ │ │ ├── FinalCTA/ │ │ │ │ │ ├── index.js │ │ │ │ │ └── styled.js │ │ │ │ ├── Header/ │ │ │ │ │ └── index.js │ │ │ │ ├── Masthead/ │ │ │ │ │ ├── index.js │ │ │ │ │ └── styled.js │ │ │ │ ├── USPs/ │ │ │ │ │ ├── Example.js │ │ │ │ │ ├── ExampleSection.js │ │ │ │ │ ├── index.js │ │ │ │ │ └── styled.js │ │ │ │ └── index.js │ │ │ ├── Pose/ │ │ │ │ ├── Header/ │ │ │ │ │ └── index.js │ │ │ │ ├── USPs/ │ │ │ │ │ ├── AnimateAnything.js │ │ │ │ │ ├── ChildrenExample.js │ │ │ │ │ ├── CustomExample.js │ │ │ │ │ ├── DeclarativeExample.js │ │ │ │ │ ├── DraggableExample.js │ │ │ │ │ ├── FlipExample.js │ │ │ │ │ ├── PassiveExample.js │ │ │ │ │ ├── PluginsExample.js │ │ │ │ │ ├── ReactExample.js │ │ │ │ │ ├── ZeroConfigExample.js │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ ├── Stylefire/ │ │ │ │ └── index.js │ │ │ ├── blog/ │ │ │ │ ├── components/ │ │ │ │ │ └── BlogItem/ │ │ │ │ │ ├── index.js │ │ │ │ │ └── styled.js │ │ │ │ └── index.js │ │ │ ├── content/ │ │ │ │ ├── ContentCTA/ │ │ │ │ │ ├── index.js │ │ │ │ │ └── styled.js │ │ │ │ ├── ContentNav.js │ │ │ │ ├── MenuPage/ │ │ │ │ │ └── index.js │ │ │ │ ├── PostDetails/ │ │ │ │ │ └── index.js │ │ │ │ ├── TableOfContents/ │ │ │ │ │ ├── index.js │ │ │ │ │ └── styled.js │ │ │ │ ├── Template.js │ │ │ │ └── styled.js │ │ │ ├── global/ │ │ │ │ ├── Analytics.js │ │ │ │ ├── Footer.js │ │ │ │ ├── Header.js │ │ │ │ ├── SectionNav.js │ │ │ │ ├── SocialLinks.js │ │ │ │ ├── Template.js │ │ │ │ ├── grid.js │ │ │ │ └── styled.js │ │ │ ├── global-new/ │ │ │ │ ├── ContentPage/ │ │ │ │ │ └── index.js │ │ │ │ ├── Footer/ │ │ │ │ │ ├── index.js │ │ │ │ │ └── styled.js │ │ │ │ ├── Header/ │ │ │ │ │ └── index.js │ │ │ │ ├── Nav/ │ │ │ │ │ ├── index.js │ │ │ │ │ └── styled.js │ │ │ │ └── styled.js │ │ │ └── homepage/ │ │ │ └── index.js │ │ └── utils/ │ │ └── string.js │ └── style-value-types/ │ ├── CHANGELOG.md │ ├── LICENSE.md │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src/ │ │ ├── _tests/ │ │ │ └── index.test.ts │ │ ├── color/ │ │ │ ├── hex.ts │ │ │ ├── hsla.ts │ │ │ ├── index.ts │ │ │ ├── rgba.ts │ │ │ └── utils.ts │ │ ├── complex/ │ │ │ ├── filter.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── numbers/ │ │ │ ├── index.ts │ │ │ └── units.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── tsconfig.json │ └── tslint.json ├── playground/ │ ├── animate.js │ ├── index.js │ └── worklet.js ├── rollup-generate-config.js ├── tsconfig.json ├── tslint.json └── types/ ├── animations/ │ ├── decay.d.ts │ ├── index.d.ts │ ├── inertia.d.ts │ ├── keyframes.d.ts │ ├── spring.d.ts │ ├── types.d.ts │ └── utils/ │ └── detect-animation-from-options.d.ts ├── easing/ │ ├── cubic-bezier.d.ts │ ├── index.d.ts │ ├── steps.d.ts │ ├── types.d.ts │ └── utils.d.ts ├── global.d.ts ├── index.d.ts ├── types.d.ts ├── utils/ │ ├── angle.d.ts │ ├── apply-offset.d.ts │ ├── attract.d.ts │ ├── clamp.d.ts │ ├── degrees-to-radians.d.ts │ ├── distance.d.ts │ ├── inc.d.ts │ ├── interpolate.d.ts │ ├── is-point-3d.d.ts │ ├── is-point.d.ts │ ├── mix-color.d.ts │ ├── mix-complex.d.ts │ ├── mix.d.ts │ ├── pipe.d.ts │ ├── point-from-vector.d.ts │ ├── progress.d.ts │ ├── radians-to-degrees.d.ts │ ├── smooth-frame.d.ts │ ├── smooth.d.ts │ ├── snap.d.ts │ ├── to-decimal.d.ts │ ├── velocity-per-frame.d.ts │ ├── velocity-per-second.d.ts │ └── wrap.d.ts └── worklet/ ├── custom-properties.d.ts └── load-worklet.d.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [["env", { "loose": true} ], "stage-2"], "plugins": ["transform-export-extensions", "transform-react-jsx"], "env": { "development": { "sourceMaps": "inline" } } } ================================================ FILE: .circleci/config.yml ================================================ # Javascript Node CircleCI 2.0 configuration file # # Check https://circleci.com/docs/2.0/language-javascript/ for more details # version: 2 jobs: build: docker: # specify the version you desire here - image: circleci/node:10 # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ # - image: circleci/mongo:3.4.4 working_directory: ~/repo steps: - checkout # Download and cache dependencies - restore_cache: keys: - v1-dependencies-{{ checksum "package.json" }} # fallback to using the latest cache if no exact match is found - v1-dependencies- - run: yarn - run: yarn bootstrap - save_cache: paths: - node_modules key: v1-dependencies-{{ checksum "package.json" }} # Build and run tests - run: yarn test - run: yarn build ================================================ FILE: .eslintrc ================================================ { "rules": { "strict": 0, "indent": [2, 2], "quotes": [2, "single"], "linebreak-style": [2, "unix"], "semi": [2, "always"], "no-unused-vars": [2, { "ignoreRestSiblings": true }], "react/jsx-uses-react": 1, "react/jsx-uses-vars": 1 }, "ecmaFeatures": { "restParams": true, "destructuring": true, "modules": true, "jsx": true }, "env": { "es6": true, "browser": true, "node": true, "jest": true }, "parser": "babel-eslint", "extends": "eslint:recommended", "plugins": ["jsx-a11y", "react", "import", "jasmine", "flowtype"] } ================================================ FILE: .github/ISSUE_TEMPLATE/Feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.md ================================================ --- name: Bug report about: Create a report to help us improve --- ## Ensure you're using the latest version CodeSandbox examples might not always be using the latest version of Pose. ## Add the package name to bug title For instance, if this is a bug in Popmotion, write "Popmotion: Name of bug" ## Describe the bug A clear and concise description of what the bug is. ## How to reproduce Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error ## Expected behaviour A clear and concise description of what you expected to happen. ## Link to CodePen example (or similar) ## Device information In which environment are you reproducing the bug? - Device: [e.g. iPhone6] - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] ## Additional context Add any other context about the problem here. ================================================ FILE: .gitignore ================================================ node_modules .DS_Store lib packages/site/.next packages/site/pages/api packages/site/pages/learn packages/site/pages/blog packages/site/pages/pose/api packages/site/pages/pose/examples packages/site/pages/pose/learn packages/site/pages/popcorn/api packages/site/data/content.json packages/site/data/menus.json packages/site/next.config.js packages/projection/cypress/screenshots/* playground/public storybook-static coverage .vscode tmp dist *.log videos ================================================ FILE: .prettierignore ================================================ *.md ================================================ FILE: .storybook/config.js ================================================ import { configure } from '@storybook/react'; function loadStories() { require('../playground'); } configure(loadStories, module); ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of conduct ![Be excellent to each other](https://user-images.githubusercontent.com/7850794/33840491-f80357bc-de8c-11e7-82fb-408f4aaeb24c.png) ================================================ FILE: CONTRIBUTING.md ================================================ # How to Contribute This guide will run through the types of contributions you can make and how to set the Popmotion repo up, ready for local development. - [Types of Contribution](#types-of-contribution) - [Bugs](#bugs) - [Features](#features) - [Development](#development) - [Codebase Overview](#codebase-overview) - [Package Hierarchy](#package-hierarchy) - [Get in Touch](#get-in-touch) ## Types of Contribution ### Bugs Open issues can be found on our [GitHub issues](https://github.com/Popmotion/popmotion/issues) page. If you begin working on a bug, post your intent on the issue itself. This will prevent more than one person tackling a bug at once. If the bug you wish to work on doesn't currently have an issue, make one and label it "Bug". ### Features Before adding any features, open a Feature Proposal as a [new issue](https://github.com/Popmotion/popmotion/issues). This will let us talk through your proposed API and/or implementation before you spend time on it. ## Development Follow these steps to get your code PR-ready: 1. [Fork the Popmotion repo](https://github.com/Popmotion/popmotion). 2. Clone your fork locally. 3. Run `yarn`, `yarn bootstrap`, and `yarn build` in the repo root ([install Yarn](https://yarnpkg.com/lang/en/docs/install/#mac-stable) if necessary). 4. Add your code and supporting tests. 5. If this is a feature that requires doc changes, make as necessary. 6. Update `CHANGELOG.md`. 7. Ensure your changes build by running `yarn build` in the appropriate package directory(s). ## Codebase Overview Other than [Stylefire](https://github.com/popmotion/stylefire) and [Framesync](https://github.com/popmotion/framesync), Popmotion is a monorepo. All Popmotion and Pose packages are contained in the [`packages` directory](https://github.com/Popmotion/popmotion/tree/master/packages). All code and tests must be written in Typescript. ### Package Hierarchy This flowchart illustrates the Popmotion package dependency tree. **Note:** Currently, this illustration depicts `popmotion` as **not** dependent on `stylefire`, which isn't currently true. In `popmotion@9.0.0`, I'm considering removing Stylefire as a Popmotion dependency. ![Popmotion package hierarchy flowchart](https://user-images.githubusercontent.com/7850794/41407730-3d921f26-6fc8-11e8-9de6-f1756572f6fc.png) ## Get in Touch If you need help, you can reach out to the [official Popmotion Twitter](https://twitter.com/popmotionjs) account. ================================================ FILE: README.md ================================================ # Popmotion ### The animator's toolbox [![npm version](https://img.shields.io/npm/v/popmotion.svg?style=flat-square)](https://www.npmjs.com/package/popmotion) [![npm downloads](https://img.shields.io/npm/dm/popmotion.svg?style=flat-square)](https://www.npmjs.com/package/popmotion) [![Twitter Follow](https://img.shields.io/twitter/follow/popmotionjs.svg?style=social&label=Follow)](http://twitter.com/popmotionjs) Popmotion is: - **Powerful**: It supports keyframe and spring animations for numbers, colors and complex strings. - **Low level**: It's designed to be composable and portable into any JavaScript environment, with an eye on worklets in the future. - **Stable**: It's written in TypeScript and enjoys over 95% test coverage. - **Tiny**: `animate` is just ~4.5kb, and every function is individually importable. ## Quick start ```bash npm install popmotion ``` ```javascript import { animate } from "popmotion" animate({ from: 0, to: 100, onUpdate: latest => console.log(latest) }) ``` ## Animation ### animate `animate` performs a keyframes or spring animation. ```javascript import { animate } from "popmotion" animate({ from: 0, to: 100, onUpdate: latest => console.log(latest) }) ``` It can animate numbers: ```javascript animate({ from: 0, to: 100 }) ``` Or strings of the same type: ```javascript animate({ from: "0px", to: "100px" }) animate({ from: "#fff", to: "#000" }) ``` The strings can be pretty complex, for instance box shadows or SVG path definitions. The only limitation is that the numbers and colors contained within must be in the same order: ```javascript animate({ from: "0px 0px 0px rgba(0, 0, 0, 0)", to: "10px 10px 0px rgba(0, 0, 0, 0.2)" }) ``` The type of animation performed will be automatically detected from the provided options, or can be chosen manually by defining `type` as `"keyframes"`, `"spring"` or `"decay"`. #### Options These options can be set for **all animations**: ##### from An initial value to start the animation from. Defaults to `0` ```javascript animate({ from: "linear-gradient(#e66465, #9198e5)", to: "linear-gradient(#9198e5, #e66465)" }) ``` ##### elapsed Sets an initial elapsed time, in milliseconds. Set to a negative value for a delay. ```javascript animate({ to: 100, elapsed: -300 }) ``` ##### repeat The number of times to repeat the animation. Set to `Infinity` to repeat forever. ```javascript animate({ to: 100, repeat: 2 }) ``` ##### repeatDelay The duration, in milliseconds, to wait before repeating the animation. ```javascript animate({ to: 100, repeat: 2, repeatDelay: 200 }) ``` ##### repeatType Either `"loop"`, `"mirror"` or `"reverse"`. Defaults to `"loop"`. - `"loop"`: Repeats the animation from `0`. - `"mirror":` Swaps the `from`/`to` values alternately. - `"reverse":` Reverses the animation alternately. ```javascript animate({ to: 100, repeat: 2, repeatType: "reverse" }) ``` ##### driver By default, the animation will be driven by a `requestAnimationFrame` loop. `driver` can specify a different source. A `Driver` is a function that accepts the animations `update` function. This is a function that can be called with a time delta from the previous frame. The `Driver` must return a function that will be called when the animation is stopped. ```javascript const xrDriver = session => update => { let latestRequestId = 0 let prevTimestamp = performance.now() const step = timestamp => { const delta = timestamp - prevTimestamp prevTimestamp = timestamp update(delta) latestRequestId = session.requestAnimationFrame(step) } let latestRequestId = session.requestAnimationFrame(step) return () => session.cancelRequestAnimationFrame(latestRequestId) } animate({ to: 100, driver: xrDriver(xrSession) }) ``` ##### type `animate` will automatically detect the type of animation to use based on the options provided. But a specific type can be chosen manually by defining `type` as `"keyframes"`, `"spring"` or `"decay"`. ```jsx animate({ to: 100, type: "spring" }) ``` #### Lifecycle events The following lifecycle events are available for **all animations**: ##### onUpdate This is called every frame the animation fires with the latest computed value. ```javascript animate({ to: 100, onUpdate: latest => console.log(latest) }) ``` ##### onPlay This is called when the animation starts. Currently this automatically when `animate` is called. ```javascript animate({ to: 100, onPlay: () => {} }) ``` ##### onComplete This is called when the animation successfully completes. ```javascript animate({ to: 100, onComplete:() => {} }) ``` ##### onRepeat This is called when an animation repeats. ```javascript animate({ to: 100, repeat: 2, onRepeat: () => {} }) ``` ##### onStop This is called when the animation is stopped by the `stop` control. ```javascript const animation = animate({ to: 100, onStop: () => {} }) animation.stop() ``` #### Keyframes options A keyframes animation is the default animation type and it can be defined either with a `from` and `to` option: ```javascript animate({ from: 0, to: 100 }) ``` Or as a series of keyframes provided to the `to` option: ```javascript animate({ to: [0, 100, 200] }) ``` ##### to A single value to animate to, or an array of values to animate through. ```javascript animate({ to: ["#0ff", "#f00", "#0f0"] }) ``` If `to` is an array, any defined `from` will be ignored. ##### duration This defines the duration of the animation, in milliseconds. ```javascript animate({ to: 100, duration: 300 }) ``` ##### ease This is an easing function, or array of functions, to use when easing between each keyframe. ```javascript import { animate, linear, easeInOut } from "popmotion" animate({ to: 100, ease: linear }) animate({ to: ["#fff", "#000", "#f00"], ease: [linear, easeInOut] }) ``` If set as any array, the length of this array must be one shorter than the number of values being animated between. ##### offset This is an array of values between `0` and `1` that defines at which point throughout the animation each keyframe should be reached. This array should be the same length as the number of defined keyframes. ```javascript animate({ to: ["#fff", "#000", "#f00"], offset: [0, 0.2, 1] }) ``` #### Spring options Springs are great for creating natural-feeling interfaces and dynamic interruptable animations. A spring animation will be used if any of the `stiffness`, `damping` or `mass` options are detected. **Note:** A spring simulation is inherently numerical so if it's given a color, array or object, it runs the animation from `0` to `100` and interpolates that to the given values. This strategy is likely to be tweaked before the official release so animations made this way may change in feel. ##### to A single value to animate to. ```javascript animate({ to: 100, type: "spring" }) ``` If `to` is an array, any defined `from` will be ignored. ##### stiffness This defines the stiffness of the spring. A higher stiffness will result in a snappier animation. Defaults to `100` ```javascript animate({ to: 100, stiffness: 1000 }) ``` ##### damping This is the opposing force to `stiffness`. As you reduce this value, relative to `stiffness`, the spring will become bouncier and the animation will last longer. Likewise, higher relative values will have less bounciness and result in shorter animations. Defaults to `10` ```javascript animate({ to: 100, damping: 50 }) ``` ##### mass This is the mass of the animating object. Heavier objects will take longer to speed up and slow down. Defaults to `1`. ```javascript animate({ to: 100, mass: 2 }) ``` ##### velocity The initial velocity, in units per second, of the animation. ```javascript animate({ to: 100, velocity: 1000 }) ``` ##### duration The duration of the spring, in milliseconds. Will be overridden by `stiffness`, `mass` or `damping`. ```javascript animate({ to: 100, duration: 1000 }) ``` ##### bounce The bounciness of the spring, as a value between `0` and `1`, where `0` is no bounce. Will be overridden by `stiffness`, `mass` or `damping`. ```javascript animate({ to: 100, bounce: 0.2 }) ``` ##### restDelta The distance from the animation target at which the animation can be considered complete. When both `restDelta` and `restSpeed` are met, the animation completes. ```javascript animate({ to: 100, restDelta: 0.5 }) ``` ##### restSpeed The absolute velocity, in units per second, below which the animation can be considered complete. When both `restDelta` and `restSpeed` are met, the animation completes. Defaults to `10`. ```javascript animate({ to: 100, restSpeed: 5 }) ``` #### Playback controls `animate` returns `PlaybackControls`, which can be used to control the playback of the animation. Currently this only includes a `stop` method, but may expand with more. ##### stop Stops the animation. ```javascript const playback = animate({ from: 0, to: 100 }) playback.stop() ``` ### inertia The `inertia` animation is used to gradually decelerate a number. Think smartphone scroll momentum. #### Options In addition to `animate`'s `from`, `onUpdate` and `onComplete` options, `inertia` also supports the following: ##### velocity The initial velocity, in units per second, of the animation. ```javascript inertia({ from: 0, velocity: 100 }) ``` ##### power A constant with which to calculate a target value. Higher power = further target. Defaults to `0.8`. ```javascript inertia({ from: 0, power: 0.3 }) ``` ##### timeConstant Adjusting the time constant will change the duration of the deceleration, thereby affecting its feel. Defaults to `350`. ```javascript inertia({ from: 0, velocity: 100, timeConstant: 400 }) ``` ##### modifyTarget A function that receives the calculated target and returns a new one. Useful for snapping the target to a grid. ```javascript const roundToNearest = target => v => Math.ceil(v / target) * target inertia({ from: 0, velocity: 100, modifyTarget: roundToNearest(100) }) ``` ##### min The minimum value at which the animation will switch from gradual deceleration and use a spring animation to snap to this point. ```javascript inertia({ from: 50, velocity: -100, min: 0 }) ``` ##### max The maximum value at which the animation will switch from gradual deceleration and use a spring animation to snap to this point. ```javascript inertia({ from: 50, velocity: 100, max: 100 }) ``` ##### bounceStiffness This defines the stiffness of the spring when the animation hits either `min` or `max`. A higher stiffness will result in a snappier animation. Defaults to `500` ```javascript inertia({ from: 0, velocity: 100, max: 50, bounceStiffness: 1000 }) ``` ##### bounceDamping This is the opposing force to `bounceStiffness`. As you reduce this value, relative to `bounceStiffness`, the spring will become bouncier and the animation will last longer. Likewise, higher relative values will have less bounciness and result in shorter animations. Defaults to `10` ```javascript inertia({ from: 0, velocity: 100, max: 50, bounceDamping: 300 }) ``` ##### restDelta The distance from the animation target at which the animation can be considered complete. ```javascript inertia({ from: 0, velocity: 100, restDelta: 0.5 }) ``` ### Iterators Powering `animate` and `inertia` are the `keyframes`, `spring`, and `decay` [iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterator_protocol). ```javascript import { keyframes, spring, decay } from "popmotion"; ``` Iterators give you the ability to run an animation with a high degree of control. For example, [Framer](https://framer.com) uses the `spring` iterator to draw its animation editor visualiser by running it synchronously. Each can be initialised with the matching options above (`decay` with a subset of `inertia`'s options, excluding the `bounce-` options): ```javascript const animation = spring({ from: 0, to: 100, stiffness: 200 }) ``` With the returned iterator, you can resolve the animation at a specific timestamp with its `next` method. ```javascript // Resolve the animation at 200ms const { value, done } = animation.next(200) ``` ## Easing Popmotion includes a number of in-built easing functions, as well as factory functions to make entirely new ones. ### Functions Each easing function can be imported like so: ```javascript import { linear } from "popmotion" ``` Each function accepts a progress value between `0` and `1`, and returns a new one: ```javascript const progress = 0.5 const easedProgress = easeInOut(progress) ``` - `linear` - `easeIn` - `easeInOut` - `easeOut` - `circIn` - `circInOut` - `circOut` - `backIn` - `backInOut` - `backOut` - `anticipate` - `bounceIn` - `bounceInOut` - `bounceOut` ### Factories #### cubicBezier ```javascript import { cubicBezier } from "popmotion" const easing = cubicBezier(0, .42, 0, 1) ``` New cubic bezier definitions can be created in the [Framer](https://framer.com) animation editor and copy/pasted directly into this function. #### steps `steps` returns an easing function that will convert the animation into a discrete series of steps. ```javascript import { steps } from "popmotion" const easing = steps(5) ``` It optionally accepts a second parameter, either `"start"` or `"end"` (default)that decides whether the steps are aligned with the start or end of the animation. ```javascript steps(5, "start") ``` #### mirrorEasing Mirrors an existing easing function. #### reverseEasing Reverses an existing easing function. For instance, providing it `easeIn` would return an `easeOut`. ```javascript import { reverseEasing, linear } from "popmotion" const reversed = reverseEasing(linear) reversed(1) // 0 reversed(0.5) // 0.5 reversed(0) // 1 ``` #### createExpoIn Creates an easing function based on the exponent of the provided `power`. The higher the `power`, the stronger the easing. ```javascript import { createExpoIn } from "popmotion" const expoIn = createExpoIn(4) ``` The returned easing function is an ease in, which means it starts slow and finished fast. `mirrorEasing` and `reverseEasing` can be used to create ease in out, and ease out variations: ```javascript const expoIn = createExpoIn(4) const expoOut = mirrorEasing(easeIn) const expoInOut = reverseEasing(easeIn) ``` #### createBackIn Creates an easing function with an overshoot. It accepts a `power` value, the higher the `power` the stronger the overshoot. ```javascript import { createBackIn } from "popmotion" const backIn = createBackIn(4) ``` The returned easing function is an ease in, which means the overshoot happens at the start of the animation. `mirrorEasing` and `reverseEasing` can be used to create ease in out, and ease out variations: ```javascript const backIn = createBackIn(4) const backOut = mirrorEasing(easeIn) const backInOut = reverseEasing(easeIn) ``` #### createAnticipate Creates an easing that pulls back a little before animating out with an overshoot. The stronger the `power` the bigger the overshoot. ```javascript import { createAnticipate } from "popmotion" const anticipate = createAnticipate(4) ``` ## Utils #### angle Returns an angle between two points, in degrees. ```javascript import { angle } from "popmotion" angle( { x: 0, y: 0 }, { x: 45, y: 100 } ) ``` #### attract ```javascript import { attract } from "popmotion" attract(5, 10, 12) ``` #### attractExpo ```javascript import { attractExpo } from "popmotion" attractExpo(5, 10, 12) ``` #### clamp Clamp a value to within the given range. ```javascript import { clamp } from "popmotion" const min = 50 const max = 100 clamp(min, max, 150) // 100 ``` #### degreesToRadians Converts degrees to radians. ```javascript import { degreesToRadians } from "popmotion" degreesToRadians(45) // 0.785... ``` #### distance Returns the distance between two numbers, two 2D points, or two 3D points. ```javascript import { distance } from "popmotion" distance(10, 50) distance({ x: 0, y: 0 }, { x: 45, y: 100 }) distance({ x: 0, y: 0, z: 100 }, { x: 45, y: 100, z: 0 }) ``` #### interpolate Creates a function that will interpolate from an linear series of numbers, to a non-linear series of numbers, strings of the same numerical format, colours, or arrays/objects of those. ```javascript import { interpolate } from "popmotion" const mapXToOpacity = interpolate( [-100, 0, 100], [0, 1, 0] ) mapXToOpacity(-50) // 0.5 const mapProgressToValues = interpolate( [0, 1], [ { x: 0, color: "#fff" }, { x: 100, color: "#000" } ] ) mapProgressToValues(0.5) // { x: 50, color: "#888" } const rescale = interpolate( [0, 1], [100, 200], { clamp: false } ) rescale(2) // 300 ``` #### Options `interpolate` accepts an optional third argument, an object of options. - `clamp`: Clamps values to within given range. Defaults to `true`. - `ease`: An `Easing` function, or array of easing functions, to ease the interpolation of each segment. - `mixer`: A function that, when provided a `from` and `to` value, will return a new function that accepts a progress value between `0` and `1` to mix between those two values. For integration with libraries like Flubber. #### isPoint Returns `true` if the provided argument is a 2D point. ```javascript import { isPoint } from "popmotion" isPoint({ x: 0 }) // false isPoint({ x: 0, y: 0 }) // true ``` #### isPoint3D Returns `true` if the provided argument is a 3D point. ```javascript import { isPoint3D } from "popmotion" isPoint3D({ x: 0 }) // false isPoint3D({ x: 0, y: 0 }) // false isPoint3D({ x: 0, y: 0, z: 0 }) // true ``` #### mix Will mix between two values, given `progress` as a third argument. ```javascript import { mix } from "popmotion" mix(0, 100, 0.5) // 50 mix(0, 100, 2) // 200 ``` #### mixColor Returns a function that, when provided a `progress` value, will mix between two colors. Accepts hex, rgba and hsla colors. ```javascript import { mixColor } from "popmotion" mixColor("#000", "#fff")(0.5) // "rgba(125, 125, 125, 1)" ``` #### mixComplex Returns a function that, when provided a `progress` value, will mix between two strings with the same order of numbers and colors. ```javascript import { mixComplex } from "popmotion" mixComplex("100px #fff", "0px #000")(0.5) // "50px rgba(125, 125, 125, 1)" ``` #### pointFromVector Given a point, angle in degrees, and distance, will return a new point. ```javascript import { pointFromVector } from "popmotion" const point = { x: 0, y: 0 } const angle = 45 const distance = 100 pointFromVector(point, angle, distance) ``` #### progress Given a min and a max range, and a value, will return the `progress` of the value within the range as normalised to a `0`-`1` range. ```javascript import { progress } from "popmotion" const min = 100 const max = 200 progress(min, max, 150) // 0.5 ``` #### radiansToDegrees Converts radians to degrees. ```javascript import { radiansToDegrees } from "popmotion" radiansToDegrees(0.785) // 45 ``` #### snap Creates a function that will snap numbers to the nearest in a provided array or to a regular interval. ```javascript import { snap } from "popmotion" // Snap to regular intervals const snapTo = snap(45); snapTo(1); // 0 snapTo(40); // 45 snapTo(50); // 45 snapTo(80); // 90 // Snap to values in an array const snapTo = snap([-100, -50, 100, 200]); snapTo(-200); // -100 snapTo(-76); // -100 snapTo(-74); // -50 ``` #### toDecimal Rounds a number to a specific decimal place. ```javascript import { toDecimal } from "popmotion" toDecimal(3.3333); // 3.33 toDecimal(6.6666, 1); // 6.67 ``` #### velocityPerFrame ```javascript import { velocityPerFrame } from "popmotion" velocityPerFrame(50, 16.7); // 0.835 ``` #### velocityPerSecond ```javascript import { velocityPerSecond } from "popmotion" velocityPerSecond(1, 16.7); // 59.880... ``` #### wrap ```javascript import { wrap } from "popmotion" wrap(0, 1, 0.5); // 0.5 wrap(0, 1, 1.5); // 0.5 ``` ================================================ FILE: lerna.json ================================================ { "lerna": "3.0.6", "version": "independent", "packages": ["packages/*"], "npmClient": "yarn", "useWorkspaces": true } ================================================ FILE: package.json ================================================ { "private": true, "name": "popmotion-packages", "author": "Matt Perry", "homepage": "https://popmotion.io", "scripts": { "bootstrap": "lerna bootstrap", "build": "cd packages/style-value-types && yarn build && cd ../framesync && yarn build && cd ../popmotion", "playground": "start-storybook -s ./storybook-static -p 6006", "build-playground": "build-storybook", "clean": "lerna clean --yes && rm -rf node_modules", "clear": "yarn clean && rm yarn.lock", "test": "lerna run test" }, "devDependencies": { "@rollup/plugin-node-resolve": "^7.0.0", "@rollup/plugin-replace": "^2.3.0", "@storybook/react": "^3.2.13", "babel-cli": "^6.26.0", "babel-core": "^6.26.3", "babel-eslint": "^5.0.4", "babel-loader": "^6.0.1", "babel-plugin-annotate-pure-calls": "^0.2.2", "babel-plugin-root-import": "^5.1.0", "babel-plugin-transform-export-extensions": "^6.4.0", "babel-plugin-transform-react-constant-elements": "^6.9.1", "babel-plugin-transform-react-jsx": "^6.24.1", "babel-plugin-transform-react-remove-prop-types": "^0.2.6", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.6.1", "babel-preset-react": "^6.16.0", "babel-preset-stage-0": "^6.24.1", "babel-preset-stage-2": "^6.17.0", "babel-register": "^6.3.13", "eslint": "^3.19.0", "eslint-plugin-flowtype": "^2.30.0", "eslint-plugin-import": "^2.2.0", "eslint-plugin-jasmine": "2.2.0", "eslint-plugin-jsx-a11y": "^4.0.0", "eslint-plugin-react": "^6.10.0", "estraverse-fb": "^1.3.1", "husky": "^1.1.2", "jest": "^24.0.0", "jest-cli": "^24.0.0", "jest-diff": "^24.0.0", "lerna": "^3.0.6", "lint-staged": "^7.3.0", "prettier": "^2.1.0", "react": "^16.8.6", "react-dom": "^16.8.6", "rollup": "^2.40.0", "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.30.0", "string-replace-loader": "^1.3.0", "styled-components": "^4.1.1", "ts-jest": "^22.4.6", "tslint": "^5.11.0", "tslint-config-prettier": "^1.15.0", "tslint-react": "^4.1.0", "typescript": "^4.7", "webpack": "^3.0.0" }, "workspaces": { "packages": [ "packages/*" ], "nohoist": [ "gatsby" ] }, "resolutions": { "jest": "24.2.0-alpha.0", "babel-jest": "^22.4.3", "tslib": "2.4.0" }, "prettier": { "parser": "typescript", "singleQuote": true }, "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "*.{ts,tsx}": [ "prettier --write", "git add" ] }, "version": "10.1.0" } ================================================ FILE: packages/blend-tweens/blend-tweens.js ================================================ import tween from './tween'; import { linear } from '../inc/easing'; import { getProgressFromValue, getValueFromProgress } from '../inc/calc'; import { flow, clamp, bezier } from '../inc/transformers'; const clampProgress = clamp(0, 1); function calcValueAtTime(from, to, duration, elapsed, ease) { const progressAtTime = ease(clampProgress(getProgressFromValue(0, duration, elapsed))); return getValueFromProgress(from, to, progressAtTime); } export default ({ from, to, onUpdate, accuracy = 60, ...props }) => { // Get tween properties const a = from; const b = to; const aCurrent = a.get(); const aDuration = a.getProp('duration'); const bDuration = b.getProp('duration'); const aEase = a.getProp('ease'); const bEase = b.getProp('ease'); const aFrom = a.getProp('from'); const bFrom = b.getProp('from'); const aTo = a.getProp('to'); const bTo = b.getProp('to'); // Analyse tweens const overlapDuration = Math.min(aDuration - a.getElapsed(), bDuration); const bValueAtTweenOverlapComplete = calcValueAtTime(bFrom, bTo, bDuration, overlapDuration, bEase); const bStartsHigherThanA = (bFrom > aCurrent); const bEndsHigherThanA = (bValueAtTweenOverlapComplete > aTo); // Blend points are defined as [t = time, v = valueAtTime] // P1 // The start of the tween blend const P1 = aCurrent; const blendPoints = [P1]; // Find P2 // The possible intersection point between the two tweens const tweensIntersect = (bStartsHigherThanA !== bEndsHigherThanA); if (tweensIntersect) { let timestep = overlapDuration / accuracy; for (let i = 0; i < accuracy; i++) { const runningTime = i * timestep; const aValueAtTime = calcValueAtTime(aFrom, aTo, aDuration, a.elapsed + runningTime, aEase); const bValueAtTime = calcValueAtTime(bFrom, bTo, bDuration, b.elapsed + runningTime, bEase); const hasIntersected = ( (bStartsHigherThanA && aValueAtTime > bValueAtTime) || (!bStartsHigherThanA && aValueAtTime < bValueAtTime) ); if (hasIntersected) { const P2 = bValueAtTime; blendPoints.push(P2); break; } } } // P3 // The points the two tweens stop overlapping const P3 = bValueAtTweenOverlapComplete; blendPoints.push(P3); // Push the end state of b tween as final control point const P4 = bTo; blendPoints.push(P4); return tween({ ...props, duration: bDuration, ease: linear, onUpdate: flow( bezier(blendPoints), onUpdate ) }); }; ================================================ FILE: packages/blend-tweens/cross-fade.js ================================================ import Action from './'; import tween from './tween'; import { linear } from '../inc/easing'; import { getValueFromProgress } from '../inc/calc'; class CrossFade extends Action { static defaultProps = { ease: linear } onStart() { const { duration, ease, fader } = this.props; this.fader = fader || tween({ to: 1, duration, ease }).start(); } update() { const { from, to } = this.props; const balance = this.fader.get(); const latestFromValue = from.get(); const latestToValue = to.get(); return getValueFromProgress(latestFromValue, latestToValue, balance); } } export default (props) => new CrossFade(props); ================================================ FILE: packages/framesync/CHANGELOG.md ================================================ # Changelog Framesync adheres to [Semantic Versioning](http://semver.org/). ## [6.1.2] 2022-08-15 ### Update - `tslib` and `typescript`. ## [6.1.1] 2022-08-10 ### Update - Adding `types` to `exports` field. ## [6.1.0] 2021-11-24 ### Update - Updating `tslib`. ## [6.0.1] 2021-10-22 ### Fixed - Fixing bug where `flushSync` being called from an existing process would boot keep-alive processes off the thread. ## [6.0.0] 2021-09-23 ### Fixed - Fixing `exports` and `module` in `package.json`. This will break (unsupported) direct file imports. ## [5.3.0] 2021-03-30 ### Added - Adding `flushSync` API for manual flushing of job queues. ## [5.2.3] 2021-03-19 ### Fixed - Fixing `main` entry point. ## [5.2.2] 2021-03-19 ### Fixed - Fixing `main` entry point. ## [5.2.1] 2021-03-19 ### Added - Adding `exports` to `package.json`. ### Updated - `tslib` to latest. ## [5.2.0] 2021-03-01 ### Fixed - Unbundling ES code to facilitate code-splitting in Webpack. ## [5.1.0] 2021-02-22 ### Fixed - Adding polyfill for `performance.now` for better compatibility with Node environments. ## [5.0.0] 2021-01-01 ### Changed - Using `performance.now` to measure polyfilled elapsed time. ## [4.1.0] 2020-8-24 ### Added - `preRender` step. ## [4.0.2] 2019-02-05 ### Fixed - Fixing rescheduling keepAlive method. ## [4.0.1] 2018-09-24 ### Changed - Updated README. ## [4.0.0] 2018-09-24 ### Changed - New API. - `onFrameUpdate(callback, immediate)` -> `sync.update(callback, keepAlive, immediate)` - `cancelOnFrameUpdate(callback)` -> `cancelSync.update(callback)` ### Added - `keepAlive` parameter. ## [3.1.9] 2018-05-16 ### Fixed - Fixing Rollup config. ## [3.1.8] 2018-05-13 ### Added - Rollup support for outputting ES modules and UMD bundles. ## [3.1.7] 2018-01-04 ### Fixed - Fixing illegal invocation errors. ## [3.1.6] 2018-01-04 ### Changed - Using previous frame duration as default duration (for instance between active cycles). ## [3.1.5] 2018-01-04 ### Changed - Cleaning polyfill. ## [3.1.4] 2018-01-04 ### Changed - Max permitted time elapsed is now 40ms to permit 30fps max. - When `startRenderLoop` is fired, and the loop is not active, we set a new `currentTime`. ## [3.1.3] 2017-11-08 ### Fixed - Actually pointing to new declaration file. ## [3.1.2] 2017-11-08 ### Fixed - Pointing to new declaration file. ## [3.1.1] 2017-11-08 ### Fixed - Automatically exporting declaration file. ## [3.1.0] 2017-11-08 ### Added - Added optional `true` flag to `schedule`. This will schedule a job to run at the end of the current frame step. ## [3.0.0] 2017-08-28 - `currentFrameTimestamp` becomes `currentFrameTime` for symmetry with `currentTime`. ## [2.0.1] 2017-08-26 ### Added - Changelog and Readme. ## [2.0.0] 2017-08-26 ### Added - First publish. ================================================ FILE: packages/framesync/LICENSE.md ================================================ MIT License Copyright © 2019 Framer BV Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/framesync/README.md ================================================ # Framesync A tiny frame scheduler for performantly batching reads, updates and renders. Segregating actions that read and write to the DOM will avoid [layout thrashing](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing). It's also a way of ensuring order of execution across a frame. For instance, [Framer Motion](https://github.com/framer/motion) batches animations on the `update` step, and renders on the `render` step, allowing independent animation of `transform` properties. ## Install ```bash npm install framesync ``` ## Usage Framesync splits a frame into discrete `read`, `update`, `preRender`, `render` and `postRender` steps. ### Scheduling functions Functions can be scheduled to different parts of the render loop with `sync`. ```javascript import sync from 'framesync'; ``` It provides four functions, one for scheduling a function to run on each part of the frame: ```javascript sync.update(() => {}); ``` ### Frame data Each function is provided data about the current frame: ```javascript sync.update(({ delta, timestamp }) => {}); ``` - `delta`: Time since last frame (in milliseconds) - `timestamp`: Timestamp of the current frame. This object is recycled across frames, so values should be destructured if intended to be used asynchronously. ### Keep alive We can run a function as an ongoing process by passing `true` as the second parameter: ```javascript let count = 0; sync.update(() => count++, true); ``` This will keep the process running until it's actively cancelled. ### Run immediately The third parameter, `immediate`, can be used to sync a function on the **current frame step**. By default, Framesync will schedule functions to run the next time that frame step is fired: ```javascript sync.update(({ timestamp }) => { // The following function will run on the subsequent frame: sync.update((frame) => frame.timestamp !== timestamp); }); ``` By setting `immediate` to `true`, we can add this at the end of the current step: ```javascript sync.update(({ timestamp }) => { // The following function will run on the **current** frame: sync.update( (frame) => frame.timestamp === timestamp, false, true ); }); ``` ### Cancelling Synced processes can be cancelled with the `cancelSync` function: ```javascript import sync, { cancelSync } from 'framesync'; let count = 0; const process = sync.render(() => { count++; if (count >= 10) cancelSync.render(process); }, true); ``` ================================================ FILE: packages/framesync/package.json ================================================ { "name": "framesync", "license": "MIT", "version": "6.1.2", "description": "A frame-synced render loop for JavaScript", "author": "Matt Perry", "homepage": "https://popmotion.io", "main": "dist/framesync.cjs.js", "types": "lib/index.d.ts", "module": "dist/es/index.mjs", "jsnext:main": "dist/es/index.mjs", "unpkg": "./dist/framesync.min.js", "exports": { ".": { "types": "./lib/index.d.ts", "import": "./dist/es/index.mjs", "require": "./dist/framesync.cjs.js", "default": "./dist/framesync.cjs.js" }, "./package.json": "./package.json" }, "scripts": { "build": "tsc -p . && rollup -c && yarn measure", "watch": "rollup -c -w", "lint": "tslint -c tslint.json 'src/**/*.{ts}'", "test": "jest", "measure": "gzip -c dist/framesync.min.js | wc -c", "prepublishOnly": "npm run test && npm run build" }, "files": [ "lib", "dist" ], "repository": { "type": "git", "url": "git+https://github.com/Popmotion/popmotion.git" }, "keywords": [ "animation", "raf" ], "bugs": { "url": "https://github.com/Popmotion/popmotion/issues" }, "jest": { "moduleFileExtensions": [ "ts", "js" ], "transform": { "\\.(ts)$": "../../../node_modules/ts-jest/preprocessor.js" }, "testRegex": "/_tests/.*\\.(ts|js)$", "rootDir": "src" }, "prettier": { "printWidth": 80, "tabWidth": 4, "semi": false, "trailingComma": "es5" }, "dependencies": { "tslib": "2.4.0" } } ================================================ FILE: packages/framesync/rollup.config.js ================================================ import generateConfig from '../../rollup-generate-config'; import pkg from './package.json'; export default generateConfig(pkg); ================================================ FILE: packages/framesync/src/_tests/test.ts ================================================ import sync, { cancelSync, flushSync } from "../" import { onNextFrame } from "../on-next-frame" describe("onNextFrame", () => { it("fires callback on following frame", () => { return new Promise((resolve) => onNextFrame(resolve)) }) }) describe("sync", () => { it("fires callbacks in the correct order", () => { return new Promise((resolve, reject) => { const order: number[] = [] sync.read(() => order.push(0)) sync.update(() => order.push(1)) sync.preRender(() => order.push(2)) sync.render(() => order.push(3)) sync.postRender(() => { order.push(4) if ( order[0] === 0 && order[1] === 1 && order[2] === 2 && order[3] === 3 && order[4] === 4 ) { resolve() } else { reject(order) } }) }) }) it("cancels callbacks", () => { return new Promise((resolve, reject) => { let hasFired = false const process = sync.render(() => (hasFired = true)) sync.update(() => cancelSync.render(process)) sync.postRender(() => (hasFired ? reject(hasFired) : resolve())) }) }) it("fires callback on current frame if scheduled with `true` within the same step", () => { return new Promise((resolve, reject) => { let v = 0 sync.update(({ timestamp: prevTimestamp }) => { v++ sync.update( ({ timestamp }) => { v++ if (timestamp !== prevTimestamp) { reject(`${timestamp} ${prevTimestamp}`) } }, false, true ) }) sync.render(() => (v === 2 ? resolve() : reject(v))) }) }) it("fires callback on next frame if scheduled with `true` outside the same step", () => { return new Promise((resolve: Function, reject: Function) => { let v = 0 sync.update(() => v++) sync.update(() => v++, false, true) sync.render(() => (v === 2 ? resolve() : reject())) }) }) it("uses default elapsed time if first fire", () => { return new Promise((resolve: Function, reject: Function) => { sync.update(({ delta: defaultElapsed }) => { setTimeout( () => sync.update(({ delta }) => delta === defaultElapsed ? resolve() : reject(defaultElapsed, delta) ), 50 ) }) }) }) it("correctly cancels", () => { return new Promise((resolve, reject) => { const callback = () => reject() sync.read(() => cancelSync.update(callback)) sync.update(callback) sync.render(() => resolve()) }) }) it("correctly keeps alive", () => { return new Promise((resolve) => { let v = 0 sync.update(() => v++, true) sync.render(() => v === 2 && resolve(), true) }) }) it("correctly cancels a keepAlive process", () => { return new Promise((resolve, reject) => { let updateCount = 0 let renderCount = 0 const update = sync.update(() => { updateCount++ if (updateCount === 4) cancelSync.update(update) }, true) sync.render(() => { renderCount++ if (renderCount === 6) { if (renderCount !== updateCount) { resolve() } else { reject([renderCount, updateCount]) } } }, true) }) }) it("correctly keeps alive after a flush", async () => { const promise = new Promise((resolve) => { let v = 0 sync.update(() => { if (v === 2) flushSync.update() }, true) sync.update(() => { v++ if (v > 6) resolve(true) }, true) }) flushSync.update() return expect(promise).resolves.toBe(true) }) }) ================================================ FILE: packages/framesync/src/create-render-step.ts ================================================ import { Step, Process } from "./types" export function createRenderStep(runNextFrame: () => void): Step { /** * We create and reuse two arrays, one to queue jobs for the current frame * and one for the next. We reuse to avoid triggering GC after x frames. */ let toRun: Process[] = [] let toRunNextFrame: Process[] = [] /** * */ let numToRun = 0 /** * Track whether we're currently processing jobs in this step. This way * we can decide whether to schedule new jobs for this frame or next. */ let isProcessing = false let flushNextFrame = false /** * A set of processes which were marked keepAlive when scheduled. */ const toKeepAlive = new WeakSet() const step: Step = { /** * Schedule a process to run on the next frame. */ schedule: (callback, keepAlive = false, immediate = false) => { const addToCurrentFrame = immediate && isProcessing const buffer = addToCurrentFrame ? toRun : toRunNextFrame if (keepAlive) toKeepAlive.add(callback) // If the buffer doesn't already contain this callback, add it if (buffer.indexOf(callback) === -1) { buffer.push(callback) // If we're adding it to the currently running buffer, update its measured size if (addToCurrentFrame && isProcessing) numToRun = toRun.length } return callback }, /** * Cancel the provided callback from running on the next frame. */ cancel: (callback) => { const index = toRunNextFrame.indexOf(callback) if (index !== -1) toRunNextFrame.splice(index, 1) toKeepAlive.delete(callback) }, /** * Execute all schedule callbacks. */ process: (frameData) => { /** * If we're already processing we've probably been triggered by a flushSync * inside an existing process. Instead of executing, mark flushNextFrame * as true and ensure we flush the following frame at the end of this one. */ if (isProcessing) { flushNextFrame = true return } isProcessing = true // Swap this frame and the next to avoid GC ;[toRun, toRunNextFrame] = [toRunNextFrame, toRun] // Clear the next frame list toRunNextFrame.length = 0 // Execute this frame numToRun = toRun.length if (numToRun) { for (let i = 0; i < numToRun; i++) { const callback = toRun[i] callback(frameData) if (toKeepAlive.has(callback)) { step.schedule(callback) runNextFrame() } } } isProcessing = false if (flushNextFrame) { flushNextFrame = false step.process(frameData) } }, } return step } ================================================ FILE: packages/framesync/src/index.ts ================================================ import { onNextFrame, defaultTimestep } from "./on-next-frame" import { createRenderStep } from "./create-render-step" import { Process, StepId, FrameData, CancelSync, FlushSync, Sync, Steps, } from "./types" const maxElapsed = 40 let useDefaultElapsed = true let runNextFrame = false let isProcessing = false const frame = { delta: 0, timestamp: 0, } const stepsOrder: StepId[] = [ "read", "update", "preRender", "render", "postRender", ] const steps = stepsOrder.reduce((acc, key) => { acc[key] = createRenderStep(() => (runNextFrame = true)) return acc }, {} as Steps) const sync = stepsOrder.reduce((acc, key) => { const step = steps[key] acc[key] = (process: Process, keepAlive = false, immediate = false) => { if (!runNextFrame) startLoop() return step.schedule(process, keepAlive, immediate) } return acc }, {} as Sync) const cancelSync = stepsOrder.reduce((acc, key) => { acc[key] = steps[key].cancel return acc }, {} as CancelSync) const flushSync = stepsOrder.reduce((acc, key) => { acc[key] = () => steps[key].process(frame) return acc }, {} as FlushSync) const processStep = (stepId: StepId) => steps[stepId].process(frame) const processFrame = (timestamp: number) => { runNextFrame = false frame.delta = useDefaultElapsed ? defaultTimestep : Math.max(Math.min(timestamp - frame.timestamp, maxElapsed), 1) frame.timestamp = timestamp isProcessing = true stepsOrder.forEach(processStep) isProcessing = false if (runNextFrame) { useDefaultElapsed = false onNextFrame(processFrame) } } const startLoop = () => { runNextFrame = true useDefaultElapsed = true if (!isProcessing) onNextFrame(processFrame) } const getFrameData = () => frame export default sync export { cancelSync, flushSync, getFrameData, FrameData, Process } ================================================ FILE: packages/framesync/src/on-next-frame.ts ================================================ /* Detect and load appropriate clock setting for the execution environment */ export const defaultTimestep = (1 / 60) * 1000 const getCurrentTime = typeof performance !== "undefined" ? () => performance.now() : () => Date.now() export const onNextFrame = typeof window !== "undefined" ? (callback: FrameRequestCallback) => window.requestAnimationFrame(callback) : (callback: FrameRequestCallback) => setTimeout(() => callback(getCurrentTime()), defaultTimestep) ================================================ FILE: packages/framesync/src/types.ts ================================================ export type FrameData = { delta: number timestamp: number } export type Process = (data: FrameData) => void export type Schedule = ( process: Process, keepAlive?: boolean, immediate?: boolean ) => Process export interface Step { schedule: Schedule cancel: (process: Process) => void process: (frame: FrameData) => void } export type StepId = | "read" | "update" | "postUpdate" | "preRender" | "render" | "postRender" export type Sync = { [key in StepId]: Schedule } export type Steps = { [key in StepId]: Step } export type CancelSync = { [key in StepId]: (process: Process) => void } export type FlushSync = { [key in StepId]: () => void } ================================================ FILE: packages/framesync/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "declarationDir": "lib", "rootDir": "src", "outDir": "lib" }, "include": ["src/**/*"] } ================================================ FILE: packages/popcorn/docs/api/utilities/angle.md ================================================ --- title: angle description: Returns the angle between two 2D points. category: functions --- # `angle` `angle` returns the angle between two 2D points, in degrees. ## Import ```javascript import { angle } from '@popmotion/popcorn'; ``` ## Usage ```javascript angle( { x: 0, y: 0 }, { x: 0, y: 20 } ) // 90 ``` ## Types ```typescript type Point2D = { x: number; y: number; } angle(a: Point2D, b: Point2D): number ``` ================================================ FILE: packages/popcorn/docs/api/utilities/apply-offset.md ================================================ --- title: applyOffset description: Creates a function that measures the offset of incoming values and applies it to an initial value. category: functions --- # `applyOffset` `applyOffset` creates a function that measures the offset of any given value from the initial `from`, and applies it to `to`. ## Import ```javascript import { applyOffset } from '@popmotion/popcorn'; ``` ## Usage `applyOffset` can be used both when you know the initial value you want to measure the offset from, and when you don't. ### With an initial value ```javascript applyOffset(100, 0)(101) // 1 ``` ### Without an initial value If we don't provide an initial `from` value, the first value we call the returned function will be considered `from` and apply `to` with a delta of `0`. ```javascript const applyPointerMovementToX = applyOffset(x); applyPointerMovement(100); // x applyPointerMovement(120); // x + 20 ``` ## Types ```typescript applyOffset(from: number, to: number): (v: number) => number; applyOffset(to: number): (v: number) => number; ``` ================================================ FILE: packages/popcorn/docs/api/utilities/clamp.md ================================================ --- title: clamp description: Restricts a number to within the defined range. category: functions --- # `clamp` `clamp` restricts a number to within the given range. ## Import ```javascript import { clamp } from '@popmotion/popcorn'; ``` ## Usage Clamp accepts a `min` and `max` value. If a third value is provided, it returns a number that is clamped within that given range: ```javascript clamp(100, 200, 99); // 100 clamp(100, 200, 201); // 200 ``` If only a range is provided, it returns a function. When that function is provided a number, it returns a new number that is clamped within the defined range: ```javascript const restrictOpacity = clamp(0, 1); restrictOpacity(-1); // 0 ``` ## Types ```typescript clamp(min: number, max: number, v: number) => number; clamp(min: number, max: number) => (v: number) => number; ``` ================================================ FILE: packages/popcorn/docs/api/utilities/conditional.md ================================================ --- title: conditional description: Creates a function that conditionally transforms provided values. category: functions --- # `conditional` `conditional` is used to create a function that will conditionally transform incoming values. ## Import ```javascript import { conditional } from '@popmotion/popcorn'; ``` ## Usage `conditional` is provided a `check` and `transform` function, and returns a new function. This function, when provided a value, will provide it to `check`. If `check` returns `true`, it will return the value as transformed by `transform`. If `false`, it will return the value. ```javascript const conditionallyDouble = conditional( v => v > 5, v => v * 2 ); conditionallyDouble(4); // 4 conditionallyDouble(6); // 12 ``` ## Types ```typescript type Check = (v: any) => boolean; type Apply = (v: any) => any; conditional(check: Check, transform: Apply) => (v: any) => any; ``` ================================================ FILE: packages/popcorn/docs/api/utilities/degreesToRadians.md ================================================ --- title: degreesToRadians description: Converts degrees to radians. category: functions --- # `degreesToRadians` `degreesToRadians` converts degrees to radians. ## Import ```javascript import { degreesToRadians } from '@popmotion/popcorn'; ``` ## Usage ```javascript degreesToRadians(45) // 0.785... ``` ## Types ```typescript degreesToRadians(degrees: number): number; ``` ================================================ FILE: packages/popcorn/docs/api/utilities/distance.md ================================================ --- title: distance description: Calculate the distance between two n-dimensional points. category: functions --- # `distance` `distance` calculates the distance between two n-dimensional points. ## Import ```javascript import { distance } from '@popmotion/popcorn'; ``` ## Usage ### 1D points Provide two numbers to measure the distance between them. ```javascript distance(-100, 100); // 200 ``` ### 2D and 3D points Provide two `Point` values, with `x`, `y` and optionally `z` numbers, to measure the distance between them. ```javascript distance( { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 10 } ); // 10 ``` ## Types ```typescript type Point = { x: number; y: number; z?: number; } distance(a: number | Point, b: number | Point): number ``` ================================================ FILE: packages/popcorn/docs/api/utilities/interpolate.md ================================================ --- title: interpolate description: Map from a series of numbers to a range of numbers, colors or strings. category: functions --- # `interpolate` Using an `input` range of sequential numbers, and an `output` range of numbers or colors, `interpolate` will create a function that maps a number from the `input` range to the `output`. ## Import ```javascript import { interpolate } from '@popmotion/popcorn'; ``` ## Usage `interpolate` can map from an `input` array of sequential numbers like this: ```javascript [0, 1, 2] ``` To an array of **either** numbers, colors, or strings containing one or more numbers or colors. The two arrays must be the **same length**. ### Numbers ```javascript const mapper = interpolate( [-100, -50, 50, 100], [0, 1, 1, 0] ); mapper(-200); // 0 mapper(-75); // 0.5 mapper(50); // 1 ``` ### Colors `interpolate` can map to hex, RGB(A) or HSL(A) colors. **Note:** Hex numbers will be output as RGBA. ```javascript const mapper = interpolate( [0, 50, 100], ['#fff', '#f00', '#000'] ); mapper(25); // rgba(255, 128, 128, 1) mapper(50); // rgba(255, 0, 0, 1) mapper(75); // rgba(128, 0, 0, 1) ``` ### Strings `interpolate` can maps to complex value types that include multiple numbers or colors. Every string in the `output` range must be of the exact same format. ```javascript const mapper = interpolate( [0, 1, 2], [ '20px, rgba(0, 0, 0, 0)', '10px, rgba(255, 255, 255, 1)', '40px, rgba(100, 100, 100, 0.5)' ] ); mapper(0.5); // '15px, rgba(128, 128, 128, 0.5)' ``` ## Options `interpolate` takes an optional third argument, options. This is an object that may contain the following configurations: ### Clamp Clamp values to within the provided ranges. Default: `true`. ```javascript interpolate([0, 1], [0, 100])(2) // 100 interpolate([0, 1], [0, 100], { clamp: false })(2) // 200 ``` ### Ease Default: `linear` An easing function, or list of easing functions, to apply to every transition. If defined as an array, it must be the `input.length - 1`, as it applies to the transitions **between** the defined values. ```javascript interpolate( [0, 100, 200], ['#fff', '#500', '#000'], { ease: [linear, easeOut] } ) ``` ## Types ```typescript interpolate( input: number[], output: string[] | number[], options: InterpolateOptions ): string | number; type InterpolateOptions = { clamp?: boolean = true, ease?: Easing | Easing[] }; type Easing = v => v; ``` ================================================ FILE: packages/popcorn/docs/api/utilities/is-point-3d.md ================================================ --- title: isPoint3D description: Test a value for x, y and z properties. category: functions --- # `isPoint3D` Returns `true` if the provided value has an `x`, `y` and `z` properties. ## Import ```javascript import { isPoint3D } from '@popmotion/popcorn'; ``` ## Usage ```javascript isPoint3D(0); // false isPoint3D({ x: 0, y: 0 }); // false isPoint3D({ x: 0, y: 0, z: 0 }); // true ``` ## Types ```typescript isPoint3D(v: any): boolean; ``` ================================================ FILE: packages/popcorn/docs/api/utilities/is-point.md ================================================ --- title: isPoint description: Test a value for an `x` and `y` property. category: functions --- # `isPoint` Returns `true` if the provided value has an `x` and `y` property. ## Import ```javascript import { isPoint } from '@popmotion/popcorn'; ``` ## Usage ```javascript isPoint(0); // false isPoint({ x: 0, y: 0 }); // true ``` ## Types ```typescript isPoint(v: any): boolean; ``` ================================================ FILE: packages/popcorn/docs/api/utilities/mix-array.md ================================================ --- title: mixArray description: Creates a function that can interpolate between two arrays of matching value types. category: functions --- # `mixArray` `mixArray` creates a function that can interpolate between two arrays of matching value types. Values can be any number, color, or a string containing multiple numbers or colors. ## Import ```javascript import { mixArray } from '@popmotion/popcorn'; ``` ## Usage `mixArray` accepts two arrays, a `from` and `to` array. The values in each can be: - Number - Hex color - RGB(A) color - HSL(A) color - String containing one or more of the above types It returns a function that can be used to interpolate between the two arrays using a value between `0-1`. ```javascript const mixer = mixArray( [0, '60px', '#fff'], [100, '-60px', '#000'] ) mixer(0.5) // [50, '0px', 'rgba(128, 128, 128, 1)'] ``` **Note:** Colors, if defined as hex values, will be returned as rgba. ## Types ```typescript type Mixable = Array; mixArray(from: Mixable, to: Mixable): Mixable ``` ================================================ FILE: packages/popcorn/docs/api/utilities/mix-color.md ================================================ --- title: mixColor description: Creates a function that can interpolate between two hex, rgba or hsla colors. category: functions --- # `mixColor` `mixColor` creates a function that can interpolate between two hex, rgba or hsla colors. Hex and RGBA colors are mixed using linear color space blending which avoids brightness dips. [Learn more](https://www.youtube.com/watch?v=LKnqECcg6Gw). ## Import ```javascript import { mixColor } from '@popmotion/popcorn'; ``` ## Usage `mixColor` accepts two colors, either hex/RGB(A), or HSL(A). It returns a function that can be used to interpolate between the two colors using a value between `0-1`. ```javascript const mixer = mixColor('#f00', '#0f0'); mixer(0.5) // 'rgba(128, 128, 0, 1)' ``` **Note:** Colors, if defined as hex values, will be returned as rgba. ## Types ```typescript mixColor(from: string, to: string) => (v: number) => string ``` ================================================ FILE: packages/popcorn/docs/api/utilities/mix-complex.md ================================================ --- title: mixComplex description: Creates a function that can mix between two strings that contain one or more numbers and/or colors. category: functions --- # `mixComplex` Creates a function that can mix between two strings that contain one or more numbers and/or colors. Hex and RGBA colors are mixed using linear color space blending which avoids brightness dips. [Learn more](https://www.youtube.com/watch?v=LKnqECcg6Gw). ## Import ```javascript import { mixComplex } from '@popmotion/popcorn'; ``` ## Usage `mixComplex` can be used to animate any string in which numbers or colors are detected. As simple as single unit types: ```javascript const mixer = mixComplex('10px', '20px'); mixer(0.5); // '15px' ``` Or strings containing multiple numbers and colors: ```javascript const mixer = mixComplex( '0px 0px auto rgba(0, 0, 0, 0)', '5px 5px auto rgba(0, 0, 0, 0.5)' ); mixer(0.5); // '2.5px 2.5px auto rgba(0, 0, 0, 0.25)' ``` **Note:** The non-numerical portions of the string must be the same in both `from` and `to` values. ## Types ```typescript mixColor(from: string, to: string) => (v: number) => string ``` ================================================ FILE: packages/popcorn/docs/api/utilities/mix.md ================================================ --- title: mix description: Mixes between two numbers category: functions --- # `mix` `mix` can interpolate between two numbers. ## Import ```javascript import { mix } from '@popmotion/popcorn'; ``` ## Usage ```javascript mix(0, 100, 0.5); // 50 mix(0, 100, 1); // 100 mix(0, 100, 2); // 200 ``` ## Types ```typescript mix(from: number, to: number, progress: number): number ``` ================================================ FILE: packages/popcorn/docs/api/utilities/pipe.md ================================================ --- title: pipe description: Left-to-right function composition. category: functions --- # `pipe` `pipe` composes a new function out of a series of provided functions. When this new function is provided a value, it will run this value through the provided functions in left-to-right order. This provides a way of composing logic from simpler, reusable functions. ## Import ```javascript import { pipe } from '@popmotion/popcorn'; ``` ## Usage ```javascript const double = v => v * 2; const add = toAdd => v => v + toAdd; const negative = v => -v; const func = pipe(double, add(1), negative); func(2); // -5 ``` ## Types ```typescript pipe(...funcs: Function): Function ``` ================================================ FILE: packages/popcorn/docs/api/utilities/point-from-vector.md ================================================ --- title: pointFromVector description: Calculates a point, given a point and vector (angle and distance) category: functions --- # `pointFromVector` `pointFromVector` takes a point, an angle (in degrees), and a distance, and returns a new point. ## Import ```javascript import { pointFromVector } from '@popmotion/popcorn'; ``` ## Usage ```javascript pointFromVector({ x: 0, y: 0 }, 45, 100); // { x: 70.710..., y: 70.710... } ``` ## Types ```typescript type Point = { x :number; y: number; }; pointFromVector(origin: Point, angle: number, distance: number): Point ``` ================================================ FILE: packages/popcorn/docs/api/utilities/progress.md ================================================ --- title: progress description: Calculates the progress of a value between in a given range. category: functions --- # `progress` `progress` returns the progress of a value in a given range. Progress is represented as a number between `0` and `1`, but is unclamped. ## Import ```javascript import { progress } from '@popmotion/popcorn'; ``` ## Usage ```javascript progress(100, -100, 50); // 0.25 progress(100, -100, -300); // 2 ``` ## Types ```typescript progress(from: number, to: number, value: number): number ``` ================================================ FILE: packages/popcorn/docs/api/utilities/radians-to-degrees.md ================================================ --- title: radiansToDegrees description: Converts radians to degrees. category: functions --- # `radiansToDegrees` `radiansToDegrees` converts radian units to degrees. ## Import ```javascript import { radiansToDegrees } from '@popmotion/popcorn'; ``` ## Usage ```javascript radiansToDegrees(0.7); // 40.107... ``` ## Types ```typescript radiansToDegrees(radians: number): number ``` ================================================ FILE: packages/popcorn/docs/api/utilities/smooth-frame.md ================================================ --- title: smoothFrame description: Smooth the motion from one value to a new value over a single frame. category: functions --- # `smoothFrame` `smoothFrame` can be used to smooth motion across a single frame, independent of framerate. This can be used, for example, to implement input smoothing. **Note:** To smooth motion using a [Framesync](/api/framesync)-based process or library like [Popmotion Pure](/pure), the [smooth](/popcorn/api/smooth) utility offers a simpler API. ## Import ```javascript import { smoothFrame } from '@popmotion/popcorn'; ``` ## Usage `smoothFrame` accepts `prevValue`, `nextValue`, `frameDuration` and `strength` arguments. It will return a value that smooths the difference between `prevValue` and `nextValue` according to the `frameDuration` and `strength` properties. ```javascript smoothFrame(0, 100, 16.7, 50); // 33.4 ``` Higher `strength` values impose stronger smoothing, with values the same as or less than the `frameDuration` imposing no smoothing. ## Types ```typescript smoothFrame( prev: number, next: number, frameDuration: number, strength: number ): number ``` ================================================ FILE: packages/popcorn/docs/api/utilities/smooth.md ================================================ --- title: smooth description: Creates a function that will smooth values across frames when used with Framesync or Popmotion Pure. category: functions --- # `smooth` `smooth` creates a function that can smooth a value across frames when paired with a [Framesync](/api/framesync) process or [Popmotion Pure](/pure) animation. ## Import ```javascript import { smooth } from '@popmotion/popcorn'; ``` ## Usage ### With Framesync ```javascript import { smooth } from '@popmotion/popcorn'; import sync from 'framesync'; let counter = 0; const smoother = smooth(100); sync.update(() => { counter = counter + 10; counter = smoother(counter); }, true); ``` ### With Popmotion Pure As Popmotion Pure runs on Framesync, we can use it to smooth animations. For instance, here's an example of input smoothing: ```javascript import { smooth } from '@popmotion/popcorn'; import { pointer } from 'popmotion'; pointer({ x: 100 }) .pipe( ({ x }) => x, smooth(50) ) .start(v => console.log(v)) ``` ## Types ```typescript smoothFrame(strength: number) => (v: number) => number ``` ================================================ FILE: packages/popcorn/docs/api/utilities/snap.md ================================================ --- title: snap description: Creates a function that will snap provided values to regular intervals, or to numbers provided as an array. category: functions --- # `snap` `snap` creates a function that will snap numbers to the nearest in a provided array or to a regular interval. ## Import ```javascript import { snap } from '@popmotion/popcorn'; ``` ## Usage ### Snap to regular interval ```javascript const snapTo = snap(45); snapTo(1); // 0 snapTo(40); // 45 snapTo(50); // 45 snapTo(80); // 90 ``` ### Snap to values in an array ```javascript const snapTo = snap([-100, -50, 100, 200]); snapTo(-200); // -100 snapTo(-76); // -100 snapTo(-74); // -50 ``` ## Types ```typescript snap(interval: number) => (v: number) => number; snap(intervals: number[]) => (v: number) => number; ``` ================================================ FILE: packages/popcorn/docs/api/utilities/to-decimal.md ================================================ --- title: toDecimal description: Rounds a number to a specified decimal place. category: functions --- # `toDecimal` `toDecimal` will round a number to the specified decimal place. ## Import ```javascript import { toDecimal } from '@popmotion/popcorn'; ``` ## Usage ```javascript toDecimal(3.3333); // 3.33 toDecimal(6.6666, 1); // 6.67 ``` ## Types ```typescript toDecimal(value: number, precision: number = 2): number ``` ================================================ FILE: packages/popcorn/docs/api/utilities/velocity-per-frame.md ================================================ --- title: velocityPerFrame description: Converts velocity per second into velocity per frame. category: functions --- # `velocityPerFrame` `velocityPerFrame` converts velocity per second into velocity per frame. It is framerate-independent. ## Import ```javascript import { velocityPerFrame } from '@popmotion/popcorn'; ``` ## Usage If a number is changing at a velocity of `50` per second, we can find its velocity per frame by passing `50` along with the duration of the previous frame (in milliseconds): ```javascript velocityPerFrame(50, 16.7); // 0.835 ``` ## Types ```typescript snap(velocityPerSecond: number, frameDuration: number): number; ``` ================================================ FILE: packages/popcorn/docs/api/utilities/velocity-per-second.md ================================================ --- title: velocityPerSecond description: Converts velocity per frame into velocity per second. category: functions --- # `velocityPerSecond` `velocityPerSecond` converts velocity per frame into velocity per second. It is framerate-independent. ## Import ```javascript import { velocityPerSecond } from '@popmotion/popcorn'; ``` ## Usage If a number changed, for instanced, by an increase of `1` in the previous frame, we can calculate its velocity per second by passing `velocityPerSecond` `1` along with the duration of the previous frame (in milliseconds): ```javascript velocityPerSecond(1, 16.7); // 59.880... ``` ## Types ```typescript velocityPerSecond(velocityPerFrame: number, frameDuration: number): number; ``` ================================================ FILE: packages/popcorn/docs/api/utilities/wrap.md ================================================ --- title: wrap description: Wraps a number when it breaches the defined range. category: functions --- # `wrap` Wraps a number when it breaches the defined range. ## Import ```javascript import { wrap } from '@popmotion/popcorn'; ``` ## Usage `wrap` accepts a range, defined as a `min` and `max`. When a third number is provided: - If it lies within the range, it is returned. - If it lies outside the range, it is wrapped back around: ```javascript wrap(0, 1, 0.5); // 0.5 wrap(0, 1, 1.5); // 0.5 ``` ## Types ```typescript wrap(min: number, max: number, v: number): number; wrap(min: number, max: number) => (v: number) => number; ``` ================================================ FILE: packages/popmotion/.gitignore ================================================ /*.js /*.js.map *.d.ts /coverage ================================================ FILE: packages/popmotion/.npmignore ================================================ *.md *.map docs webpack.config.js tmp coverage src ================================================ FILE: packages/popmotion/CHANGELOG.md ================================================ # Changelog Popmotion adheres to [Semantic Versioning](http://semver.org/). ## [11.0.5] 2022-08-15 ### Update - `tslib` and `typescript`. ## [11.0.4] 2022-08-10 ### Update - Adding `types` to `exports` field. ## [11.0.3] 2021-12-03 ### Update - Dynamic `restDelta` for smoother springs at low values. ## [11.0.2] 2021-12-02 ### Update - Locking dependencies. ## [11.0.1] 2021-12-02 ### Update - Locking dependencies. ## [11.0.0] 2021-11-03 ### Changed - `mixColor` always outputs RGB, even when mixing HSLA. ### Added - Animate HSLA with linear color mix. - Animate between HSLA, RGBA and Hex with `mixColor`. ## [10.0.2] 2021-10-22 ### Fixed - Updating `framesync`. ## [10.0.0] 2021-09-23 ### Fixed - Fixing `exports` and `module` in `package.json`. This will break (unsupported) direct file imports. ### [9.4.1] 2021-09-21 ### Changed - Change `mixComplex` "too complex" error to warning, and allowing instant interpolation when `p > 0`. ### [9.4.0] 2021-06-18 ### Added - `onStop` to `inertia`. ### [9.3.6] 2021-05-11 ### Fix - Correctly end spring animation if provided `bounce` without `duration`. ### [9.3.5] 2021-03-30 ### Update - Updating `framesync`. ### [9.3.4] 2021-03-19 ### Fixed - Updating `style-value-types`. ### [9.3.3] 2021-03-19 ### Fixed - Fixing `main` entry point. ### [9.3.2] 2021-03-19 ### Added - Adding `exports` to `package.json`. ### Updated - `tslib` to latest. ### [9.3.1] 2021-03-02 ### Fixed - Scheduling of first animation frame was previously set to `immediate`, leading to double animations for animations stopped/started within the same frame. ### [9.3.0] 2021-03-01 ### Fixed - Unbundling ES code to facilitate code-splitting in Webpack. ## [9.2.1] 2020-02-22 ### Update - Upgrading to `style-value-types@4.0.3`. ## [9.2.0] 2020-02-22 ### Update - Upgrading to `framesync@5.1.0` for better Node compatibility. ## [9.1.0] 2020-01-08 ### Update - Support for hex alpha in `mixColor`. ## [9.0.2] 2020-12-18 ### Update - Dependency updates. ## [9.0.1] 2020-12-01 ### Fixed - If `offsets` is a different length to `values`, rather than throw an error we now just generate default offsets. ## [9.0.0] 2020-10-24 - Full API revamp, see documentation. ## [8.7.5] 2020-09-03 ### Fixed - Refreshing published bundle. ## [8.7.2] 2020-04-28 ### Fixed - Decay and inertia correctly start motion at from prop when using modifyTarget - Decay and inertia no longer round target calculation - Inertia calls modifyTarget even with zero-velocity ### Changed - Inertia implementation refinements ## [8.7.1] 2019-11-14 ### Upgrade - Upgraded to Typescript 3.7. ## [8.7.0] 2019-06-25 ### Added - `repeatDelay` prop to `tween`. ## [8.6.10] 2019-05-09 ### Fixed - Fixing undefined action creator. (https://github.com/Popmotion/popmotion/issues/794)[#794] ## [8.6.9] 2019-05-01 ### Update - Dependencies. ## [8.6.8] 2019-04-01 ### Update - `@popmotion/popcorn@0.3.6` ## [8.6.7] 2019-04-01 ### Fixed - Making `inertia.complete` call conditional on there not being a subsequent animation. (https://github.com/Popmotion/popmotion/pull/763)[#763] ## [8.6.6] 2019-04-01 ### Update - `@popmotion/popcorn@0.3.5` ## [8.6.5] 2019-04-01 ### Update - Changing the behaviour of `inertia` to always spring back if the initial value is out of bounds. On multi-axis `inertia` animations it used to be possible that both values are out of bounds and only one was travelling towards its bound - this led to a situation where one animation used a spring and the other used decay. ## [8.6.4] 2019-03-12 ### Upgrade - Upgrading `style-value-types@3.1.0`. ## [8.6.3] 2019-02-21 ### Fixed - Correctly integrating remainder of `elapsed` in yoyo tweens to ensure multiple concurrent tweens stay in sync. ## [8.6.2] 2019-01-28 ### Updated - Tightened type definitions for TransitionMapFactory [#714](https://github.com/Popmotion/popmotion/pull/714) ## [8.6.1] 2019-01-23 ### Added - Adding `bounceStiffness`, `bounceDamping`, `restDelta`, `timeConstant` and `power` options to `inertia`. ## [8.6.0] 2019-01-16 ### Added - `inertia` animation. ## [8.5.5] 2018-12-28 ### Update - `stylefire@8.3.4` ## [8.5.4] 2018-12-10 ### Fixed - When a subscriber is added, only that subscriber is fired with the latest value. ## [8.5.3] 2018-11-20 ### Fixed - Ensuring `.stop()` is part of `ColdSubscription` definition. ## [8.5.2] 2018-11-16 ### Upgrade - Moving to `popcorn@0.2.0` ## [8.5.0] 2018-11-01 ### Fixed - Enforcing minor upgrade to Popcorn. ## [8.4.2] 2018-11-01 ### Fixed - Migrating functions to Popcorn. ## [8.4.1] 2018-09-29 ### Fixed - Calling `transform.steps()` should round properly to each step. [#528](https://github.com/Popmotion/popmotion/issues/528) ## [8.4.0] 2018-09-21 ### Updated - Moved to `framesync@4.0.0` ## [8.3.8] 2018-08-30 ### Fixed - Calling `tween.resume()` would speed the tween up by firing multiple timers. [#395](https://github.com/Popmotion/popmotion/issues/395) ## [8.3.7] 2018-08-30 ### Fixed - Updating latest style-value-types and stylefire ## [8.3.6] 2018-08-21 ### Fixed - Fixed `postinstall` script by removing Chalk. ## [8.3.4] 2018-08-20 ### Fixed - Forcing `multitouch` to use `passive: true`. ## [8.3.2] 2018-06-27 ### Fixed - Forced fix from `style-value-types@3.0.3` ## [8.3.2] 2018-06-27 ### Fixed - Updated color regex in `style-value-types` ## [8.3.1] 2018-06-27 ### Fixed - Fixing some TypeScript definition incompatibilities with newer versions of TS. ## [8.3.0] 2018-06-20 ### Added - Support for animating value types: - `px` - `deg` - `%` - `vw` - `vh` - Complex types including: - SVG paths - CSS gradients - CSS shadows ## [8.2.5] 2018-06-13 ### Fixed - Removing `import`s from output declaration files. ## [8.2.4] 2018-06-08 ### Added - Exporting popular types from index.ts. ## [8.2.3] 2018-05-28 ### Fixed - Renaming `popmotion.min.js` back to `popmotion.global.min.js` ## [8.2.1] 2018-05-17 ### Fixed - Removing `process.env.NODE_ENV` from bundled hey-listen ## [8.2.0] 2018-05-17 ### Changed - Added Rollup to build process. Now just 11.1kb. ### Deprecated - `css` and `svg` functions from `stylefire`. Use `styler` instead. ## [8.1.23] 2018-05-10 ### Fixed - If an action stops more than once, `parallel` doesn't increase the stop counter. ## [8.1.22] 2018-03-23 ### Fixed - Making `easing` type optional in `interpolate`. ## [8.1.21] 2018-03-13 ### Updated - Updating `stylefire`. ## [8.1.20] 2018-03-13 ### Updated - Updating `stylefire`. ## [8.1.19] 2018-03-13 ### Updated - Updating `stylefire`. ## [8.1.18] 2018-03-13 ### Updated - Changed `Update` type definition to `Function`. ## [8.1.17] 2018-03-09 ### Updated - Stylefire updated to `1.2.6` ## [8.1.16] 2018-03-06 ### Fixed - `value` now fires a velocity check at the end of the following frame to set velocity to `0` if it hasn't been updated. ## [8.1.15] 2018-03-06 ### Updated - Stylefire updated to `1.2.5` ## [8.1.14] 2018-03-04 ### Updated - Stylefire updated to `1.2.4` ## [8.1.13] 2018-03-04 ### Updated - Stylefire updated to `1.2.3` ## [8.1.12] 2018-03-04 ### Update - Running `parseFloat` on string values when returning velocity. - Stylefire and Style Value Types upgrade ## [8.1.11] 2018-02-19 ### Update - `interpolate` roughly 200x faster when interpolating between just two numbers. ## [8.1.10] 2018-02-04 ### Fixed - Stylefire update. ## [8.1.9] 2018-01-22 ### Fixed - Fix removeEventListener in Chrome 46 [#248](https://github.com/Popmotion/popmotion/pull/248) ## [8.1.8] 2018-01-15 ### Fixed - Fixing TS errors for `stop` ## [8.1.7] 2018-01-04 ### Changed - Updating Framesync to improve performance at 30fps. ## [8.1.6] 2018-01-03 ### Fixed - Fix build. ## [8.1.5] 2018-01-03 ### Fixed - Upgrading Stylefire. - Corrected types on `tween`. ## [8.1.4] 2017-12-22 ### Added - `generateStaticSpring` transformer. ## [8.1.3] 2017-12-18 ### Added - `conditional` transformer. ## [8.1.2] 2017-12-12 ### Changed - Removed `esnext` and `module` settings from package.json, added `src` to exclude. ## [8.1.1] 2017-12-12 ### Changed - Adding README.md to package. ## [8.1.0] 2017-12-12 ### Release! ## [8.0.37@beta] 2017-12-11 ### Changed - `everyFrame` now outputs the `timeSinceStart` rather than the current framestamp (which can be taken from `framesync`). ### Fixed - Actions that use internal `everyFrame` actions now stop the timer when complete. ## [8.0.35@beta] 2017-12-09 ### Added - The API matching of `parallel` and `composite` can now accept arguments with the same shape. For instance: ```javascript composite({ x: physics(), y: physics(), }).setSpringStrength({ x: 100, y: 50 }) ``` ## [8.0.34@beta] 2017-12-05 ### Changed - `keyframes` prop `ease` is now `easings`. `ease` applies easing to the overall tween. ## [8.0.33@beta] 2017-12-04 ### Added - `timeline` animation. ### Changed - `stagger` now outputs array. ## [8.0.32@beta] 2017-11-28 ### Added - New `schedule` compositor. ### Changed - `transformChildValues` is now `transformMap`, and finally docced. ## [8.0.31@beta] 2017-11-27 ### Fixed - Stopped new instances of actions mutating shared vars. ## [8.0.30@beta] 2017-11-24 ### Fixed - Published with wrong version of Stylefire. ## [8.0.29@beta] 2017-11-24 ### Fixed - Ensuring `everyFrame` stops when it's bloody well told to. ## [8.0.28@beta] 2017-11-22 ### Changed - Upgrading Stylefire ## [8.0.27@beta] 2017-11-16 ### Fixed - `delay` waits appropriate number of time before `complete`. ## [8.0.26@beta] 2017-11-16 ### Fixed - Fixing `scale` delta equation and firing `preventDefault` on multitouch events. ## [8.0.25@beta] 2017-11-16 ### Fixed - Providing `multitouch` `scale` and `rotate` props outputs the delta as applied to those values. ## [8.0.24@beta] 2017-11-16 ### Added - `keyframes` `ease` property can now be a single function. - `multitouch` accepts initial `scale` and `rotate` properties. - `filter` chainable to actions and reactions. ### Changed - `touch` becomes `multitouch`. - `reaction` becomes `multicast`. - `onFrame` becomes `everyFrame`. ## [8.0.22@beta] 2017-11-16 ### Fixed - Fixed `tween` flip. ## [8.0.21@beta] 2017-11-16 ### Added - `scale` and `rotate` properties to `touch`. - `listen` action. ## [8.0.20@beta] 2017-11-14 ### Fixed - Fixed `isActive` bug with observable. Should look at removing this in favor of Reaction. ## [8.0.19@beta] 2017-11-14 ## [8.0.18@beta] 2017-11-14 ### Changed - Changed `spring` equation. ## [8.0.17@beta] 2017-11-14 ### Changed - Changed `spring` equation. ## [8.0.16@beta] 2017-11-14 ### Fixed - Reactivating a reaction when provided a new parent. ## [8.0.15@beta] 2017-11-14 ### Fixed - Re-reversing `spring` initial velocity. ## [8.0.14@beta] 2017-11-14 ### Fixed - Not reversing `spring` initial velocity. ## [8.0.13@beta] 2017-11-14 ### Fixed - `keyframes` composite undefined value fixed. ## [8.0.12@beta] 2017-11-11 ### Added - `value` handles objects and arrays. - `reaction` and `value` automatically stop previous action if used to start a new one. ### Fixed - `composite` APIs now map return values to the provided object, rather than as a flat array. ## [8.0.11@beta] 2017-11-10 ### Added - Object, array and colour support to `keyframes`. ### Changed - `pointerDelta` has been removed, `pointer` now accepts an initial point. ## [8.0.10@beta] 2017-11-09 ### Fixed - Fixing broken entry point in `package.json`. ## [8.0.9@beta] 2017-11-08 ### Added - Color support to all vector actions. ## [8.0.8@beta] 2017-11-08 ### Added - `crossfade`, `delay`, `merge` and `stagger` compositors. ## [8.0.7@beta] 2017-11-07 ### Added - `chain` compositor. ## [8.0.4@beta] 2017-10-30 ### Added - New reactive API. - `keyframes` animation. - `decay` animation. - `delta` transformer. ### Removed - `flow` alias for `pipe` - Transformers: `add`/`subtract`/`divide`/`multiply`/`conditional`/`alpha`/`percent`/`degrees`/`px`/`rgbUnit`/`rgba`/`hex`/`color`/`hsla` - Renderers: Moved to [Stylefire](https://github.com/Popmotion/stylefire) - Render Loop: Moved to [Framesync](https://github.com/Popmotion/framesync) - Color Tween: Moved to [Popmotion Color Tween](https://github.com/Popmotion/popmotion-color-tween) - Value Types: Moved to [Style Value Types](https://github.com/Popmotion/style-value-types) ### Changed - `physics` prop `to` is now `to` - `physics` prop `spring` is now `springStrength` - `physics` prop `autoStopSpeed` is now `restSpeed`. - `spring` prop `restDisplacement` is now `restDelta`. ## [7.8.2] 2017-10-02 ### Changed - Now publishing `dist` folder on npm for use on CodePen via unpkg.com ### Changed - `spring` transformer becomes `linearSpring` to avoid clashing with the `spring` action. ## [7.8.1] 2017-08-29 ### Fixed - Fixing mistake where Framesync's renamed `currentFrameTime` was exported instead of the previously-named `currentFrameTimestamp`. ## [7.8.0] 2017-08-29 - Spinning off `framesync` to it's own module and deprecating render loop functions for removal in `8.0.0`. ## [7.7.1] 2017-08-27 ### Fixed - Bug in `touches` (via [Mars](https://twitter.com/marsi)) ## [7.7.0] 2017-08-24 ### Added - `touches` action for multitouch support. (via [Mars](https://twitter.com/marsi)) - `parallel.getChildren` method now returns array of children actions. - `parallel` can now accept an `onUpdate` function that is provided an array of latest child values. ## [7.6.2] 2017-08-17 ### Fixed - Composite Actions were firing `onUpdate` as soon as registered. ## [7.6.1] 2017-08-17 ### Fixed - Actions were firing `onUpdate` as soon as registered. ## [7.6.0] 2017-08-07 ### Added - New `spring` action based on an interpretation of Apple's `CASpringAnimation` by [@skevy](https://github.com/skevy) ## [7.5.6] 2017-08-04 ### Fixed - Prevent `Value.toUpdate` from being undefined. ## [7.5.5] 2017-08-02 ### Fixed - Only firing `onStop` on an action bound to a value if the action is active. ## [7.5.4] 2017-07-31 ### Fixed - A key is only added to Renderer's `changedValues` array if it doesn't already exist for that frame. ## [7.5.3] 2017-07-18 ### Fixed - Consistency issue with scheduling update on `value.set` - area to look at in the future. ## [7.5.2] 2017-07-18 ### Fixed - Bug with `css` translate map. ## [7.5.1] 2017-07-17 ### Fixed - Fixed a bug with `smooth`, preventing it from working if the initial given numbers were `0`, or if it was called twice in the same frame. ## [7.5.0] 2017-07-17 ### Added - Enforced order for css `transform` properties. ## [7.4.0] 2017-07-16 ### Added - Added `seek` to `tween`. ## [7.3.1] 2017-07-13 ### Added - Updated `snap` transform to take numbers for regular interval snapping. ## [7.3.0] 2017-07-13 ### Added - New `snap` transformer. ## [7.2.9] 2017-07-12 ### Changed - `trackOffset` now calls `get` instead of `getBeforeTransform`. ## [7.2.8] 2017-07-12 ### Fixed - `angle` calculation fixed. ## [7.2.7] 2017-07-11 ### Fixed - `steps` now returning actual value rather than stepped progress. ## [7.2.6] 2017-07-05 ### Fixed - `speedPerSecond` divide by `0` bug. ## [7.2.5] 2017-07-05 ### Fixed - `lastUpdated` set to `0` on action start to prevent `NaN` velocities. ## [7.2.4] 2017-07-05 ### Fixed - `prev = current` on action start, to prevent `NaN` velocities. ## [7.2.3] 2017-07-04 ### Fixed - `pointer` events are now `passive: true` if `preventDefault` is also set to `true` to fix bug introduced by Chrome 56. ## [7.2.2] 2017-07-03 ### Changed - `composite` action is now `passive: true` to enable composition of other passive actions. ## [7.2.0] 2017-06-26 ### Added - `value` can now be provided to an action's `onUpdate` for automatic action management. ## [7.1.1] 2017-06-23 - Minor bugfix. ## [7.1.0] 2017-06-23 ### Added - `complex` `valueType`. ## [7.0.3] 2017-06-23 ### Fixed - Changed `pointer` to read mouse event `client` positioning instead of `page`, to make consistent with touch events. ## [7.0.2] 2017-06-23 - Minor bugfix. ## [7.0.1] 2017-06-23 - Minor bugfix. ## [7.0.0] 2017-06-22 ### Features - `trackOffset` action returns to simplify the process of tracking the offset of other actions. - `esnext` package.json property supported. ### Deprecated - `flow` is now `pipe`, to avoid terminology clashes with [Flow](https://flow.org/). `flow` will continue to work until `7.1.0`. ### Removed - `blendTweens` is now an optional include. ## [6.7.2] 2017-06-01 ### Fixed - Removing dependencies from `package.json` ## [6.7.1] 2017-06-01 ### Fixed - `colorTween` now uses `transform` property to convert from progress to color, which fixes composition compatibility. ## [6.7.0] 2017-06-01 ### Added - `transform` property for actions. If set, any values passed to `onUpdate` or returned from `get` will be run through this function. - `getBeforeTransform` method added for actions. Will return the current value, before transform is applied. ## [6.3.5] 2017-03-03 ### Added - `applyOffset` transformer. ## [6.3.4] 2017-03-01 ### Fixed - Undocumented range easing property in `interpolate` transformer was looking at the wrong index. ## [6.3.3] 2017-18-01 ### Added - `top`, `left`, `bottom` and `right` CSS renderer value types. ## [6.3.2] 2017-11-01 ### Added - `smooth` transformer ## [6.3.1] 2017-11-01 ### Fixed - `transformChildValues` transformer now keeps a local mutable state rather than mutating the provided state. ## [6.3.0] 2017-11-01 ### Added - Actions now have an `output` shorthand for setting the `onUpdate` property. ## [6.2.1] 2017-09-01 ### Added - Updated bezier tween blend algorithm. - Exposed bezier resolver as a transformer. ## [6.2.0] 2017-09-01 ### Added - Bezier tween blending ## [6.1.0] 2017-06-01 - Public release of new API ## [6.0.0@alpha] 2017-01-01 - New API ## [5.0.20] 2016-11-15 ### Fixed - Accidentally transpiled a version of 6.0. ## [5.0.19] 2016-11-15 ### Added - `track` Can take `preventDefault` as optional second param. Defaults to `true`. ## [5.0.18] 2016-08-08 ### Fixed - Firing `onActivateLoop` even if task is already active to prevent `once` from running more than once. ## [5.0.17] 2016-05-08 ### Fixed - Actually fixing spring clamping this time. ## [5.0.16] 2016-05-08 ### Fixed - Spring physics action now automatically ending when target is reached. ## [5.0.15] 2016-18-07 ### Fixed - Missing compilation of `5.0.14` ## [5.0.14] 2016-18-07 ### Fixed - Preventing `current` being overwritten by value type default properties. - `Tween`s now finish automatically when scrubbed, fixing timeline/stagger bug where tweens wouldn't end. ## [5.0.13] 2016-06-07 ### Fixed - `track.start` and `track.stop` now returning `this`, allowing chaining. - `from` restored as `track` default value. ## [5.0.12] 2016-06-01 ### Fixed - @[jamieowen](https://github.com/jamieowen): `transform` now being prefixed correctly. - [#156](https://github.com/Popmotion/popmotion/issues/156): Explicit tween `from` values now honoured by `flow.start`. ### Changed - Converted project from 4 space indent to 2. ## [5.0.11] 2016-05-21 ### Changed - Tween `blend` property set to `false` by default. ### Fixed - Mouse tracking in Firefox - `Object.watch` is truthy in Firefox. ## [5.0.10] 2016-05-21 ### Fixed - @[FreakTheMighty](https://github.com/FreakTheMighty): Setting bound pointer listener to ensure correct removal on `Pointer.stop`. ## [5.0.9] 2016-05-21 ### Fixed - @[jamieowen](https://github.com/jamieowen): Prefixed properties now being set correctly. ## [5.0.8] 2016-05-15 ### Added - Unit tests for `transformers.createMapper`. ### Fixed - Fixed mapped values calculated from `transformers.createMapper` mappers that are outside of the input range returning `undefined`. ## [5.0.7] 2016-04-20 ### Changed - Subsequent actions started on a flow are only given the flow's velocity if their velocity is 0. ## [5.0.6] 2016-04-17 ### Changed - Inherited actions now receive a `parentId` property with the `id` of their parent action. ## [5.0.5] 2016-04-13 ### Fixed - On `set`, `current` was returning undefined if `values` prop set as value, forcing an `adapter` read. ## [5.0.4] 2016-04-13 ### Fixed - If `adapter` returns a string that should be a pure number, we parse that as a float. ## [5.0.3] 2016-04-06 ### Fixed - Regression from testing `5.0.2`. ## [5.0.2] 2016-04-06 ### Fixed - Fixing entry point in `package.json`. ## [5.0.1] 2016-03-30 Minor bugfixes. ## [5.0.0] 2016-03-29 ### Popmotion 5.0: timelines, streamlined API, tween blending, still 12kb. **Warning:** This is a major API revision. Previous Popmotion code **will** be incompatible with this upgrade. ### Added - **Timelines**: Super-simple yet fully-featured nestable timelines to easily sequence `tweens`. - **Tween blending**: Smooth transitions between overlapping tweens. - **Standalone actions**: `tween`, `physics` and `track` can all run without the need for an `actor`. - **Adapters**: Minimal `get`/`set` API wrappers for smoothing differences between DOM, SVG and frameworks. - **Transformers**: Composable functions to transform values between update and render. - **Unified physics engine**: Handles `velocity`, `friction` and `spring` in one unified `physics` action. - **Small**: All this for less than 12kb minified & gzipped. - **Smaller**: Rewritten entirely using ES6 exports to allow tree-shaking, ignoring the parts of Popmotion you don't include. - **Global time dilation**: `setGlobalDilation` method can change the global time. - **Flow layer**: Replaces Actors and can work entirely in the background to manage multiple actions on the same object. - Support `points` property for `polygon` and `polyline` tags. - **MIT**: Changed licence to MIT. ### Removed - `new` - dropped in favour of factory functions (ie `new Tween()` becomes `tween()`). This will allow further non-API-breaking optimisations. - `Actor` dropped in favour of `flow`. The Actor model was monolithic, flows are automatically generated for `element`s in the background and can be accessed optionally via `detectFlow`. - `process`: Now `task`. Prevents conflict with global common in browsers. - Removed Action `watch` property in favour of more flexible `transform`: Simply provide a function that returns a different value. - Native Meteor support, as we kept forgetting to update it. - jQuery support - provide elements as returned from `$('.yourElement').get()` instead. - `Sequence`: Dropped in favour of the `timeline` function. - `Iterator`: Dropped in favour of using native array methods and the new `stagger` function. - `Simulate`: Dropped in favour of unified `physics` action. ### Changed - `friction` now a value between `0` and `1` - `0` provides no friction, `1` will strip all velocity within a single frame. ## [4.3.4] 2015-12-30 ### Fixed - `Simulate.hasChanged` incorrectly calculated. ## [4.3.3] 2015-12-29 ### Fixed - Added `acceleration` to list of properties that can be set as functions, resolved when an Action starts. ## [4.3.2] 2015-12-26 ### Fixed - Recursion bug with `Actor.sync` under specific circumstances. ## [4.3.1] 2015-12-24 ### Removed - `deceleration` from `Simulate`. ### Fixed - Fixed bug where `Tween` would start as ended if initialised during `update` ie via a `Sequence`. ## [4.3.0] 2015-12-17 ### Added - `scale` value type hooked up to CSS and SVG roles. - `utils.camelToDash` utility function for converting camelCase props to dash-case. ### Changed - Default `Simulate` `stopSpeed` changed from `5` to `0.0001` to account for tiny numbers like 0-1. ## [4.2.7] 2015-12-09 ### Fixed - SVG properties set ie `fillOpacity` are now getting set as `fill-opacity`. ## [4.2.6] 2015-12-08 ### Fixed - Fixed recursive loop when splitting Actor values. ## [4.2.5] 2015-12-07 ### Added - `opacity`, `fillOpacity` and `strokeOpacity` to `ui.svg` Role type map. ## [4.2.4] 2015-12-07 ### Added - `Simulate.autoEnd` property, set to `false` to prevent simulations from automatically ending. ### Fixed - TweenControls `reverse` now work on completed Tweens. ## [4.2.3] 2015-12-06 ### Added - Actors automatically `sync` on init. ## [4.2.2] 2015-12-03 ### Update - Moving from Babel's official es2015 preset to a custom build. ## [4.2.1] 2015-12-03 ### Update - Updating Babel to fix transpilation errors in IE10. ## [4.2.0] 2015-12-02 ### Changed - Moving to dual commercial and GPLv3 licence. ## [4.1.0] 2015-11-28 ### Added - Actor value `watch` property can now be a function returning any numerical value. - Added `smooth` for all actions. - Added `calc.toDecimal` calculator function. ## [4.0.0] 2015-11-20 ### Added - New core processing loop. Separates processes into four stages: `update`, `preRender`, `render`, `postRender`. - `Process.once` to fire a Process for a single frame. - Run background processes by passing `true` to `Process.setBackground` or as the last argument to the `Process` constructor. Background processes won't spin up the process loop when activated, only running when non-background processes are active. ### Changed - `new ui.Process()` takes `callback, scope` instead of `scope, callback`. - `Process` callbacks now receive `scope` as the first argument, then `framestamp, frameDuration`. ### Removed - `Process.every`, `Process.reset` and `Process.fire`. Processes should only be started/stopped with `Process.start` and `Process.stop` respectively, to ensure they're run as part of the main loop. `every` is redundant with a combination of `setInterval` and `Process.once`. - `Process.start` no longer takes a time period as an argument - use `setTimeout` to `Process.stop`. ## [3.8.1] 2015-11-19 ### Fixed - Pow easings incorrectly named var. ## [3.8.0] 2015-11-19 ### Added - Elliot Geno's awesome `'anticipate'` easing. Follow him at [@ElliotGeno](https://twitter.com/ElliotGeno/). - In-built easings now available in the `ui.ease` namespace. So instead of providing `'backOut'` you can reference it literally - `ui.ease.anticipate`. - Create modified easing function strengths, for instance `ease: ui.modifyEase(ui.ease.backOut, 2)`. `'ease'` and `'back'` in/out/inOut variants and `'anticipate'` are available. You must provide the literal reference rather than string reference. ## [3.7.1] 2015-11-11 ### Changed - Refactored core clock to increase performance in lower-end devices. ## [3.7.0] 2015-11-07 ### Changed - Software licence changed. Popmotion is now free for non-commercial uses, and requires a per-project licence for commercial projects. - More files converted to ES6 syntax. - Moved back to small for production compilation. - Upgraded Babel from 5 to 6. ### Added - Beginning suite of Mocha unit tests. ## [3.6.3] 2015-11-03 ### Added - Extra tests to ensure Popmotion is loading correctly in Meteor. ### Fixed - Explicit test for rAF for when `window` is available and `requestAnimationFrame` isn't. ## [3.6.2] 2015-11-02 ### Added - Meteor support. Published on [Atmosphere](https://atmospherejs.com/popmotion/popmotion) as `popmotion:popmotion`. ### Fixed - `performance` and `window` checks in Node. ## [3.6.1] 2015-10-29 ### Fixed - Smoothing lower than frameduration produced odd values. - Smooth by `0` `Infinity` error ## [3.6.0] 2015-10-29 ### Added - New `transform(value, key, actor)` property for `Actor` values. Allows you to return a new value after its been processed by an action, but before it's been limited/rounded etc. - Exposed our `Track` `smooth` calculator as `ui.calc.smooth`. ### Changed - Refactored some files to cut down on filesize. ### Fixed - Fixed a regression from `3.3.3` that fixed `performance` in IE9. ## [3.5.0] 2015-10-21 ### Added - Roles are now getter/setters, if `get` and `set` methods are present. For example: CSS role has getter/setters, so you can call: ui.css(element, { position: 'absolute', top: 0 }); ## [3.4.4] 2015-10-22 ### Fixed - `Actor.stop().start()` was failing to run. ## [3.4.3] 2015-10-21 ### Fixed - `Actor.sync` now returns `Actor` rather than the bound sync `Action`. ## [3.4.2] 2015-10-21 ### Fixed - `svg` tags are now assigned `ui.css` role. ## [3.4.1] 2015-10-17 ### Fixed - Delaying test element creation in CSS Role prevents error in server-side rendering. ## [3.4.0] 2015-10-16 ### Added - `Sequence` class, for creating a sequence of non-sequential actions. - `onComplete` property to `Iterator.stagger`. - Pass an action as the `Iterator.each` and `Iterator.stagger` `method` argument to automatically run `start` on each `Actor`. ### Changed - Using rAF-provided timestamp instead of `performance.now` for smoother motion. ## [3.3.3] 2015-10-13 ### Fixed - @stoikerty: Fixed `performance.now` check for IE9. ## [3.3.2] 2015-10-10 ### Fixed - `Pointer` events now properly unbound on `Actor` stop. ## [3.3.1] 2015-10-09 ### Added - `Actor.smooth` now settable on a per-value basis. - `direct` value property added to take direct input values instead of inputOffset. ## [3.3.0] 2015-10-08 ### Added - `smooth` property added to `Track` - smoothes out values coming from `Input` sources. ## [3.2.1] 2015-10-07 ### Fixed - `watch` no longer overwritten on `set`. ## [3.2.0] 2015-10-06 ### Added - `ui.select` now caches and finds cached Actors bound to DOM elements. If one Actor is found, it returns an Actor. If multiple Actors are found, an Iterator. - `reverse` method to `Iterator`. ## [3.1.7] 2015-10-05 ## Fixed - Adjusting `hasChanged` logic to always fire on first frame. - Changing `Iterator.stagger` logic for more even staggers. ## [3.1.6] 2015-10-05 ## Fixed - Manually moved the library class filenames to uppercase. ## [3.1.5] 2015-10-05 ## Fixed - Renamed some class files to uppercase to fix include fails in some Browserify compilations. ## [3.1.4] 2015-10-05 ## Added - Simulation functions are now sent `value , frameDuration , timeStarted ` as arguments. ## [3.1.3] 2015-10-04 ## Fixed - Actions fired within an `onComplete` wouldn't start because the number of active Actions was being counted incorrectly. ## [3.1.2] 2015-10-03 ### Fixed - Major Firefox bug 1) `values` false-positive test for `watch` property, now explicitly checking for `string`. - Major Firefox bug 2) `element.style.hasOwnProperty()` always returning false, switching to `in element.style`. ## [3.1.1] 2015-10-02 ### Fixed - Clearing Actor then/next queue on `start`. - Saving all arguments to queue via `then` so we can pass Inputs. ## [3.1.0] 2015-10-01 ### Added - Action-specific `onStart`, `onUpdate`, `onFrame` and `onComplete` callbacks. These callbacks will be active only for the duration of the action, while callbacks attached to the host Actor will persist through all actions. ### Changed - `onStart` callbacks are now fired immediately before the Actor is activated, giving actions extra time to initiate and make use of the [< 100ms response window](https://aerotwist.com/blog/flip-your-animations/). ### Fixed - Bugfix for deep-copying Actions on Action start. That was a whoops and a half. ## [3.0.4] 2015-09-30 ### Fixed - Bugfix for subvalues not being flipped correctly. ## [3.0.3] 2015-09-30 ### Fixed - SVG Role divide by 0 error when `scale` === 0. ## [3.0.2] 2015-09-29 ### Fixed - Bugfix where Popmotion would attempt to split numerical values. - Bugfix for `complex` valueType regex where negative numbers were made positive. ## [3.0.0] 2015-09-29 ### Added - Full SVG support. - Multiple simultaneous Actions on a single Actor - mix Simulations, Tracking and Tweens on the same elements! - Animate complex strings, like `path` `d` attributes. - Iterator - to allow single classes to be extended the ActorCollector has been dropped in favour of a generic Iterator class, which can iterate over any set of classes. This also makes it clearer when you're interacting with multiple items vs a single Actor, and these items are not longer limited to Actors. - Roles - `ui.css`, `ui.attr`, `ui.svg`, `ui.draw`. These are auto-assigned to Actors when initialised with an `element` property, but can also be manually assigned with the `as` property on Actor initialisation. - Actors can have multiple roles by providing an array to `as`. - New roles can be created with `new ui.Role()`. - Value names can be translated before a Role accesses it, via a Role's `map` property. For example `ui.cssRole` maps `x` to `translateX`. - Action classes/definitions - `Tween`, `Simulate`, `Track` ### Removed - `ui.addRoute` - see Roles - `ui.addPreset`, `ui.addAction` - see Tween, Simulate and Track classes - `ActorCollection` - see Iterator - `play`, `run`, `track` Actor methods - use `start` ### Changed - `addToQueue` Actor method is now `then` and takes Tween, Simulate and Track instances instead of raw properties. - `mapLink` Actor value property is now `mapFrom`. - Default tween `duration` set to `300`. - Default tween `ease` set to `"easeOut"`. - `reverse`, `reset` -> `restart`, `seek` methods moved to `TweenControls` (returned when a `Tween` is passed to `start`) ### Deprecated - `flipValues`, `reverse`, `reset`, `resetProgress`, `resetOrigins` and `seek` Actor methods - these are all going to be moved to the `Tween` class in `3.2.0` ## [2.0.4] 2015-08-10 ### Fixed - Fixing `bounce` and `capture` simulation calls. ## [2.0.3] 2015-08-10 ### Removed - Removed undocumented `ui.addSimulation` - new simulations now passed as function instead of simulation name. ## [2.0.2] 2015-08-10 ### Changed - `Actor.seek` no longer an Action, much higher performance. ## [2.0.1] 2015-08-07 ### Changed - Only numerical properties can be set as functions to be resolved on action start. ## [2.0.0] 2015-08-07 ### Removed - `ui.addEasing()` ### Added - New easing functions are now generated with `new ui.Easing()` and provided as the `ease` property. When provided an easeIn function, `in`, `out` and `inOut` methods are returned. ## [1.0.0] 2015-08-06 **Note:** Forked from Redshift v2.1.1. ### Changed - `redshift` object name now `ui`. - Terminology changes to fix API semantics: - **Action:** Actor - **ActionCollection:** ActionCollection - **Rubix:** Action (as in, an Action an Actor can perform) ### Added - Add new easing functions with `ui.addEasing()`. Provide the easeIn function and Popmotion will generate easeOut and easeInOut variants. - Add new simulation functions to `run` with `ui.addSimulation()`. - Any value can be a special **value type**, ie a color. `'#FFF'` splits into four numerical properties, `Red`, `Blue`, `Green` and `Alpha`. - Add new value types with `ui.addValueType()`. - HSLA value type support. ## [2.1.1] 2015-06-11 ### Fixed - Change Action `rubix` from `"seek"` to `"play"` onEnd. ## [2.1.0] 2015-06-11 ### Added - Added `.seek()` method to Actions. ## [2.0.5] 2015-06-02 ### Added - ActionGroup returns array of values when a getter is called. ## [2.0.4] 2015-06-01 ### Fixed - `onStart` regression from 2.0.0 - only firing for 'values' route. ## [2.0.3] 2015-06-01 ### Fixed - Fixing hasChanged regression, prevented .run() from stopping automatically. ## [2.0.2] 2015-06-01 ### Fixed - Adding Alpha property to all split color values (default: 1), in case a value has mixed RGB and RGBA properties. ## [2.0.1] 2015-06-01 ### Added - CSS values travelling through a splitter (ie backgroundColor is split into RGB values) can be set as functions like normal values. ## [2.0.0] 2015-06-01 ### Changed - Upgraded stepped easing algorithm. - `Action.hasChanged` set to `true` when `Action.isActive(true)` ### Removed - `.props()` method removed - properties now saved directly to Action object. - `.data()` functionality removed, just set properties of Action instead. - `.flip()` is now `.flipValues()`. ## [1.4.1] 2015-05-31 ### Fixed - ActionGroup.stagger() wasn't returning `this` ## [1.4.0] 2015-05-31 ### Added - Action Groups for controlling multiple Actions at once. - Stagger method for Action Groups. - DOM selection support for creating Actions prepopulated with DOM elements. ### Fixed - Calling .play(preset) while Action was in progress failed to add that call to the play queue. - Added check for Process timers before clearing as clearTimeout is costing ~.4ms. ================================================ FILE: packages/popmotion/LICENSE.md ================================================ The MIT License (MIT) Copyright © 2019 Framer BV Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/popmotion/README.md ================================================ # Popmotion ================================================ FILE: packages/popmotion/docs/api/action.md ================================================ --- title: Action description: Create a reactive stream of values. next: animations --- # Action Action is a simplified Rx-inspired reactive stream focused on animation. **Every Popmotion animation and input is an action.** When an action is started, it returns a simple interface that includes **at least** a `stop` method. ## Import ```javascript import { action } from 'popmotion'; ``` ## Usage ### Definition The `action` factory takes one argument, an `init` function. This is a function that is provided an object of `update`, `complete`, and `error` functions. Usage of these functions is optional. Your action may call all or just some of them: ```javascript action(({ update, complete, error }) => { update(1); }); ``` ### Initialisation `action` returns a `start` method. This also accepts an object of `update`, `complete`, and `error` functions. When called, the `init` function is provided these functions, and a **new instance** of the action is created. Calling `start` multiple times will create multiple, separate instances of the action. For example: ```javascript const foo = action(({ update }) => { let i = 0; setInterval(() => update(i++), 50); }); foo.start({ update: (v) => console.log(v) }); // 0, 1, 2... ``` If `start` is passed **only a function**, that is assigned to the `update` function: ```javascript foo.start((v) => console.log(v)); // 0, 1, 2... ``` We can also listen for the `complete` event like this: ```javascript const foo = action(({ update, complete }) => { let i = 0; setInterval(() => { update(i++); if (i === 10) complete(); }, 50); }); foo.start({ update: (v) => console.log(v), // ...8, 9, 10 complete: () => console.log('complete!') }); ``` ### Interface The `init` function can **optionally** return an API. For instance, we might use this to stop a timer: ```javascript const foo = action(({ update }) => { const interval = setInterval(() => update('ping!'), 100); return { stop: () => clearInterval(interval) }; }); const bar = foo.start(console.log); setTimeout(() => bar.stop(), 1000); ``` Any method returned by the action `init` function will be exposed when an action instance is created. ### Modification `action` is **chainable**, which means it offers methods that can alter the behaviour of the base action. Currently, these are `while` and `pipe` (see [Methods](#methods)). When an action is chained, a **new action** is returned. For instance: ```javascript const foo = action(({ update }) => { let i = 0; setInterval(() => update(i++), 50); }); const lessThanTen = (v) => v < 10; const log = (v) => console.log(v); foo.start(log); // ...8, 9, 10, 11... foo.while(lessThanTen).start(log); // ...8, 9 ``` ## Methods ### pipe ```typescript pipe(...funcs: (v: any) => any) ``` Returns a **new** action that passes the output of the original action's `update` through the provided functions, from left to right. #### Example ```javascript const init = ({ update }) => update(10); const double = (v) => v * 2; const px = (v) => v + 'px'; action(init) .pipe(double, px) .start((v) => console.log(v)); // '20px' ``` ### start ```typescript start(update: (v: any) => void) start({ complete? () => void, error?: (err: any) => void, update?: (v: any) => void }) start(reaction) ``` Starts the action by running its initiation function, and returning its API. Every interface returned by a `start` call, **regardless of the API returned from the observable**, will return at least a `stop` function. It can be provided either an `update` function, or an object with `update`, `complete` and `error` functions. #### Example ```javascript // Doesn't return an API const foo = action(({ update }) => update(1)).start(); foo.stop(); // Returns a custom API const bar = action(({ update }) => { let i = 0; setInterval(() => update(i), 100); return { setOutput: (v) => i = v }; }).start(); bar.setOutput(2); setTimeout(() => bar.stop(), 1000); ``` ### while ```typescript while(predicate: (v: any) => boolean) ``` Returns a new action, that will continue to run **while** the updated values match the provided predicate. When the predicate function returns `false`, the action will `complete`. ### Example ```javascript let latest = 0; const init = ({ update }) => { let i = latest; setInterval(() => update(i++), 50); }; action(init) .while((v) => v < 10) .start({ update: (v) => latest = v, complete: () => console.log(v) // 9 }); ``` ================================================ FILE: packages/popmotion/docs/api/animation/decay.md ================================================ --- title: Decay description: Exponential deceleration, primarily for use in momentum scrolling. category: animation --- # Decay An animation that decelerates to an automatically generated target value, based on its initial velocity. This target can be modified. **Note:** This animation is deprecated in favour of [`inertia`](/api/inertia). ## Import ```javascript import { decay } from 'popmotion'; ``` ## Usage Provide a `velocity` and `from` value, and `decay` will calculate a `to` target value and decelerate towards it. It will output values to a function provided to `start`: ```javascript decay({ velocity: 200, from: 50 }) .start(v => console.log(v)) ``` We can adjust the calculated target value with the `modifyTarget` prop. This is a function that accepts the calculated target and returns a new one. For instance, we can snap the target to the nearest `100` like so: ```javascript decay({ velocity: 200, from: 50, modifyTarget: v => Math.round(v / 100) * 100 }) ``` ## Props The following properties may be passed to `decay`: ### velocity Initial velocity to decelerate from. **Default:** `0` ### from Start value of the animation. **Default:** `0` ### power A constant with which to calculate a target value. Higher power = further target. **Default:** `0.8` ### timeConstant Adjusting the time constant will change the duration of the deceleration, thereby affecting its feel. **Default:** `350` ### restDelta Automatically completes the action when the calculated value is this far away from the target. The final output value will be snapped to the target. **Default:** `0.5` ### modifyTarget A function that receives the calculated target and returns a new one. Useful for snapping the target to a grid, for example. **Default:** `v => v` ## Methods ### Action methods `decay()` returns: #### start Starts the animation and returns playback controls. Can be provided **either** a function: ```javascript decay(props).start(v => {}) ``` Or a named map of functions for `update` and `complete`: ```javascript decay(props).start({ update: v => {}, complete: () => {} }) ``` #### filter Returns a new version of the animation, that filters out any value when the provided predicate function returns `false`: ```javascript const filtered = decay(props).filter(v => v > 0.5) // This animation will only output values higher than 0.5: filtered.start(v => {}) ``` #### pipe Returns a new animation that will pass any output value through this series of functions: ```javascript // This animation will round output values and then double them: decay({ from: 0, velocity: 1000 }) .pipe(Math.round, v => v * 2) .start(v => {}) ``` #### while Returns a new animation that will `complete` when the provided predicate function returns `false`: ```javascript // This animation will end when an output value is higher than 0.5: decay().while(v => v < 0.5) ``` ### Playback methods `decay().start()` starts a new animation and returns the following playback methods: #### stop Stops the animation. ## Example ================================================ FILE: packages/popmotion/docs/api/animation/every-frame.md ================================================ --- title: Every Frame description: Fires with timestamp, once every frame. category: animation --- # Every Frame `everyFrame` fires once per frame, and provides `update` with the duration of time since it started. ## Import ```javascript import { everyFrame } from 'popmotion'; ``` ## Usage ```javascript everyFrame() .start((timeSinceStart) => console.log(timeSinceStart)); ``` ## Methods ### Action methods `everyFrame()` returns: - `filter((v: any) => boolean)`: Returns a new action that filters out values when the provided function returns `false`. - `pipe(...funcs: Array<(v) => v)`: Returns a new action that will run `update` values through this sequence of functions. - `start(update | { update, complete })`: Starts the action and returns a subscription. - `while((v: any) => boolean)`: Returns a new action that will `complete` when the provided function returns `false`. ### Subscription methods `everyFrame().start()` returns: - `stop(): void` ## Example ================================================ FILE: packages/popmotion/docs/api/animation/inertia.md ================================================ --- title: Inertia description: Inertial scrolling with spring-loaded boundaries. category: animation --- # Inertia An animation decelerates a value based on its initial velocity, usually used to implement inertial scrolling. Optionally, `min` and `max` boundaries can be defined, and `inertia` will snap to these with a [`spring`](/api/spring) animation. This animation will automatically precalculate a target value, which can be modified with the `modifyTarget` property. This allows you to add snap-to-grid or similar functionality. ## Import ```javascript import { inertia } from 'popmotion'; ``` ## Usage Provide a `velocity` and `from` value, and `inertia` will automatically calculate a target and decelerate towards it. ```javascript inertia({ velocity: 200, from: 50 }) .start(v => console.log(v)) ``` We can adjust the calculated target value with the `modifyTarget` prop. This is a function that accepts the calculated target and returns a new one. For instance, we can snap the target to the nearest `100` like so: ```javascript inertia({ velocity: 200, from: 50, modifyTarget: v => Math.round(v / 100) * 100 }) ``` We can also set `min` and/or `max` boundaries: ```javascript inertia({ velocity: 500, from: 50, max: 1000 }) ``` When the animated value breaches `max`, it'll snap to `max` using a [spring](/api/spring) animation. ## Props The following properties may be passed to `inertia`: ### velocity Initial velocity to decelerate from. **Default:** `0` ### from Start value of the animation. **Default:** `0` ### modifyTarget A function that receives the calculated target and returns a new one. Useful for snapping the target to a grid, for example. **Default:** `v => v` ### bounceStiffness Stiffness of the bounce animations. Higher values will create more sudden movement. **Default:** `500` ### bounceDamping Strength of opposing force for the bounce animations. If set to `0`, spring will oscillate indefinitely. **Default:** `10` ### power Higher power = further target. **Default:** `0.8` ### timeConstant Adjusting the time constant will change the duration of the deceleration, thereby affecting its feel. **Default:** `700` ## Methods ### Action methods `inertia()` returns: #### start Starts the animation and returns playback controls. Can be provided **either** a function: ```javascript inertia(props).start(v => {}) ``` Or a named map of functions for `update` and `complete`: ```javascript inertia(props).start({ update: v => {}, complete: () => {} }) ``` #### filter Returns a new version of the animation, that filters out any value when the provided predicate function returns `false`: ```javascript const filtered = inertia(props).filter(v => v > 0.5) // This animation will only output values higher than 0.5: filtered.start(v => {}) ``` #### pipe Returns a new animation that will pass any output value through this series of functions: ```javascript // This animation will round output values and then double them: inertia({ from: 0, velocity: 1000 }) .pipe(Math.round, v => v * 2) .start(v => {}) ``` #### while Returns a new animation that will `complete` when the provided predicate function returns `false`: ```javascript // This animation will end when an output value is higher than 0.5: inertia().while(v => v < 0.5) ``` ### Playback methods `inertia().start()` starts a new animation and returns the following playback methods: #### stop Stops the animation. ## Example ================================================ FILE: packages/popmotion/docs/api/animation/keyframes.md ================================================ --- title: Keyframes description: Animate through a linear sequence of values. category: animation --- # Keyframes Keyframes accepts an array of values and will animate between each in sequence. Timing is defined with a combination of `duration`, `easings` and `times` properties (see [Props](#props)) ## Import ```javascript import { keyframes } from 'popmotion'; ``` ## Usage You can set the values to animate between with the `values` property. You can set as many values as you wish. ```javascript keyframes({ values: [0, 1, 3] }) .start(v => console.log(v)) ``` The overall length of the animation can be changed via `duration`. Each individual value can be given a progress value between `0` and `1` via the `times` array. That determines at which point during the animation it should be at the corresponding value. Finally, and `easings` property maps to the animations between each value transition (so the length of this array is one fewer than our total number of values). ```javascript keyframes({ values: [0, 100, 200], times: [0, 0.2, 1], duration: 1000, easings: [ease.linear, ease.cubicBezier(.17,.67,.83,.67)] }) ``` ### Value types `keyframes` supports the animation of the following value types: #### Number ```javascript keyframes({ values: [10, 40, 100, -100] }) ``` #### Units **Supported**: `px`, `%`, `deg`, `vh`, and `vw` ```javascript keyframes({ values: ['10%', '40%', '100%', '-100%'] }) ``` #### Colors **Supported**: RGB(A), HSL(A) and Hex ```javascript keyframes({ values: ['#fff', '#000', '#f00'] }) keyframes({ values: [ 'rgba(0, 200, 100, 1)', 'rgba(60, 100, 80, 0.5)', 'rgba(60, 100, 80, 1)' ] }) keyframes({ values: [ 'hsl(0, 50%, 50%)', 'hsl(180, 80%, 50%)', 'hsl(45, 60%, 60%)' ] }) ``` #### Complex Complex sequences of values, like SVG path definitions, CSS shadows and background gradients. The non-numerical portions of these values must stay in the same format in the `from` and `to` props. ```javascript keyframes({ values: [ '0px 0px 0px inset rgba(0, 0, 0, 0.2)', '3px 3px 10px inset rgba(0, 0, 0, 0.5)', '10px 10px 20px inset rgba(0, 0, 0, 0.8)' ] }) ``` ```javascript keyframes({ values: [ 'linear-gradient(to right, #f00, #0f0)', 'linear-gradient(to right, #00f, #f00)', 'linear-gradient(to right, #f00, #0f0)' ] }) ``` #### Objects Named objects composed of any of the above types may also be animated. ```javascript keyframes({ values: [ { x: 0, background: 'hsla(125, 100, 50, 1)' }, { x: 0, background: 'hsla(20, 100, 60, 1)' }, { x: 100, background: 'hsla(20, 100, 60, 1)' } ] }) ``` #### Arrays Arrays composed of any of the above types may also be animated. ```javascript keyframes({ values: [ [0, '10vh', 'hsla(125, 100, 50, 1)'], [1, '20vh', 'hsla(20, 100, 60, 1)'], [0, '40vh', 'hsla(125, 100, 50, 1)'] ] }) ``` ## Props The following properties may be passed to `keyframes`: ### values An array of values to animate between. **Required** ### duration Total duration of animation, in milliseconds. **Default:** `300` ### easings An array of easing functions for each generated tween, or a single easing function applied to all tweens. This array should be `values.length - 1`. **Default** `[...easeOut]` ### times An array of numbers between `0` and `1`, representing `0` to `duration`, that represent at which point each number should be hit. Defaults to an array of evenly-spread durations. ### elapsed Duration of animation already elapsed, in milliseconds. **Default:** `0` ### ease A function, given a progress between `0` and `1`, that returns a new progress value. Used to affect the rate of playback across the duration of the **overall** animation. **Default:** `linear` ### loop Number of times to loop animation on `complete`. Set as `Infinity` to loop forever. **Default:** 0 ### flip Number of times to flip animation on `complete`. Set as `Infinity` to flip forever. **Default:** 0 ### yoyo Number of times to reverse animation on `complete`. Set as `Infinity` to reverse forever. **Default:** 0 ## Methods ### Action methods `keyframes()` returns: `tween()` returns: #### start Starts the animation and returns playback controls. Can be provided **either** a function: ```javascript keyframes().start(v => {}) ``` Or a named map of functions for `update` and `complete`: ```javascript keyframes().start({ update: v => {}, complete: () => {} }) ``` #### filter Returns a new version of the animation, that filters out any value when the provided predicate function returns `false`: ```javascript const filtered = keyframes().filter(v => v > 0.5) // This animation will only output values higher than 0.5: filtered.start(v => {}) ``` #### pipe Returns a new animation that will pass any output value through this series of functions: ```javascript // This animation will round output values and then double them: keyframes() .pipe(Math.round, v => v * 2) .start(v => {}) ``` #### while Returns a new animation that will `complete` when the provided predicate function returns `false`: ```javascript // This animation will end when an output value is higher than 0.5: keyframes().while(v => v < 0.5) ``` ### Playback methods `keyframes().start()` starts a new animation and returns the following playback methods: #### getElapsed Returns time elapsed in milliseconds. #### getProgress Returns animation progress as a value of `0`-`1`. #### seek Seeks animation to this position as a value of `0`-`1`. ```javascript const playback = keyframes().start(v => {}) playback.seek(0.5) ``` #### pause Pauses playback. #### resume Resumes playback. #### reverse Reverses the direction of playback. #### stop Stops the animation. ## Example ================================================ FILE: packages/popmotion/docs/api/animation/physics.md ================================================ --- title: Physics description: Integrated simulation of velocity, acceleration, friction and springs. category: animation --- # Physics Simulate velocity, acceleration, friction and springs. This is an integrated simulation, meaning the latest state is incorporated at discrete time intervals. It exposes `set` methods that change the simulation while it's running. This is unlike the differential equations in [decay](/api/decay) and [spring](/api/spring), which can't be changed while in motion (although both offer higher-accuracy simulations which lead to smoother animations). ## Import ```javascript import { physics } from 'popmotion'; ``` ## Usage We can simulate a consistent velocity by providing the `velocity` property. Values are output to the function provided to `start`: ```javascript physics({ from: 0, velocity: 1000 }) .start(v => console.log(v)) ``` To slow the velocity down over time, we can provide a `friction` value between `0` (no friction) and `1` (dead stop): ```javascript physics({ from: 0, velocity: 1000, friction: 0.8 }) ``` To put speed back in the system we can use `acceleration`, measured in units per second: ```javascript physics({ from: 0, velocity: 1000, acceleration: 200 }) ``` To simulate a spring, we add `to` and `springStrength` values: ```javascript physics({ from: 0, velocity: 1000, friction: 0.8, to: 400, springStrength: 500 }) ``` ### Value types `physics` supports the animation of a number of different value types. #### Number ```javascript physics({ from: 0, velocity: 100 }) ``` #### Units **Supported**: `px`, `%`, `deg`, `vh`, and `vw` ```javascript physics({ from: '0px', velocity: 100 }) ``` #### Objects Named objects composed of any of the above types may also be animated. `friction`, `acceleration`, `velocity` and `springStrength` can also be set as objects, to apply property-specific settings: ```javascript physics({ from: { x: '0px', y: '0px' }, velocity: { x: 200, y: 1000 } }) ``` #### Arrays Arrays composed of any of the above types may also be animated. `friction`, `acceleration`, `velocity` and `springStrength` can also be set as arrays, to apply property-specific settings: ```javascript physics({ from: ['10vh', 0], velocity: [100, 100] }) ``` ## Props The following properties may be passed to `physics`: ### velocity Velocity in units per second. **Default:** `0` ### from Start simulation from this number. **Default:** `0` ### acceleration Increase `velocity` by this amount every second. **Default:** `0` ### restSpeed When absolute speed drops below this value, `complete` is fired. **Default:** `0.001` ### friction Amount of friction to apply per frame, from `0` to `1`. **Default:** `0` ### springStrength If set with `to`, will spring towards target with this strength. **Default:** `0` ### to If set with `springStrength`, will gradually "spring" towards this value. **Default:** `0` ## Methods ### Action methods `physics()` returns: #### start Starts the animation and returns playback controls. Can be provided **either** a function: ```javascript physics().start(v => {}) ``` Or a named map of functions for `update` and `complete`: ```javascript physics().start({ update: v => {}, complete: () => {} }) ``` #### filter Returns a new version of the animation, that filters out any value when the provided predicate function returns `false`: ```javascript const filtered = physics().filter(v => v > 0.5) // This animation will only output values higher than 0.5: filtered.start(v => {}) ``` #### pipe Returns a new animation that will pass any output value through this series of functions: ```javascript // This animation will round output values and then double them: physics({ from: 0, velocity: 100 }) .pipe(Math.round, v => v * 2) .start(v => {}) ``` #### while Returns a new animation that will `complete` when the provided predicate function returns `false`: ```javascript // This animation will end when an output value is higher than 0.5: physics().while(v => v < 0.5) ``` ### Playback methods `physics().start()` starts a new animation and returns the following playback methods: #### set Change the current value. #### setAcceleration Set `acceleration`. #### setFriction Set `friction`. #### setSpringStrength Set `springStrength`. #### setSpringTarget Set `to`. #### setVelocity Set `velocity`. #### stop Stops the animation. ## Example ================================================ FILE: packages/popmotion/docs/api/animation/spring.md ================================================ --- title: Spring description: Accurate, versatile spring animation. category: animation --- # Spring A spring animation based on `stiffness`, `damping` and `mass`. This simulation offers smoother motion and a greater variety of spring-feels than the basic spring simulation found in [physics](/api/physics). It's based on the same equations underlying Apple's `CASpringAnimation`. ## Import ```javascript import { spring } from 'popmotion'; ``` ## Usage `spring` accepts a `from` and `to` value, and will output values to the function provided to `start`: ```javascript spring({ from: 0, to: 100 }) .start(v => console.log(v)) ``` Pass props like `damping`, `stiffness` and `mass` to affect the character of the spring: ```javascript spring({ from: 0, to: 100, stiffness: 200, damping: 20 }) ``` ### Value types `spring` supports the animation of a number of different value types. It's generally best output as values that govern physical movement, like `x`, `y` and `rotate`, but it can be used to power colors and shadows. #### Number ```javascript spring({ from: 0, to: 100 }) ``` #### Units **Supported**: `px`, `%`, `deg`, `vh`, and `vw` ```javascript spring({ from: '0px', to: '100px' }) ``` #### Colors **Supported**: RGB(A), HSL(A) and Hex ```javascript spring({ from: '#fff', to: '#000' }) spring({ from: 'rgba(0, 200, 100, 1)', to: 'rgba(60, 100, 80, 0.5)' }) spring({ from: 'hsl(0, 50%, 50%)', to: 'hsl(180, 80%, 50%)' }) ``` #### Complex Complex sequences of values, like SVG path definitions, CSS shadows and background gradients. The non-numerical portions of these values must stay in the same format in the `from` and `to` props. ```javascript spring({ from: '0px 0px 0px inset rgba(0, 0, 0, 0.2)', to: '3px 3px 10px inset rgba(0, 0, 0, 0.5)' }) ``` ```javascript spring({ from: 'linear-gradient(to right, #fff, #000)', to: 'linear-gradient(to right, #333, #666)' }) ``` #### Objects Named objects composed of any of the above types may also be animated. `velocity`, `mass`, `damping` and `stiffness` can also be set as objects, to apply property-specific settings: ```javascript spring({ from: { x: '0px', y: '0px' }, to: { x: '100px', y: '200px' }, stiffness: { x: 200, y: 1000 }, damping: { x: 10, y: 50 } }) ``` #### Arrays Arrays composed of any of the above types may also be animated. `velocity`, `mass`, `damping` and `stiffness` can also be set as arrays, to apply property-specific settings: ```javascript spring({ from: ['10vh', 0], to: ['50vh', 100], stiffness: [400, 1000] }) ``` ## Props The following properties may be passed to `spring`: ### from Start value of the animation. **Default:** `0` ### to End value of the animation. **Default:** `1` ### stiffness Stiffness of the spring. Higher values will create more sudden movement. **Default:** `100` ### damping Strength of opposing force. If set to `0`, spring will oscillate indefinitely. **Default:** `10` ### mass Mass of the moving object. Higher values will result in more lethargic movement. **Default:** `1` ### velocity Initial velocity of the object, measured in units per second. **Default:** `0` ### restDelta End animation if distance to `to` is below this value **and** speed is below `restSpeed`. When animation ends, spring gets "snapped" to `to`. **Default:** `0.01` ### restSpeed End animation if absolute speed (in units per second) drops below this value **and** delta is smaller than `restDelta`. **Default:** `0.01` ## Methods ### Action methods `spring()` returns: #### start Starts the animation and returns playback controls. Can be provided **either** a function: ```javascript spring().start(v => {}) ``` Or a named map of functions for `update` and `complete`: ```javascript spring().start({ update: v => {}, complete: () => {} }) ``` #### filter Returns a new version of the animation, that filters out any value when the provided predicate function returns `false`: ```javascript const filtered = spring().filter(v => v > 0.5) // This animation will only output values higher than 0.5: filtered.start(v => {}) ``` #### pipe Returns a new animation that will pass any output value through this series of functions: ```javascript // This animation will round output values and then double them: spring({ from: 0, to: 100 }) .pipe(Math.round, v => v * 2) .start(v => {}) ``` #### while Returns a new animation that will `complete` when the provided predicate function returns `false`: ```javascript // This animation will end when an output value is higher than 0.5: spring().while(v => v < 0.5) ``` ### Playback methods `spring().start()` starts a new animation and returns the following playback methods: #### stop Stops the animation. ## Example ================================================ FILE: packages/popmotion/docs/api/animation/timeline.md ================================================ --- title: Timeline description: Sequence a multitrack animation with full playback controls. category: animation --- # Timeline Timeline is used to script complex sequences of animation, split across independent tracks. If offers all the same playback options as a [`tween`](/api/tween). ## Import ```javascript import { timeline } from 'popmotion'; ``` ## Usage ### Create a sequence of animations `timeline` accepts an array of playhead instructions. A playhead instruction can be an animation, array of animations for parallel or staggered execution, or a timestamp. #### Animation Each animation is defined as an object. This looks a lot like a simplified `tween`, with `from`, `to`, `duration` and `ease` properties: ```javascript timeline([ { track: 'x', from: 0, to: 300, duration: 1000 } ]) ``` There's a **required** property called `track`. No two animations should overlap that share the same `track` label, and `timeline` will output every track to the function given to `start` together as an object: ```javascript timeline([ { track: 'x', from: 0, to: 300, duration: 1000 } ]).start(v => console.log(v.x)) ``` #### Sequencing If we provide a second animation, it will (by default) play **after** the first: ```javascript timeline([ { track: 'x', from: 0, to: 300, duration: 1000 }, { track: 'y', from: 0, to: 300 } ]) ``` In this example animation, the second animation will start after `1000` milliseconds, as that's when the first ends (as defined by `duration`). #### Timestamps We can, however, move the playhead from that default position. If we provide a number as the next instruction, the playhead will move to that position. In this example, the second animation will start after `500` milliseconds: ```javascript timeline([ { track: 'x', from: 0, to: 300, duration: 1000 }, 500, { track: 'y', from: 0, to: 300 } ]) ``` If we instead provide a string, we can move the playhead **relative** to the current timestamp with either `'-'` or `'+'` instructions. For instance, this time the second animation will start after `800` milliseconds: ```javascript timeline([ { track: 'x', from: 0, to: 300, duration: 1000 }, '-200', { track: 'y', from: 0, to: 300 } ]) ``` #### Parallel and stagger Animations can be played in parallel, from the same point in time, by providing them in an array. In this example, both animations provided after the first animation will play after `1000`ms: ```javascript timeline([ { track: 'x', from: 0, to: 300, duration: 1000 }, [ { track: 'x', to: 0 }, { track: 'y', from: 0, to: 300 } ] ]) ``` If we provide a number as the last item in the array, `timeline` will stagger over all the other items in the array with this delay: ```javascript timeline([ { track: 'x', from: 0, to: 300, duration: 1000 }, [ { track: 'x', to: 0 }, { track: 'y', from: 0, to: 300 }, 50 ] ]) ``` ### Value types `timeline` supports the animation of the following value types: #### Number ```javascript timeline([ { track: 'x', from: 0, to: 100 } ]) ``` #### Units **Supported**: `px`, `%`, `deg`, `vh`, and `vw` ```javascript timeline([ { track: 'x', from: '0%', to: '100%' } ]) ``` #### Colors **Supported**: RGB(A), HSL(A) and Hex ```javascript timeline([ { track: 'backgroundColor', from: '#fff', to: '#f00' } ]) ``` #### Complex Complex sequences of values, like SVG path definitions, CSS shadows and background gradients. The non-numerical portions of these values must stay in the same format in the `from` and `to` props. ```javascript timeline([ { track: 'boxShadow', from: '0px 0px 0px inset rgba(0, 0, 0, 0.2)', to: '3px 3px 10px inset rgba(0, 0, 0, 0.5)' } ]) ``` #### Objects Named objects composed of any of the above types may also be animated. ```javascript timeline([ { track: 'ball', from: { backgroundColor: '#f00', x: 0 }, to: { backgroundColor: '#fff', x: 100 } } ]) ``` #### Arrays Arrays composed of any of the above types may also be animated. ```javascript timeline([ { track: 'ball', from: [0, '10vh'], to: [0, '0vh'] } ]) ``` ## Props The following props can be passed as the second argument to timeline: ```javascript timeline(playlist, props) ``` ### duration Total duration of animation, in milliseconds. **Default:** `300` ### elapsed Duration of animation already elapsed, in milliseconds. **Default:** `0` ### ease A function that, given a progress between `0` and `1`, will return a new progress value. Used to affect the speed of playback across the duration of the animation. If `from` and `to` are set as objects or arrays, `ease` may be set with a corresponding structure to apply a unique easing for each animating value. **Default:** `easeOut` ### loop Number of times to loop animation on `complete`. Set as `Infinity` to loop forever. **Default:** 0 ### flip Number of times to flip animation on `complete`. Set as `Infinity` to flip forever. **Default:** 0 ### yoyo Number of times to reverse animation on `complete`. Set as `Infinity` to reverse forever. **Default:** 0 ## Methods ### Action methods `timeline()` returns: #### start Starts the animation and returns playback controls. Can be provided **either** a function: ```javascript timeline().start(v => {}) ``` Or a named map of functions for `update` and `complete`: ```javascript timeline().start({ update: v => {}, complete: () => {} }) ``` #### filter Returns a new version of the animation, that filters out any value when the provided predicate function returns `false`: ```javascript const filtered = timeline().filter(v => v.x > 0.5) // This animation will only output values higher than 0.5: filtered.start(v => {}) ``` #### pipe Returns a new animation that will pass any output value through this series of functions: ```javascript timeline() .pipe(v => v) .start(v => {}) ``` #### while Returns a new animation that will `complete` when the provided predicate function returns `false`: ```javascript // This animation will end when an output value is higher than 0.5: timeline().while(v => v.x < 0.5) ``` ### Playback methods `timeline().start()` starts a new animation and returns the following playback methods: #### getElapsed Returns time elapsed in milliseconds. #### getProgress Returns animation progress as a value of `0`-`1`. #### seek Seeks animation to this position as a value of `0`-`1`. ```javascript const playback = tween().start(v => {}) playback.seek(0.5) ``` #### pause Pauses playback. #### resume Resumes playback. #### reverse Reverses the direction of playback. #### stop Stops the animation. ================================================ FILE: packages/popmotion/docs/api/animation/tween.md ================================================ --- title: Tween description: Animate between two values over a set duration of time. category: animation --- # Tween Animate between two values over a set duration of time. The behaviour and feel of the animation can be affected by providing a different [easing](/api/easing) function. ## Import ```javascript import { tween } from 'popmotion'; ``` ## Usage By default, `tween` will tween from `0` to `1` over `300` milliseconds, with `easeOut` easing, and will output values to the function provided to `start`: ```javascript tween() .start(v => console.log(v)) ``` Pass props to adjust the character of the tween: ```javascript tween({ to: 200, duration: 500 }) ``` ### Value types `tween` supports the animation of the following value types: #### Number ```javascript tween({ from: 0, to: 1 }) ``` #### Units **Supported**: `px`, `%`, `deg`, `vh`, and `vw` ```javascript tween({ from: '0px', to: '100px' }) ``` #### Colors **Supported**: RGB(A), HSL(A) and Hex ```javascript tween({ from: '#fff', to: '#000' }) tween({ from: 'rgba(0, 200, 100, 1)', to: 'rgba(60, 100, 80, 0.5)' }) tween({ from: 'hsl(0, 50%, 50%)', to: 'hsl(180, 80%, 50%)' }) ``` #### Complex Complex sequences of values, like SVG path definitions, CSS shadows and background gradients. The non-numerical portions of these values must stay in the same format in the `from` and `to` props. ```javascript tween({ from: '0px 0px 0px inset rgba(0, 0, 0, 0.2)', to: '3px 3px 10px inset rgba(0, 0, 0, 0.5)' }) ``` ```javascript tween({ from: 'linear-gradient(to right, #fff, #000)', to: 'linear-gradient(to right, #333, #666)' }) ``` #### Objects Named objects composed of any of the above types may also be animated. `ease` can also be set as an object, to apply property-specific [easing](/api/easing): ```javascript tween({ from: { x: 0, background: 'hsla(125, 100, 50, 1)' }, to: { x: 100, background: 'hsla(20, 100, 60, 1)' }, ease: { x: easeOut, background: linear } }) ``` #### Arrays Arrays composed of any of the above types may also be animated. `ease` can also be set as an array, to apply property-specific [easing](/api/easing): ```javascript tween({ from: [0, '10vh', 'hsla(125, 100, 50, 1)'], to: [1, '20vh', 'hsla(20, 100, 60, 1)'], ease: [easeOut, backOut, linear] }) ``` ## Props The following properties may be passed to `tween`: ### from Start value of the animation. **Default:** `0` ### to End value of the animation. **Default:** `1` ### duration Total duration of animation, in milliseconds. **Default:** `300` ### elapsed Duration of animation already elapsed, in milliseconds. **Default:** `0` ### ease A function that, given a progress between `0` and `1`, will return a new progress value. Used to affect the speed of playback across the duration of the animation. If `from` and `to` are set as objects or arrays, `ease` may be set with a corresponding structure to apply a unique easing for each animating value. **Default:** `easeOut` ### loop Number of times to loop animation on `complete`. Set as `Infinity` to loop forever. **Default:** 0 ### flip Number of times to flip animation on `complete`. Set as `Infinity` to flip forever. **Default:** 0 ### yoyo Number of times to reverse animation on `complete`. Set as `Infinity` to reverse forever. **Default:** 0 ## Methods ### Action methods `tween()` returns: #### start Starts the animation and returns playback controls. Can be provided **either** a function: ```javascript tween().start(v => {}) ``` Or a named map of functions for `update` and `complete`: ```javascript tween().start({ update: v => {}, complete: () => {} }) ``` #### filter Returns a new version of the animation, that filters out any value when the provided predicate function returns `false`: ```javascript const filtered = tween().filter(v => v > 0.5) // This animation will only output values higher than 0.5: filtered.start(v => {}) ``` #### pipe Returns a new animation that will pass any output value through this series of functions: ```javascript // This animation will round output values and then double them: tween({ from: 0, to: 100 }) .pipe(Math.round, v => v * 2) .start(v => {}) ``` #### while Returns a new animation that will `complete` when the provided predicate function returns `false`: ```javascript // This animation will end when an output value is higher than 0.5: tween().while(v => v < 0.5) ``` ### Playback methods `tween().start()` starts a new animation and returns the following playback methods: #### getElapsed Returns time elapsed in milliseconds. #### getProgress Returns animation progress as a value of `0`-`1`. #### seek Seeks animation to this position as a value of `0`-`1`. ```javascript const playback = tween().start(v => {}) playback.seek(0.5) ``` #### pause Pauses playback. #### resume Resumes playback. #### reverse Reverses the direction of playback. #### stop Stops the animation. ## Example ================================================ FILE: packages/popmotion/docs/api/calc.md ================================================ --- title: Calculators description: Simple functions useful in UI calculations. --- Moved to [Popcorn](/popcorn) ================================================ FILE: packages/popmotion/docs/api/compositors/chain.md ================================================ --- title: Chain description: Chain a sequence of actions, move to the next when the current one completes. category: compositors --- # Chain Chain a sequence of actions, move to the next when the current one completes. ## Import ```javascript import { chain } from 'popmotion'; ``` ## Usage ```javascript chain( tween({ to: 300 }), spring({ from: 300, to: 0 }) ).start({ update: (v) => console.log(v), complete: () => console.log('All actions complete') }) ``` ## Methods ### Action methods `chain()` returns: - `filter((v: any) => boolean)`: Returns a new action that filters out values when the provided function returns `false`. - `pipe(...funcs: Array<(v) => v)`: Returns a new action that will run `update` values through this sequence of functions. - `start(update | { update, complete })`: Starts the action and returns a subscription. - `while((v: any) => boolean)`: Returns a new action that will `complete` when the provided function returns `false`. ### Subscription methods `chain().start()` returns: - `stop(): void` ================================================ FILE: packages/popmotion/docs/api/compositors/composite.md ================================================ --- title: Composite description: Control a map of actions and output to that same structure. category: compositors --- # Composite Control a named map of actions, and output to the same structure. ## Import ```javascript import { composite } from 'popmotion'; ``` ## Usage ```javascript composite({ x: tween({ from: 60, to: 400 }), y: physics({ from: 60, velocity: 300 }) }).start(({ x, y }) => {}); ``` `composite` outputs at most once per frame. ## Methods ### Action methods `composite()` returns: - `filter((v: any) => boolean)`: Returns a new action that filters out values when the provided function returns `false`. - `pipe(...funcs: Array<(v) => v)`: Returns a new action that will run `update` values through this sequence of functions. - `start(update | { update, complete })`: Starts the action and returns a subscription. - `while((v: any) => boolean)`: Returns a new action that will `complete` when the provided function returns `false`. ### Subscription methods `composite().start()` returns: - `stop(): void` **Note:** If all actions return the same API, for instance all composed actions are `tween`s, the `composite` subscription will also return a version of that API that controls all child actions. ================================================ FILE: packages/popmotion/docs/api/compositors/crossfade.md ================================================ --- title: Crossfade description: Fade between two numerical actions. category: compositors --- # Crossfade Fade between two numerical actions. ## Import ```javascript import { crossfade } from 'popmotion'; ``` ## Usage Example: blend from one tween to another: ```javascript const blendTweens = crossfade( tween({ from: 0, to: 500, elapsed: 200 }), tween({ from: 500, to: 0 }) ).start((v) => console.log(v)); tween({ duration: 100 }).start(blendTweens.setBalance); ``` ## Methods ### Action methods `crossfade()` returns: - `filter((v: any) => boolean)`: Returns a new action that filters out values when the provided function returns `false`. - `pipe(...funcs: Array<(v) => v)`: Returns a new action that will run `update` values through this sequence of functions. - `start(update | { update, complete })`: Starts the action and returns a subscription. - `while((v: any) => boolean)`: Returns a new action that will `complete` when the provided function returns `false`. ### Subscription methods `crossfade().start()` returns: - `setBalance(): this`: Sets the balance of blended output from the first action (`0`) to the second (`1`). - `stop(): void` ## Example ================================================ FILE: packages/popmotion/docs/api/compositors/delay.md ================================================ --- title: Delay description: Fires complete after the defined interval. category: compositors --- # Delay Fires `complete` after the defined interval. ## Import ```javascript import { delay } from 'popmotion'; ``` ## Usage ```javascript delay(100).start({ complete: () => console.log('complete!') }); ``` Useful for delaying actions in a `chain`. ```javascript chain( delay(100), tween({ to: 400, duration: 500 }) ); ``` ### Action methods `delay()` returns: - `start(update | { update, complete })`: Starts the action and returns a subscription. ### Subscription methods `delay().start()` returns: - `stop(): void` ================================================ FILE: packages/popmotion/docs/api/compositors/merge.md ================================================ --- title: Merge description: Combine multiple actions into one output. category: compositors --- # Merge Combine multiple actions into one output. ## Import ```javascript import { merge } from 'popmotion'; ``` ## Usage ```javascript merge( tween(), action(({ update }) => update(1)), physics({ velocity: 1000 }) ).start((v) => console.log(v)); ``` ## Methods ### Action methods `merge()` returns: - `filter((v: any) => boolean)`: Returns a new action that filters out values when the provided function returns `false`. - `pipe(...funcs: Array<(v) => v)`: Returns a new action that will run `update` values through this sequence of functions. - `start(update | { update, complete })`: Starts the action and returns a subscription. - `while((v: any) => boolean)`: Returns a new action that will `complete` when the provided function returns `false`. ### Subscription methods `merge().start()` returns: - `stop(): void` ================================================ FILE: packages/popmotion/docs/api/compositors/parallel.md ================================================ --- title: Parallel description: Control multiple actions in parallel and output as an array. category: compositors --- # Parallel Control an n-dimensional array of actions in parallel, and output as an array. ## Import ```javascript import { parallel } from 'popmotion'; ``` ## Usage ```javascript parallel( tween({ from: 40, to: 50 }), spring({ to: 500 }) ).start(([ tweenOutput, springOutput ]) => {}); ``` `parallel` outputs max once per frame. ## Methods ### Action methods `parallel()` returns: - `filter((v: any) => boolean)`: Returns a new action that filters out values when the provided function returns `false`. - `pipe(...funcs: Array<(v) => v)`: Returns a new action that will run `update` values through this sequence of functions. - `start(update | { update, complete })`: Starts the action and returns a subscription. - `while((v: any) => boolean)`: Returns a new action that will `complete` when the provided function returns `false`. ### Subscription methods `parallel().start()` returns: - `stop(): void` **Note:** If all actions return the same API, for instance all composed actions are `tween`s, the `parallel` subscription will also return a version of that API that controls all child actions. ================================================ FILE: packages/popmotion/docs/api/compositors/schedule.md ================================================ --- title: Schedule description: Use an action to control the output of another. category: compositors --- # Schedule `schedule` can use one action to control the output of another. For instance, by default `pointer` outputs only when the pointer updates. With `schedule`, you could compose it with `everyFrame` to output the latest `pointer` value every frame. ## Import ```javascript import { schedule } from 'popmotion'; ``` ## Usage ```typescript schedule(scheduler: Action, subject: Action): Action ``` ```javascript // `pointer` will output at most once every frame schedule( everyFrame(), pointer() ).start(({ x, y }) => {}); ``` ## Methods ### Action methods `schedule()` returns: - `filter((v: any) => boolean)`: Returns a new action that filters out values when the provided function returns `false`. - `pipe(...funcs: Array<(v) => v)`: Returns a new action that will run `update` values through this sequence of functions. - `start(update | { update, complete })`: Starts the action and returns a subscription. - `while((v: any) => boolean)`: Returns a new action that will `complete` when the provided function returns `false`. ### Subscription methods `schedule().start()` returns: - `stop(): void` ================================================ FILE: packages/popmotion/docs/api/compositors/stagger.md ================================================ --- title: Stagger description: Stagger the execution of a series of actions. category: compositors --- # Stagger Stagger the execution of a series of actions. ## Import ```javascript import { stagger } from 'popmotion'; ``` ## Usage ```typescript stagger(actions: Action[], interval: number | (i: number) => number): Action ``` `stagger` accepts two arguments, an array of actions and an `interval`: ```javascript stagger([ tween(), spring() ], 100) ``` When started, it outputs the values as an array. Actions that haven't yet started will output `undefined`, and you can define a default. ```javascript stagger([ tween(), spring() ], 100).start((values) => values.forEach((v = 0, i) => { console.log(v); })) ``` ## Methods ### Action methods `stagger()` returns: - `filter((v: any) => boolean)`: Returns a new action that filters out values when the provided function returns `false`. - `pipe(...funcs: Array<(v) => v)`: Returns a new action that will run `update` values through this sequence of functions. - `start(update | { update, complete })`: Starts the tween and returns a subscription. - `while((v: any) => boolean)`: Returns a new action that will `complete` when the provided function returns `false`. ### Subscription methods `stagger().start()` returns: - `stop(): void` ================================================ FILE: packages/popmotion/docs/api/easing.md ================================================ --- title: Easing description: Functions that speed or slow a tween over time. --- # Easing Easing functions make tweened motion look more natural by emulating the changes in velocity experienced by objects in the real world. They work by accepting a progress value from `0` to `1`, and returning a new one. There's a seperate Popmotion package for easing: ## Import ``` npm install @popmotion/easing ``` ```javascript import { linear } from '@popmotion/easing'; ``` ## Example ```javascript import { tween, easing } from 'popmotion'; import { easeOut } from '@popmotion/easing'; tween({ ease: easeOut }).start(); ``` ## Presets Popmotion comes with the following preset easing functions: - `cubicBezier` - `linear` - `easeIn`, `easeOut`, `easeInOut` - `circIn`, `circOut`, `circInOut` - `backIn`, `backOut`, `backInOut` - `anticipate` Try them out by editing this live example: ```marksy {` const ball = document.querySelector('#b .ball'); const ballStyler = styler(ball); tween({ to: 300, duration: 300, ease: linear }).start(ballStyler.set('x')); `} ``` ## Easing creation Popmotion provides the following functions to create your own easing functions: ### cubicBezier Creates cubic bezier curve easing function. ```javascript const { cubicBezier } = easing; const longTail = cubicBezier(0, .42, 0, 1); ``` ### reversed Accepts an easing function and returns a new one that reverses the provided one. For instance, an `easeIn` would become an `easeOut`. ```javascript const { anticipate, reversed } = easing; const anticipateOut = reversed(anticipate); ``` ### mirrored Accepts an easing function and returns a new one that mirrors the provided one. For instance, an `easeIn` would become an `easeInOut`. ```javascript const { anticipate, mirrored } = easing; const anticipateInOut = mirrored(anticipate); ``` ### createExpoIn Creates an easing function based on the exponent function `progress ** exponent`. `easeIn` is `createExpoIn(2)`. ```javascript const { createExpoIn } = easing; const strongerEaseIn = createExpoIn(3); ``` ### createBackIn Creates an easing function with an overshoot. `backIn` is `createBackIn(1.525)`. ```javascript const { createBackIn, reversed } = easing; const strongerBackOut = reversed(createBackIn(3)); ``` ### createAnticipateEasing Creates an easing function with a small anticipate and ease out. `anticipate` is `createAnticipateEasing(1.525)`. ```javascript const { createAnticipateEasing } = easing; const strongerAnticipate = createAnticipateEasing(3); ``` ================================================ FILE: packages/popmotion/docs/api/faqs.md ================================================ --- title: FAQs description: Popmotion frequently asked questions --- # FAQs ## Browser support? Popmotion supports all major browsers. For legacy IE11 support, the following polyfills are required: - [String.endsWith](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith#Polyfill) - [WeakSet](https://github.com/dy/weakset) ## Filesize? As of version 8.3.4, Popmotion is **12kb**. ================================================ FILE: packages/popmotion/docs/api/framesync.md ================================================ --- title: Framesync description: Schedule functions to run at specific steps on the render loop. --- # Framesync A tiny frame scheduler for performantly batching reads, updates and renders. Segregating actions that read and write to the DOM will avoid [layout thrashing](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing). It's also a way of ensuring order of execution across a frame. For instance, Popmotion batches updates on the `update` step, and Stylefire batches renders on the `render` step, allowing independent animation of `transform` properties. ## Install ```bash npm install framesync ``` ## Usage Framesync splits a frame into discrete `read`, `update`, `render` and `postRender` steps. ### Scheduling functions Functions can be scheduled to different parts of the render loop with `sync`. ```javascript import sync from 'framesync'; ``` It provides four functions, one for scheduling a function to run on each part of the frame: ```javascript sync.update(() => {}); ``` ### Frame data Each function is provided data about the current frame: ```javascript sync.update({ delta, timestamp }) => {}); ``` - `delta`: Time since last frame (in milliseconds) - `timestamp`: Timestamp of the current frame. This object is recycled across frames, so values should be destructured if intended to be used asynchronously. ### Keep alive We can run a function as an ongoing process by passing `true` as the second parameter: ```javascript let count = 0; sync.update(() => count++, true); ``` This will keep the process running until it's actively cancelled. ### Run immediately The third parameter, `immediate`, can be used to sync a function on the **current frame step**. By default, Framesync will schedule functions to run the next time that frame step is fired: ```javascript sync.update(({ timestamp }) => { // The following function will run on the subsequent frame: sync.update((frame) => frame.timestamp !== timestamp); }); ``` By setting `immediate` to `true`, we can add this at the end of the current step: ```javascript sync.update(({ timestamp }) => { // The following function will run on the **current** frame: sync.update( (frame) => frame.timestamp === timestamp, false, true ); }); ``` ### Cancelling Synced processes can be cancelled with the `cancelSync` function: ```javascript import sync, { cancelSync } from 'framesync'; let count = 0; const process = sync.render(() => { count++; if (count >= 10) cancelSync.render(process); }, true); ``` ================================================ FILE: packages/popmotion/docs/api/input/listen.md ================================================ --- title: Listen description: Creates a DOM event listener as an action stream. category: input --- # Listen `listen` creates DOM event listeners as an action stream. ## Import ```javascript import { listen } from 'popmotion'; ``` ## Usage To listen to an event, provide a DOM element and an event name to `listen`: ```javascript listen(document, 'mousemove') .start((e) => console.log(e)); ``` ### Multiple events Multiple events can be subscribed to by providing a space-delimited string: ```javascript listen(document, 'touchstart touchend') ``` ### Chainable actions The primary benefit of using `listen` is passing each event through the chainable actions like `filter`. For instance, here's an event listener that only fires when two or more touches are detected: ```javascript const onMultitouch = listen(document, 'touchstart') .filter(({ touches }) => touches.length > 1); onMultitouch.start((e) => ...); ``` ### Options `listen` optionally accepts a third argument of options: ```typescript type EventOpts = boolean | { capture?: boolean; passive?: boolean; once?: boolean; }; listen(element: Element, eventNames: string, opts?: EventOpts): Action ``` ## Methods ### Action methods `listen()` returns: - `filter((v: any) => boolean)`: Returns a new action that filters out values when the provided function returns `false`. - `pipe(...funcs: Array<(v) => v)`: Returns a new action that will run `update` values through this sequence of functions. - `start(update | { update, complete })`: Starts the action and returns a subscription. - `while((v: any) => boolean)`: Returns a new action that will `complete` when the provided function returns `false`. ### Subscription methods `listen().start()` returns: - `stop(): void` ================================================ FILE: packages/popmotion/docs/api/input/multitouch.md ================================================ --- title: Multitouch description: Tracks multitouch input. category: input --- # Multitouch Tracks multitouch input and outputs a list of active touches, plus scale and rotation delta between the first two touch points. For single-point input, generally [pointer](/api/pointer) is more appropriate as it provides a simple, cross-platform interface. ## Import ```javascript import { multitouch } from 'popmotion'; ``` ## Usage ```typescript multitouch({ preventDefault?: boolean = true, scale?: number, rotate?: number }) ``` ```javascript multitouch().start(({ touches, scale, rotate }) => { touches.forEach(({ x, y }) => console.log(x, y)) }); ``` `multitouch` provides: - `touches: { x: number, y: number }[]`: An array of `x`/`y` coordinates representing each active finger. - `scale: number`: The distance between the first two fingers since `start`, represented as a multiplier of the original distance. `scale` starts from `1.0`, or the initially provided `scale`. - `rotate: number`: The angle rotation of the first two fingers as a delta of the original rotation. `rotate` starts from `0.0`, or the initially provided `rotate`. ### Commonly-used properties If you often use, for instance, `rotate`, you can easily create a new action that returns only that value: ```javascript const touchRotation = (initialRotate = 0) => multitouch({ rotate: initialRotate }) .pipe(({ rotate }) => rotate); touchRotation(45).start((rotate) => console.log(rotate)); ``` ## Props - `preventDefault?: boolean = true` - `scale?: number = 1.0` - `rotate?: number = 0.0` ## Methods ### Action methods `multitouch()` returns: - `filter((v: any) => boolean)`: Returns a new action that filters out values when the provided function returns `false`. - `pipe(...funcs: Array<(v) => v)`: Returns a new action that will run `update` values through this sequence of functions. - `start(update | { update, complete })`: Starts the action and returns a subscription. - `while((v: any) => boolean)`: Returns a new action that will `complete` when the provided function returns `false`. ### Subscription methods `multitouch().start()` returns: - `stop(): void` ## Example ================================================ FILE: packages/popmotion/docs/api/input/pointer.md ================================================ --- title: Pointer description: Outputs the screen position of a single mouse or touch point. category: input --- # Pointer Outputs the screen position of a single mouse or touch point. ## Import ```javascript import { pointer } from 'popmotion'; ``` ## Usage ### Absolute position ```javascript pointer() .start(({ x, y }) => console.log(x, y)); ``` ### Relative position Provide initial `x` and `y` properties to output pointer movement **applied to this initial point**. This is useful for dragging motion. ```javascript pointer({ x: 200, y: 175 }) .start(({ x, y }) => console.log(x, y)); ``` Reactions are provided the following pointer data: - `x`, `y`: Alias of `clientX` / `clientY`, or - `clientX`, `clientY`: Position relative to the viewport. - `pageX`, `pageY`: Position relative to the document. ## Props - `preventDefault: boolean = true` - `x?: number`: If defined, apply pointer `y` movement to this number. - `y?: number`: If defined, apply pointer `y` movement to this number. ## Methods ### Action methods `pointer()` returns: - `filter((v: any) => boolean)`: Returns a new action that filters out values when the provided function returns `false`. - `pipe(...funcs: Array<(v) => v)`: Returns a new action that will run `update` values through this sequence of functions. - `start(update | { update, complete })`: Starts the action and returns a subscription. - `while((v: any) => boolean)`: Returns a new action that will `complete` when the provided function returns `false`. ### Subscription methods `pointer().start()` returns: - `stop(): void` ## Example ================================================ FILE: packages/popmotion/docs/api/reactions/multicast.md ================================================ --- title: Multicast description: A reaction that multiple reactions can subscribe to. category: reactions --- # Multicast The multicast reaction provides a reaction that many other reactions can `subscribe` to. It also helps manage actions: if a `multicast` reaction is passed to another `action`, the first `action` will automatically `stop`. ## Import ```javascript import { multicast } from 'popmotion'; ``` ## Usage ### Subscription Provide a reactions to `mulitcast.subscribe()`: ```javascript const foo = multicast(); foo.subscribe((v) => console.log('first subscriber', v)); foo.subscribe((v) => console.log('second subscriber', v)); ``` When the multicast reaction is `update`d, all listeners will fire: ```javascript foo.update(5); // first subscriber, 5 // second subscriber, 5 ``` ### Automatically stop previous action Passing the multicast reaction to a new action will stop the previous one: ```javascript tween().start(foo); spring().start(foo); // This will stop `tween` ``` ### Chain methods `multicast` can be chained in the same way as [actions](/api/action). ```javascript const double = (v) => v * 2; const px = (v) => v + 'px'; const foo = multicast().pipe(double, px); foo.update(5); // 10px ``` ### Unsubscribe `subscribe` returns an `unsubscribe` method: ```javascript const foo = multicast(); const sub = foo.subcribe(console.log); sub.unsubscribe(); ``` ## Methods ### Multicast methods `multicast()` returns: - `filter((v: any) => boolean)`: Returns a new action that filters out values when the provided function returns `false`. - `pipe(...funcs: Array<(v) => v)`: Returns a new reaction that will run `update` values through this sequence of functions. - `subscribe(update | { update, complete, error })`: Returns a subscription. - `stop()`: Stops current parent action. - `while((v: any) => boolean)`: Returns a new reaction that will `complete` when the provided function returns `false`. ### Subscription methods `multicast().subscribe()` returns: - `unsubscribe()` ================================================ FILE: packages/popmotion/docs/api/reactions/value.md ================================================ --- title: Value description: Track value state and query velocity category: reactions --- # Value A [multicast reaction](/api/multicast) that tracks the state of a number and allows velocity queries. ## Import ```javascript import { value } from 'popmotion'; ``` ## Usage ```typescript value(initialValue, onUpdate); ``` ```javascript import { tween, value } from 'popmotion'; import styler from 'stylefire'; const div = styler(document.querySelector('div')); const divX = value(0, div.set('x')); tween({ to: 500 }).start(divX); setTimeout(() => console.log(() => { physics({ velocity: divX.getVelocity() }).start(divX); // This will automatically `stop` the tween }), 150); ``` `value` can also handle objects and arrays: ```javascript const foo = value({ x: 0, y: 0 }); const bar = value([0, 0]); foo.getVelocity(); // { x: 0, y: 0 } bar.getVelocity(); // [0, 0] ``` As a multicast reaction, you can optionally `subscribe` with multiple other reactions rather than providing an initial subscription: ```javascript const foo = value(0); foo.subscribe(() => console.log('first reaction')); foo.subscribe(() => console.log('second reaction')); ``` ## Methods ### Reaction methods `value()` returns: - `get(): number`: Returns the current value state. - `getVelocity: number`: Returns the current value velocity. - `filter((v: any) => boolean)`: Returns a new action that filters out values when the provided function returns `false`. - `pipe(...funcs: Array<(v) => v)`: Returns a new reaction that will run `update` values through this sequence of functions. - `stop()`: Stops parent action. - `subscribe(update | { update, complete })`: Returns a subscription. - `while((v: any) => boolean)`: Returns a new reaction that will `complete` when the provided function returns `false`. ### Subscription methods `value().subscribe()` returns: - `unsubscribe(): void` ================================================ FILE: packages/popmotion/docs/api/transformers.md ================================================ --- title: Transformers description: Simple composable functions that take a value and return a new one. --- Moved to [Popcorn](/popcorn) ================================================ FILE: packages/popmotion/docs/blog/20170703-choosing-a-default-easing-for-popmotion.md ================================================ --- title: Choosing the correct default easing for Popmotion description: "Why I settled on easeOut for Popmotion's default easing, and why easeInOut sucks for interfaces" published: 20170703 --- # Choosing the correct default easing for Popmotion Choosing default values for a library is difficult. Should you be the benefactor/victim of a wider audience: - These defaults will be used, often unwittingly, by your consumers. - You can change them only at the risk of breaking other people's stuff. In a motion engine, these choices manifest themselves in a visual and visceral way. A duration here, a spring tightness there. This means there's responsibility to choose values intelligently, so that the little patches of the web that use your library benefit from more responsive motion. As the userbase of Popmotion is relatively small, I've always felt comfortable shirking the shackles of API stability until I finally felt like it had arrived at a the Good Place. Apologies to anyone who's lived that. Luckily, over the years of wildly oscillating GitHub activity graphs, there has been one (one) decision that I felt like I got right at the start: Making the `tween` default easing property `easeOut`. ![An example of a sweet easeOut, generated with Ceaser @ matthewlein.com/ceaser](/guides/ease-out-example.png) ## A brief history of timing Like many greying front end devs, my first exposure to UI animation was [Greensock's](https://greensock.com) then-ActionScript library, TweenMax. For a platform so disposed to garish show-off loading screen animations, Flash animations were disgusting to hand code unless you were using TweenMax. And that's largely what we used it for: showy animations. The Flash days were as over-the-top and as divorced from the user's interests as the Amiga demoscene. All you wanted to do was play your cracked version of Worms but first you had to sit through the seizure-inducing cock-prancing of some git in Norwich. At least Flash intros occasionally had a skip button. ![This, but flashing, and scrolling, and waving. Forever](/guides/amiga-demoscene.png) Greensock, in its sunrise years, had the right intentions. It did, and still to this day, has an `easeOut` as default. That's because the author understands, despite our hedonism and heathenism, that animation in UI design **should** be as a result of a user's action. `easeOut` is snappy, and as we'll see later, "snappy" is how we should respond to user actions. Flash died, and the community collectively denied any involvement. !["Honestly never heard of it. Like Gordon?"](/guides/flash-gordon.jpg) Then it was all about jQuery. jQuery was incredible for literally **everything**, which in the new world of composable asynchronous micropackages is probably why we again collectively deny any involvement, and why I'm talking about it in the past tense even while it's standing **right there**, watching us. For me, and maybe for you, the coolest thing about jQuery was `animate`. It was amazing to easily get something resembling movement in humble HTML. Even if it defaulted to `swing` easing, a tepid, muddy `easeInOut` of homeopathic strength. That easing was coupled with a default duration of `400`ms. Then that was folded into our neanderthal, performance-ignorant tweening of the CPU-choking, layout-breaking `width` and `height` properties. Lethargic animation ruled the internet. ![Feels like a good idea at the time](/guides/ease-in-out-example.png) ## The problem with `easeInOut` To arrive at an actual point, the problem with `easeInOut` is that it **feels** incredibly slow. When we [watch videos about the principles of animation](https://vimeo.com/93206523), one of the things we're taught is that objects in motion don't start in motion and they don't stop dead. They start slow, and end slow. So that sounds about right, we think, and then we stop thinking, and use `easeInOut` by choice, or a framework like jQuery makes it default, and then everyone uses it, by default, and everything feels sluggish, by default. Why? ## Why Our eyes and brain are hardwired to detect motion. We've evolved to find that motion distracting. Motion indicates that the present situation has changed, and we need to react if we'd prefer to survive. We **need** motion to be distracting. When you're scrolling through a website, and a space opens for an advert in the middle of your article, and everything jumps around, and then after a couple of seconds your crap, overpriced "4G" connection finally loads the banner ad, which animates on loop, until you scroll past it, when it then needlessly closes, god I hate Motherboard, your text jumps back up, you think of the poor bastard forced to make it, their hopes, their dreams, their broken dreams - all of those things are distracting. That motion, the things causing it: They're not imparting useful information, they're not even trying to kill you. So it's annoying. It's all shit, the user isn't involved in any of this, he's happy about the broken dreams. And yet, `easeInOut` is entirely appropriate, it is the least damaging easing to use for uninstigated animation, it is the right choice. What does it say about an easing that **this** is a good use case for it? ## It's sometimes okay, I guess A **very** tiny amount of uninstigated motion is fine in a UI, as long as it correctly indicates that **the situation has changed, and the user needs to react**. A small "new message" dot, probably. Or a notification panel, possibly. For this kind of animation, an `easeInOut` or `anticipate` is very appropriate because it gives the user a little bit of time to acclimatise to the movement, and it stops quite gently, naturally. It adheres to the principles of animation, these dots and panels now feel a little more real, your UI a little more alive. It's certainly better than things just flicking around the page without the user understanding what's changed. ## But usually it very much isn't The **vast majority** of animation in UI is, or should be, user instigated. For these animations, these interactions, the user already knows that an action has taken place. **They** started the motion, and the situation has changed, and the UI needs to react. The "in" of an `easeIn` or `easeInOut` feels sluggish in this instance because for god's sake hurry up, get on with it. The energy and momentum of `easeInOut` starts in the virtual reality and stays there. The energy and momentum `easeOut`, conversely, starts in the user's reality and ends in the interface's. It takes the user's energy, directly from their finger, through the wire or screen, straight into the button, the tooltip, the whatever, and bursts with the maximum energy at that initial moment of contact. `easeInOut` is barely bothered to get out of bed, like a stupid stroppy teenager who probably doesn't even have a finger. Probably. A user will feel that difference, even if it's subconscious, even if I have to pretend to myself that deep down they **know**, just to justify the hours of my life I've spent writing (and now writing about) animations. This is why Greensock comes with `easeOut` by default. It's why [Anime.js](http://animejs.com/) uses a zinger `elasticOut`. And it's why Popmotion uses `easeOut` too. Whereas, CSS transitions default to the `swing`-alike `ease`, which is exactly as bland as it sounds. Worse, the native Web Animations API uses `linear` easing, which is, frankly, a fucking crime. ![Blood at crime scene. Yawn.](/guides/ease-linear-example.png) ## Feel it for yourself Here's `easeInOut`. ```marksy {` const ball = document.querySelector('#a .ball'); const ballRenderer = styler(ball); tween({ to: 300, duration: 500, ease: easing.easeInOut }).start((x) => ballRenderer.set('x', x)); `} ``` Buuuuuuurrrrr. END ME. Conversely, here's an `easeOut` with the Popmotion default of `300`ms. ```marksy {` const ball = document.querySelector('#b .ball'); const ballRenderer = styler(ball); tween({ to: 300, ease: easing.easeOut }).start((x) => ballRenderer.set('x', x)); `} ``` I. Am. Blushing. Notice that when you press start on these examples, you're pressing the button yet it's the ball that's moving. Even with this physical disconnect, the first example feels like you're **telling** the ball to move, and then it moves of its own accord. Whereas the second example, you're **making** the ball move, it's using **your** energy. It makes a difference! Users can tell. It does. It makes a difference.It does.Userscantellitmakesadifferencepleasemakeadifferenceuserscantell --- [Get started with Popmotion](/learn/get-started) ================================================ FILE: packages/popmotion/docs/blog/20170704-manually-set-scroll-while-ios-momentum-scroll-bounces.md ================================================ --- title: How to manually set scrollTop during an iOS momentum bounce description: Why does existence hurt so much published: 20170804 --- # How to manually set scrollTop during an iOS momentum bounce Here's one to file under [the mobile is web is awful](/blog/20170710-mobile-web-is-awful-and-were-all-to-blame). I've been writing a Slack-esque chat that lazy-loads new messages as you scroll up. The adventure of reliably anchoring scroll position as these new messages are added to the DOM is probably a blog post in itself, but there's a specific issue on iOS that I'd like to share solution to. ## The problem Here's the code to restore a previous scroll position, (which has to be recalculated to accommodate the newly-loaded messages): ```javascript node.scrollTop = node.scrollHeight - prevScrollTop; ``` Works great in every browser, except of course the modern-day IE: ![Knew this wouldn't be so simple](/images/chat-without-hack.gif) As you can see, the scroll position stays at the top, almost as if we never manually reset the scroll position at all. The problem, as I began to understand, was that if you try and manually set a node's (and probably the viewport's) scroll position **while iOS is doing it's out-of-bounds bouncy thing**, that scroll position will be immediately overridden by the rest of the bounce animation. ## The fix To fix, we simply disable momentum scrolling on that element, restore scroll position, and then re-enable momentum scrolling: ```javascript node.style['-webkit-overflow-scrolling'] = 'auto'; node.scrollTop = node.scrollHeight - prevScrollTop; node.style['-webkit-overflow-scrolling'] = 'touch'; ``` Here's how that looks: ![Better](/images/chat-with-hack.gif) Not as graceful as I'd prefer, but looking at the timestamps the user is still left on the correct message. It also avoids funny residual momentum effects, so I presume this technique can also be used to kill scroll momentum when a pointer moves from one overflowing node to another. --- Popmotion is a 9kb JavaScript motion engine, and it'll give you way less bother than mobile Safari. Create awesome interactions with tweens, physics and input tracking. [Excite your users with Popmotion](/learn/get-started) and/or [excite us with 140 characters](https://twitter.com/popmotionjs) ================================================ FILE: packages/popmotion/docs/blog/20170710-mobile-web-is-awful-and-were-all-to-blame.md ================================================ --- title: The mobile web is awful, and everyone's to blame description: From browser vendors to front end developers, everyone is implicated in the state of the mobile web. published: 20170710 --- # The mobile web is awful, and everyone's to blame At work today, we were discussing the future direction of the website and app. A concern was raised that, without a clear mandate, the website would "continue to be a great desktop version, and a much poorer version of the app." As the front end lead, that stung. I felt like the "much" was undeserved. I took the point. It's worth imagining that the use-case of the app and website may well be different. However, the comment came as a response to a mini-moan that it'd taken me a couple hours to get an input field to stick to the bottom of the page and still be tappable in mobile Safari. A simple task made difficult. It was an indictment that I haven't yet changed perceptions about the capabilities of the mobile web. Maybe those perceptions don't deserve to be changed. I've long held that the web experience **can** be just as performant and polished as native. It's only for a lack of time and ambition that it isn't. That belief is the reason I wrote Popmotion in the first place. So I began to think, it's usually easy and quite enjoyable to make a great desktop experience. Yet developing for mobile is a constant headache. With some mobile Geekbench scores approaching laptops, the technical argument is diminishing. So why? I realised that everyone is to blame. Browser vendors, Business People, every member of the Google AMP team, you reading this, me writing this: Everyone. ## Browsers Know Best Implementing simple design features like overlays or bottom navigation on mobile browsers invariably involves trawling through blogs and forums to find that special hack that worked for that one guy that one day in June. As every developer knows, no good hack goes unpunished. Testing cross-browser serves a specific cocktail; a nostalgic blend of heart palpitations, dream-like confusion and a palpable sense of impending disappointment. It takes you right back to '99. It usually comes down to the same problem. Browsers Know Best. In their infinite wisdom, browser vendors implement novel or specific behaviour, ostensibly to aid performance and accessibility. It's a noble aim but developing for browsers is already quite chaotic enough, thankyouverymuch. The absolute last thing the internet needs is more platform-specific behaviour. Let's take a look at some examples. ### Safari A search for "Safari is the new IE" brings up enough results **refuting** that statement that there is, at the very least, a **vibe**. It also wouldn't be surprising that a team of self-isolated Apple engineers working out of their space bunker would be susceptible to Browsers Know Best (BKB). They've historically dragged their moonboots over implementing new performance-enhancing standards and APIs (like Service Workers), and what progress they do make is diminished by a lethargic release schedule intimately coupled with iOS and macOS. Which is fine on desktop because virtually nobody gives a shit about desktop Safari. On mobile, however, the demographics are different, and this holds up progress. ### Bottom navigation The behaviour that inspired this 2000-word rant. Now we live in the future, the outrageous size of phones means they can't be safely touched in their upper halves. Reflecting this shift, design guidelines are increasingly recommending putting app navigation at the bottom of the screen. Except, the greedy gits at Apple have colonised the bottom 40-ish pixels and used it as invisible magic space. Touch this, and the app navigation reappears. In exchange for this genius piece of UX, you can't put a nav there, or an action button, or a link or or a chat box or whatever. No, because we're dirty web peasants, we have to put our nav at the top, where no one can ever reach it. Unless, of course, we resort to the now-canonical hack detailed on [this god-given Eventbrite blog post](https://www.eventbrite.com/engineering/mobile-safari-why/), rightfully titled "Mobile Safari (Whyyyy?)". BKB. Or what do `100%` and `100vh` mean in a viewport that constantly changes and never seems to report the right size? BKB. Or why is it so difficult to lock page scroll when displaying an overlay, or prevent the page from scrolling to the top when you **do** manage to lock it, or or or. BKB. ### Get over here Chrome isn't blameless, either. In Chrome 56, the Chrome team set `touch` events to `{ passive: true }` by default. They [excuse themselves in this blog post](https://developers.google.com/web/updates/2017/01/scrolling-intervention). `passive: true` allows a developer to reassure the browser that the page will not be calling `e.preventDefault()`, which would block page scroll. This allows the browser to respond immediately to scroll, which at risk of singing its praises, I don't remember ever being an issue in Safari. Is this an Android thing? Either way, it's broken every instance of a draggable DOM element I've implemented, and every virtual DOM event-handler abstraction like those found in React. Thanks, Chrome team. ### A decade of this It's been ten years of this shit. Take `hover` events. A hover event **obviously** has no place firing on a touch device. Originally though, large parts of the web weren't written as an input-agnostic place. As a result, key functionality was hidden behind hover events. To unbreak the web on mobile, browsers starting emulating `hover` on the very `mousedown`-looking `touchstart`. So, we end up with very weird interactions, like having to double-tap to open menus that rely on hover. Or CSS hover styles enabling on taps. Or sticky hover states when you're merely trying to scroll the page. Or or or... If Apple had the bravery to break Flash websites, where was this bravery with hover interactions? Just break them, stick to a standard, the web will fix itself. Now, ten years later, we're still stuck with this. Scoping `:hover` effects to special capability classes and yet another esoteric behavior that's difficult to grok and embarrassing to explain to newcomers. "Well you see, in 2007..." BKB. ## Business has feelings too Whether the interests of business and user are polarised or harmonised is a debate much older than software. As a wide-eyed, UX-minded front end dev, I'm predisposed to thinking the latter. I think most people **believe** they are. Interests are hard to quantify. It's easy, then, for a business person to frame a stat in a way that makes interests look aligned. In my own experience it's been an obvious, and incidentally statistical truth, that app users are stickier and more engaged with the product than mobile web users. Somehow it then leads that it's in the user's interests to be sticky and engaged, which then leads into all manner of poor choices for the mobile web user's experience. ### Reddit For example. I have installed two Reddit apps - Alien Blue and the official app. I usually use the desktop web version. Periodically, I'll be mid-surf and it'll be easier to hit the address bar, type "sta" and spent the next five minutes browsing the preloaded Star Trek subreddit. My point is, as a user, **I'm engaged**. So why is it that I'm occasionally given the ol' mobile fuck you: ![What on earth](/images/reddit-popover.png) The USPs are mind-bending. - **50% faster:** By what metric? - **Infinite scrolling:** Infinite scrolling isn't difficult to implement, so why isn't it? Either the devs are lazy and/or incompetent, or much more likely the Business People needed another bullet point to whack on the pop-up and bag themselves a cheeky conversion. So there was a specific choice to make the web version worse. - **Autoplay gifs:** BKB. ### Facebook I had the Facebook app, once. Then, because an analyst got a whiff that Messenger had higher engagement on the stand-alone app, the Business People decided to force users to download that. Once that was downloaded, the analyst got a whiff that users were stickier when push notifications were enabled. So the Business People forced users to turn those on. So I deleted it and used the web instead. The mobile messages worked perfectly. I was quite happy not to have Facebook on my phone - despite lacking a messenger, it's half a gig. The mobile site loaded near-instantly with just a couple hundred of progressively downloaded kb. Sniff. **Sniff.** ![What on earth](/images/facebook-messenger.jpg) Now, when we click the "new messages" icon, we're treated to a splash screen telling us to download Messenger (or we were, there's presently a bug so it does literally nothing). This is purposeful degradation of a perfectly capable website and it is totally anti-user. It is wild. I am wild. ## AMP A lot has been made of the ill-comings of AMP (Accelerated Mobile Pages) recently, so I won't go too deep. My favourite is this [mic drop by The Register](https://www.theregister.co.uk/2017/05/19/open_source_insider_google_amp_bad_bad_bad/). The complaints are usually the same; it's proprietary, Google is using you for your content, homogeneous, opaque, maintaining two views. Etc. IMO the biggest problem is the flagrant abuse of power. Google has a special AMP section at the top of their search results, where AMP results get a nice picture. It's labeled "Top stories", but realistically it's just a carousel of sell-outs. Not that you can blame them. Mobile browser usage is, on some sites, far higher than desktop. Yet can you imagine if Google created a special standard for desktop sites and showcased those at the top of their results? There'd be outrage. It'd be a clear power play. But this is just the shitty mobile web, so who cares, right? Now, there's no denying that AMP pages are faster than the majority of the net and this definitely has some value for the user. AMP isn't magic though, and if a normal site is fast enough to be featured, why not include those, too? We can't complain, though. AMP is our fault, yours and mine. ## Mostly you I'll take a bit of this blame. You can take the rest. Ultimately we're in this position because the mobile web is slow. We develop sites on our fancy Macbook Pros and fiber connections and then serve those sites to low-end Android phones on Edge networks. When we're not being mindful of our user, this is our fault. Or when we include expansive libraries like Lodash or Moment in their entirety, this is our fault. Or when we serve a site without server-side rendering, this is our fault. Or when we don't split our bundles, this is our fault. Or or or. ## Do we even deserve redemption John Gruber of [Daring Fireball](https://daringfireball.net) has been banging on recently about how JavaScript was a mistake, that the web would load in under a second if it didn't exist. Well, maybe. It's an interesting thought experiment. Practically, JS does make things slower in the majority of cases. A little bit of power. With care, and `fetch`, and code sharding, it can theoretically also make things quicker. It can also be the case that a server-side rendered website with a JS payload of 100kb can belt down the network and render **quicker** than a bloated 500mb app can load into memory. There is opportunity here. The address bar is an app launcher of power user speeds, and browsers can preload pages while the user is still thinking. No context-switching, or sloshing about with Apple's horrific app search, no need to delete, to arrange. There's opportunity here, too. ## Proceed with caution Browsers Still Know Best, and keep releasing breaking changes that we have to learn to hack around. Business interests will always inexplicably collide with user interests. Google will always want control. And you'll always be lazy. Me, too, a bit. Mostly you. Let's be optimistic though. JavaScript, for all Gruber's delicious tears, isn't going anywhere. Nor should it. It's important that there's a democratic and open platform, and in the emerging world of closed devices the **web is it**. It's essential this platform remains open, and continues to become more and more capable. We're getting WebAssembly, so it'll get faster. WebGL is gaining adoption and we'll see improved AR and VR support, so it'll become more capable. Already, we can use tools like Webpack and [bundlesize](https://github.com/siddharthkp/bundlesize) to code split and maintain size budgets. Tools like [Next.js](https://github.com/zeit/next.js/) makes SSR trivial to implement. Hopefully, [Popmotion](/learn/get-started) can help create delightful, high-quality interactions. We just need to have the opportunity to create the time and space to dedicate to polish and performance, rather blindly belting along the Business People's feature treadmill. Then next time there won't be a "much" in sight. --- Popmotion is a 9kb JavaScript motion engine. Create awesome interactions with tweens, physics and input tracking. [Make the mobile web marginally better](/learn/get-started) or [follow us on Twitter](https://twitter.com/popmotionjs) ================================================ FILE: packages/popmotion/docs/blog/20170803-coding-style-dont-be-a-dick.md ================================================ --- title: "Adhering to a project's coding style: Don't be a dick" description: published: 20170803 author: Matt Perry --- # When it comes to adhering to a project's coding style, follow the golden rule. I just finished reading Ben Nadel's blog post, titled [After 3 Months Of JavaScript Linting, It's Pretty Much All Pain And No Gain](https://www.bennadel.com/blog/3312-after-3-months-of-javascript-linting-it-s-pretty-much-all-pain-and-no-gain.htm). It makes the argument that linting for maintaining a consistent style is burdensome. I couldn't disagree more. In fact, I wouldn't disagree more even if the post was titled "2 plus 2 actually equals 5", a 500 word rebuttal of a hard, rudimentary, objective fact. For both solo, pet projects, and sprawling codebases with multiple contributors, linting for maintaining style is essential. Popmotion itself is just under 3,000 lines of code, I'm the primary contributor, and linting is useful even to keep just my messy self in check. If anything, I'd argue it doesn't go far enough. The number of times I've argued with myself about whether I should declare as a `function` or an arrow function `const`, in different situations, and decided **different things**... I would to deprecate that from my life. At work, five of us sit on about 100,000 lines of code. To you, this might be relatively big, or relatively small. It's all relative. Here, linting is one of many tools (types, tests) that have helped keeps us sane, for reasons we'll explore. > [...] the goal of consistency is a complete charade. A charade? Well it isn't a charade in the sense that linting does a great job of achieving that goal. To use Ben's own examples, indentation, quotes etc are easily kept consistent. So it's a slightly self-defeating argument. One example given is `let` vs `const`. I don't understand how this is stealth-bundled in amongst actual stylistic choices. There is a right way and a wrong way to use these, they have semantic and practical meaning and a linter helps here, too. But I suppose his question is, why would you want to? The following quotes are, with varying quantities of melodrama, essentially saying **because I don't want to**. > [...] my approach to code formatting is the best. If it weren't, I wouldn't use it. And a doozy: > When I'm asked to change my style, I am - quite literally - asked to deny a fundamental Truth of my being. Me, I know best, you do it your way, I do it my way. But my way's the best way. This attitude simply isn't very team-like. One of the nice things about code consistency is simply the human interaction that leads you to your linting settings. It is team bonding. Not as bonding as oversharing at the pub, but certainly bonding, learning how to compromise and sharing ownership. When we originally set up linting at work, I insisted on semicolons. The team didn't feel strongly either way, fine, semicolons. The team liked two space indentations. Despite my preference for four, I compromised on two. I now like two. And safely refactoring Popmotion to also use two was a simple as changing - yes - my linting config. On a more practical note, this discussion settles the argument early. You don't "ugh" when you inevitably stumble across four-space indentations and you don't get passive-aggressive PRs back-and-forthing over indentation. The thought hurts my brain a little. Are there seriously projects out there where lines are written by Bobbie Two-Spaces, interleaved with heathen lines written by Four-Space Jimmy? If we remember to consider that our own stylistic **preferences** are **not** a "fundamental Truth of [our] being", we'll immediately arrive at a more collaborative, less combative mindset and thus workplace. It's fine. Nobody is dying today. The sober point comes a little later: > [...] code is so much more than indentation and quotation choices. It is true that code is an expression, even an artistic one. If the one thing we can all agree on is that indentation and quotation choices are essentially petty, then we won't feel so precious about them and start to trust that our character will come through in the actual solutions we choose and invent. We're not selling out through compromise. It doesn't make things hard, it doesn't make things painful. It is a collaborative effort and an act of imposed self-discipline. It's the act of letting go of ego, a little, and acting as a team. Because we're not all fucking "peacocks". We're just assholes, trying to do something cool. --- Popmotion is a 9kb JavaScript motion engine, and way better than this daft nonsense. Create awesome interactions with tweens, physics and input tracking. [Make things lovely](/learn/get-started) and/or [send us your stupid abuse on Twitter](https://twitter.com/popmotionjs) ================================================ FILE: packages/popmotion/docs/blog/20171210-popmotion-8-upgrade-guide.md ================================================ --- title: Popmotion 8 upgrade guide description: How to upgrade Popmotion 7 to 8 published: 20171210 author: Matt Perry --- # Popmotion 8 upgrade guide This guide will explain the breaking changes in Popmotion 8 and how to upgrade your project from version 7. ## `onUpdate` and `onComplete` The biggest change, from a practical point of view, is the way animations are initialised. Previously, a `tween` might have been defined like this: ```javascript tween({ from: 0, to: 1, onUpdate: (v) => console.log(v) }).start(); ``` Now, that `onUpdate` is instead provided to `start`: ```javascript tween({ from: 0, to: 1 }) .start((v) => console.log(v)); // This is equivalent: tween({ from: 0, to: 1 }) .start({ update: (v) => console.log(v) }); ``` Likewise, `onComplete` also provided to `start`: ```javascript tween({ from: 0, to: 1 }).start({ update: (v) => console.log(v), complete: () => console.log('complete!') }) ``` ## Immutable Each animation, once defined, is immutable. Once `start` is called, a **new instance** of that animation is created and executed. Which means you can define an animation once: ```javascript const foo = tween({ from: 0, to: 1 }); ``` And use that definition to power multiple animations: ```javascript foo.start((v) => console.log('first animation', v)); foo.start((v) => console.log('second animation', v)); ``` ## Playback controls This means that `stop`, and other playback controls, aren't returned by `tween`, they're returned from `start`: ```javascript const myTween = foo.start(console.log); myTween.stop(); ``` ## `transform` property Previously, there was a `transform` property available to actions. It accepted a pure function that accepted a value and returned a new one. In Popmotion, we refer to these as [transformers](/api/transformers). As the API has evolved, its skewed towards using these pure functions to define functionality. Documents increasingly included references to the `pipe` transformer, which composes a new transformer from multiple others. In Popmotion 8, `pipe` has been promoted to an action method. When used, it returns **a new version of that action** that pipes all `update` values through the functions given to `pipe`: ```javascript const double = (v) => v * 2; const px = (v) => v + 'px'; const foo = tween({ from: 0, to: 1 }); const bar = foo.pipe(double, px); foo.start(console.log); // Ends with 1 bar.start(console.log); // Ends with '2px' ``` ## Chainable `pipe` isn't the only chainable functions available to actions. There's also `while` and `filter`, and an API to add new functions will be available in the near future. Here's a `listen` action, which is our [DOM event listener action](/api/listen), that only fires when it detects more than two `touches`: ```javascript listen(document, 'touchstart') .filter(({ touches }) => touches.length > 1) .start(({ touches }) => { console.log(touches.length); // more than 1 }); ``` ## `value.set` `value` is now a **reaction**. Which means you can still pass one to an action like this: ```javascript const foo = value(0, console.log); spring({ to: 300 }).start(foo); ``` As `start` accepts reactions either as a function or an object with an `update` function, `value.set()` becomes `value.update()`. ## `getVelocity` Previously, every action has a `getVelocity` method. In Popmotion 8 I'm attempting to reduce statefulness and move to reactive streams. `value` is now the only stateful function to offer a `getVelocity` function. It's a special type of reaction that maintains state. ```javascript const ball = document.querySelector('#ball'); const ballX = value(0, styler(ball).set('x')); spring({ to: 400, stiffness: 500 }).start(ballX); setTimeout(() => console.log(ballX.getVelocity()), 200); ``` ## Every animation can handle color The `colorTween` animation has been replaced by the capability for every animation to handle colors. Even `spring` and `physics`! So this: ```javascript colorTween({ from: '#fff', to: '#000' }) ``` Is now this: ```javascript tween({ from: '#fff', to: '#000' }) ``` ## Every animation can handle arrays and objects In Popmotion 7, animation of arrays and objects was achieved via the `parallel` and `composite` actions respectively. These functions still exist in order to compose different animations. But when the animations are the same, this syntax has been greatly simplified: ```javascript physics({ from: 0, velocity: { x: 100, y: 200 } }).start(console.log) // Receives { x, y } ``` `value` can now also be an n-dimensional array or object too, and `value.getVelocity` will return velocities in the defined format. ## `stagger` As the `update` method is bound to an animation only when `start` is called, `stagger` now accepts functions that can **optionally** return a started action. So this: ```javascript stagger([ tween({ onUpdate: (v) => console.log('1st', v) }), tween({ onUpdate: (v) => console.log('2nd', v) }) ], 50).start(); ``` Becomes: ```javascript stagger([ () => tween().start((v) => console.log('1st', v)), () => tween().start((v) => console.log('2nd', v)) ], 50).start(); ``` This also means that `stagger` can iterate over any function, not just animations. ## `touch` is now `multitouch` The `touch` action has been renamed `multitouch`. Previously, it provided `onUpdate` with an array of touches. Now, `touches` is a property that sits alongside `scale` and `rotate` properties: ```javascript multitouch().start(({ touches, scale, rotate }) => {}); ``` ## And the rest - `physics` `spring` property is now `springStrength`. - `physics` `autoStopSpeed` property is now `restSpeed`. - `spring` `restDisplacement` property is now `restDelta`. - `flow` alias for `pipe` has been removed. - `add`, `subtract`, `divide`, `multiply`, `conditional`, `alpha`, `percent`, `degrees`, `px`, `rgbUnit`, `rgba`, `hex`, `color`, `hsla` transformers have been removed. Value type transformers like `hex` can still be accessed from the [valueTypes](https://github.com/Popmotion/popmotion/tree/master/packages/style-value-types). - Render loop function must be imported separately from [Framesync](https://github.com/Popmotion/framesync) ================================================ FILE: packages/popmotion/docs/blog/20171211-introducing-popmotion-8.md ================================================ --- title: Introducing Popmotion 8 description: Popmotion 8 is a functional, reactive approach to animation. published: 20171211 author: Matt Perry --- # Introducing Popmotion 8 Popmotion 8 is a functional, reactive animation library. It's the result of over three months work, the result of wanting to add a humble `pipe` method and instead tumbling down the rabbit hole. It introduces new animations: `decay`, `everyFrame`, `keyframes` and `timeline`. It makes input dragging a breeze with our revamped `pointer` and `multitouch` actions. Crucially, there's a new streamlined, reactive API. This new API reflects Popmotion's gradual shift towards functional programming and adopts it as a core part of the design philosophy. The result is a small, flexible and composable library that I hope you'll find fun to use. Let's take a look at what's new. ## Animations ### `decay` `decay` models a form of exponential deceleration to create motion like momentum scrolling on smartphones. Just provide it `velocity` and `from` properties: ```javascript decay({ velocity: 1000, from: 0 }).start((v) => ...) ``` It also exposes a `modifyTarget` option that provides a functional API for adding features like snap-to-grid. ### `everyFrame` `everyFrame`, as you might imagine, fires once per frame. It provides the amount of time since it started: ```javascript everyFrame().start((timeSinceStart) => ...); ``` It's the fundamental driver of every Popmotion animation. We've exposed it because we reckon this kind low-level action will be useful in animations, like porting After Effects expressions: ### `keyframes` `keyframes` transitions through a number of states over a set duration of time. Its API is inspired by Apple's `CAKeyframeAnimation`, which makes it trivial to resize the overall animation: ```javascript keyframes({ values: [ { x: 0, y: 0 }, { x: 100, y: 0 }, { x: 100, y: 100 } ], times: [0, 0.3, 1], duration: 1000 }) ``` ### `timeline` `timeline` is used to orchestrate more complicated patterns of tweens. It supports absolute and relative timestamps, as well as parallel and staggered motion. The output action has all the same playback methods as `tween`, making it fully scrubbable. In most animation libraries, the timeline function is a bit of a black box that we chuck setters or selectors into. As Popmotion is a reactive library, we label each segment with a `track` property, and that then the latest state gets output as an object with those labels: ```javascript timeline([ { track: 'shade', from: 0, to: 1 }, '-100', { track: 'modal', from: { y: -100, opacity: 0 }, to: { y: 0, opacity: 1 } } ]).start(({ shade, modal }) => ...) ``` This means we can pass the output of timeline through the chainable methods `pipe`, `while` and `filter` (more on those later). As timelines become more complicated, maintaining the link between labels and setters can become increasingly difficult, but the trade-off is a timeline that is immutable, composable, pure, and testable. We're also experimenting with functions that can take a timeline definition and automatically generate the output reaction. ## Input ### `pointer` The `pointer` action has been in Popmotion since before it was called Popmotion. But, its API has always been confused making simple tasks like dragging more convoluted than they needed to be. In Popmotion 7, `pointer` output its absolute position and it was up to a second action, `trackOffset`, to get the **movement** of `pointer` relative to another point (say, a DOM element). Popmotion 8 scraps the `trackOffset` action entirely. Now, `pointer` will output its absolute position when used like this: ```javascript pointer().start(({ x, y }) => ...) ``` (Also notice how we no longer need to provide `pointer` with a `MouseEvent` or `TouchEvent`.) To output that pointer's movement **applied to another point**, we simply need to provide that point as our initial argument: ```javascript pointer({ x: 0, y: 0 }).start(({ x, y }) => ...) ``` ### `multitouch` Thanks to the efforts of [Mars](https://twitter.com/marsi), Popmotion has had multitouch support since version 6. There are two major changes coming in 8. The first and most apparent is we've changed the name of the action from `touches` to `multitouch` to highlight the second change: It no longer outputs just an array of touches. `touches` is joined by `scale` and `rotate` properties: ```javascript multitouch().start(({ touches, scale, rotate }) => ...) ``` Like `pointer`, `multitouch` accepts `scale` and `rotate` arguments and, if defined, will output the **change** in those properties as applied to the given values. ## Reactive API At the heart of all these new features is a change in the core building block of all animations, the **action**. The `action` function is used to create streams of reactive values. Think of it as an animation-focused, tiny alternative to Rx Observables. It looks like this: ```javascript action(({ update, complete, error }) => {}) ``` For a practical example of how `action` works, let's define an function called `just`. It'll return an action that, when started, will fire `update` with the provided value and then `complete`: ```javascript const just = (v) => action(({ update, complete }) => { update(v); complete(); }); just(2).start(console.log); // 2 ``` Every time we `start` an action, its initialisation function runs anew, creating a new instance of the action. Because all animations are actions, we can define an animation once: ```javascript const moveRight = tween({ to: 300 }); ``` And use it multiple times: ```javascript moveRight.start(console.log); // 0 - 300 moveRight.start(console.log); // 0 - 300 ``` Actions offer a number of chainable methods (currently `filter`, `pipe` and `while`, with an API for adding more on the way). Each returns a brand new version of the action with the added functionality: ```javascript const double = v => v * 2; const px = v => v + 'px'; const justTwo = just(2); justTwo.start(console.log); // 2 justTwo .pipe(double, px) .start(console.log); // '4px' ``` In the last 6 months Popmotion has spun out [Framesync](/api/framesync) and [Stylefire](/stylefire) as standalone libraries. It's helped me take greater care and consideration over where to draw the lines between the role and responsibilities of various parts of the library and enables people to use the isolated functionality in their own code or libraries. I can imagine a near-future where actions are spun out as a tiny reactive library, where people can start dabbling with reactive programming outside of animation without the full payload of something the size of Rx. ## And the rest... ### File size and individual imports Popmotion has always tried to respect your bytes. One of the reasons I wrote it in the first place was a dissatisfaction with the size of existing libraries in comparison to the benefits they provided. Popmotion 8 is a little bigger than 7 (11.5kb vs 10kb). Though, as such a radical rewrite with so many new features, I think there are efficiencies to be made over the coming months. In the meantime, everything in Popmotion is **now available as an individual import**. Which means, if you only want to use (for instance) `spring`, you can import and use that for roughly 2kb. ### Colours and multi-prop animations In Popmotion 7, we exposed the ability to animate colours with `colorTween`. Multi-property animations could be composed with the `composite` and `parallel` compositors. This has all been streamlined in 8. Every animation can now animate colors, objects, arrays (and objects and arrays of colors!). It's as simple as this: ```javascript tween({ from: { x: 0, color: '#f00' }, to: { x: 100, color: '#fff' } }).start(({ x, color }) => ...) ``` ## Conclusion That's most of what's new in Popmotion 8. Existing users should check out our [upgrade guide](/blog/20171210-popmotion-8-upgrade-guide) to handle breaking changes. After three years of development I'm **finally** happy with the API. I think the reactive model works incredibly well for neatly and declaratively handling streams of values and fits perfectly with the functional approach I was already moving towards. With these solid foundations in place, the next logical step feels like exploring a way of describing the properties of UI elements and having motion and interactions derive naturally from that. Like a physically-based rendering for motion. Once more down the rabbit hole. --- [Get started with Popmotion](/learn/get-started) ================================================ FILE: packages/popmotion/docs/blog/20180104-when-ios-throttles-requestanimationframe.md ================================================ --- title: When iOS throttles requestAnimationFrame to 30fps description: In specific situations, iOS throttles rAF to 30fps. Here's when, and why it's wrong. published: 20180104 author: Matt Perry --- # When iOS throttles requestAnimationFrame to 30fps > **TL;DR:** iOS throttles `requestAnimationFrame` to 30fps in cross-origin iframes and low power mode. Yesterday I was looking at the examples on the [Popmotion homepage](https://popmotion.io) with my iPhone. They looked odd, clearly suffering a sluggish stutter, the cruel affliction of (**gasp**) 30 frames per second. Here's an artificially-throttled example: I'd seen this illness once before. If you're looking at the example above on an iOS browser, check out this unthrottled example: Looks the same, right? To other readers, this second example runs at a silky 60fps. Animation as the gods intended. I found that it was only affecting animations running on CodePen. The reason was initially unclear. I assumed that CodePen itself was doing **something** weird in the background that caused the stuttering. They helpfully pointed me to the Debug View, which runs the pen without the iframe. It worked. 60fps on iOS. It wasn't CodePen after all. My first discovery: ## iOS throttles `requestAnimationFrame` in iframes Specifically, it turns out, **cross-origin iframes**. Which is every CodePen embed, even the ones hosted on CodePen's own site (as the live panel is served on a subdomain). As detailed in this [WebKit changelog](https://trac.webkit.org/changeset/215070/webkit), the throttling is cleared once a user interacts with the iframe. Try it for yourself. If you're on iOS, take a look at the tween animation again: This time, tap anywhere within the frame and watch as the 30fps eye-thrashing is replaced by the soft caress of 60fps. ### But... why? I haven't found a stated reason for this behaviour. But that doesn't stop semi-educated speculation. Iframes are commonly used for advertising. Adverts are fairly liberal with your CPU cycles. So I imagine the throttling is an attempt to prevent adverts from eating your battery. The performance cap is cleared once the user interacts with the iframe and indicates that the advert (or other embedded content) isn't unwelcome. An alternative solution might be to throttle iframes that lie outside the current viewport (maybe pause execution entirely), and unthrottle when they enter. This current solution feels heavy-handed. ### The remaining mystery Back to the present, this didn't explain why the examples on the homepage were stuttering. They're part of the page itself, no iframes. As the myriad of known and unknown potential causes flashed before my eyes, a familiar feeling of nausea set in. Standing on the precipice of a rabbit hole of unknown depth, I began the ritual. Opened my MacBook. Chrome. Performance tab. Throttled CPU... 60fps. Checked my partner's phone, same OS, same make and model... 60fps. Googled for bugs with iOS 11.2.1 and 30fps rAF... Nothing. What the fuuuuuu... I stared at my phone, dumbfounded. Maybe I needed to reset my phone. Maybe it was because my phone was the wrong colour. Maybe I simply needed to be kinder to Siri. Luckily, I saw it, and I knew I'd found the culprit. The nausea lifted. Relief settled: ![Take me from this earth](/images/low-power-mode.png) Low power mode. Though part of me wouldn't believe it until I toggled it on and off and saw the difference with my own eyes, it made sense. My second discovery: ## iOS throttles `requestAnimationFrame` in low power mode As detailed in this [WebKit bug](https://bugs.webkit.org/show_bug.cgi?id=168837), iOS throttles `requestAnimationFrame` to 30fps whilst low power mode is active. I have mixed feelings about this. First, there's no doubt that it's very clever. It is, in the short-term, a boon for users. In the long-term, I'm not so sure. I've already covered in detail why the [mobile web is a dreadful platform](https://popmotion.io/blog/20170710-mobile-web-is-awful-and-were-all-to-blame/) to develop for. This is yet another unpredictable tributary feeding into a much larger river of unpredictable nonsense that we have to put up with. The salmon will get tired. It's a self-perpetuating cycle. Horizons, ambitions and standards will drop. Further emboldening mobile browser vendors to push for clever solutions to improve user experience and degrade the developer one. On iOS, the app developers themselves are given the responsibility to [respond to low power mode](https://developer.apple.com/library/content/documentation/Performance/Conceptual/EnergyGuide-iOS/LowPowerMode.html), whereas WebKit still hasn't implemented the [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API). It's a familiar story, and a shame. A clever, battery-saving shame. --- If you still have any energy left in your tired little salmon fins, make the mobile web an app-quality kinda place with [Popmotion](https://popmotion.io). Alternatively, throw your opinions in my stupid salmon face [on Twitter](https://twitter.com/popmotionjs) ================================================ FILE: packages/popmotion/docs/blog/20180521-pose-2-migration-guide.md ================================================ --- title: "What's new in Pose 2.0" description: A quick overview of what's new in Pose and React Pose 2.0, and how to migrate published: 20180529 --- # What's new in Pose 2.0? Pose and React Pose 2.0 are out now! They introduce a simple new API for `transition`, so there's now no requirement to import Popmotion separately for custom transitions. `transition` can also now accept a named map of values, meaning no more having to check `key` to return different animations. As we all know all-too-well, major version increases also mean one thing: All your shit's broke. Just kidding. Only some of your shit's broke. Let's take a look. ## Transition definitions Until now, creating custom transitions involved importing Popmotion animations and manually passing along Pose-generated values like `from`, `to` and `velocity`: ```javascript import { tween, easing } from 'popmotion'; const config = { visible: { opacity: 0, transition: (props) => tween({ ...props, duration: 1000, ease: easing.linear }) } }; ``` Now, we can simply define `transition` as an object of animation props: ```javascript const config = { visible: { opacity: 0, transition: { duration: 1000, ease: 'linear' } } }; ``` Notice that we don't need to import easing functions separately, as Pose provides a collection of named easings. We can even automatically define a cubic bezier easing by providing an array: ```javascript ease: [.01, .64, .99, .56] ``` By default, a transition defined like this will be a [tween](/api/tween). But Pose supports Popmotion's [spring](/api/spring), [keyframes](/api/keyframes), [physics](/api/physics) and [decay](/api/decay) animations too. We can switch to one of these by setting `transition.type`: ```javascript const config = { dragEnd: { x: 0, transition: { type: 'spring', stiffness: 1000 } } } ``` Or dynamically set them as a function: ```javascript const config = { dragEnd: { x: 0, transition: ({ velocity }) => velocity < 0 ? { type: 'spring', stiffness: 1000 } : { type: 'physics', to: -1000 } } } ``` There's also some new properties supported by all animations: `round`, `min`, `max` and `delay`. Take a look at the [custom transition tutorial for full details](/pose/learn/custom-transitions). Of course, for animations that aren't yet possible with the new transition API, these functions can still return Popmotion animations just as before. ## Named transition maps Previously, to return different animations for different properties, we had to check the provided `key` property: ```javascript const config = { hidden: { x: -100, opacity: 0, transition: ({ key, ...props }) => (key === 'x') ? spring({ ...props, stiffness: 1000 }) : tween({ ...props, duration: 100 }) } } ``` Instead, we can now **optionally** define `transition` as a named map, with a `default` prop for a catch-all transition: ```javascript const config = { hidden: { x: -100, opacity: 0, transition: { x: { type: 'spring', stiffness: 1000 }, default: { duration: 100 } } } } ``` These named transitions can also be functions that return dynamic transition definitions or Popmotion animations. Transition definitions and named transition maps will both be coming to React Native Pose in the near future. ## Migration guide Of course, with a major version change there's always a breaking change or two. We've started flagging these in advance with deprecation warnings, where possible, so hopefully the impact will be minimal. Here's what's changed: ### `transitionProps` is now `props` In a bit of bikeshedding that should, nonetheless, make the API a little easier to explain, `transitionProps` is now `props`. When they were first introduced, `transitionProps` were used to dynamically generate `transition`s. Now, they can be used to dynamically set any pose prop, hence the change in terminology: ```javascript const config = { open: { x: ({ x }) => x }, props: { x: 0 } }; // Pose const poser = pose(config); // React Pose const Posed = posed.div(config); () => ``` This has also led to the renaming of `setTransitionProps` to `setProps` for the vanilla Pose users in the audience. ### FLIP is now opt-in Previously, Pose would detect if you were animating a layout property like `width` or `top` and automatically convert that to a FLIP animation. Although FLIP animations are more performant, if not handled with care they can cause unexpected visual artifacts. We also don't currently have a good strategy for animating to `0` (which makes the "invert" stage of FLIP decidedly tricky - what's the inverse of 0?) This was one of the most commonly-occurring points of confusion with the API, so I've made it opt-in with `flip: true`: ```javascript const config = { open: { width: '100vw', height: '100vh', flip: true }, close: { width: '100px', height: '100px', flip: true } } ``` The good news is that this shouldn't break any of your code. If left unchanged, Pose will simply animate the layout property, **unless**, as above, you're animating between two different unit types which Pose doesn't currently support (PRs welcome!) ================================================ FILE: packages/popmotion/docs/blog/20180820-introducing-pose-3.md ================================================ --- title: Introducing Pose 3.0 description: A look at the latest Pose features, changes, and a hint of what's next published: 20180820 --- # Introducing Pose 3.0 [Pose 3.0](/pose) is officially out, and with it, some new features! Let's take a look at what's new, what's changed, and what's next. ## New UI events Pose is designed to simplify **both** animations and interactions. But so far, it's only supported dragging. Now, elements can animate on `hover`, `focus` and `press` too, with code as simple as this: ```javascript const config = { hoverable: true, init: { scale: 1 }, hover: { scale: 1.2 } }; ``` As usual, these poses flow through the pose tree. So all children of these posed components will also animate receive these poses, allowing simple coordination of animations across the DOM. It also handles some usually finicky logic, which we solve with our new pose resolution logic. ## Pose resolution Imagine an element that is both hoverable and draggable. This sequence of events often happens: 1. User hovers: hover animation plays 2. User drags: drag animation plays 3. User hovers out while still dragging: hover out animation plays 4. User hovers: hover animation plays 5. User stops dragging: ??? There's two problems with this flow. The first is that if a user hovers out while still dragging (usually because the UI is updated slower than the pointer), **no** animation should play. The dragging animation should take precedence. Second, when dragging stops we should know that the user is still hovering and animate to that state, rather than to an "at rest" state. Pose handles this for you: This is because values can now be in multiple poses at once. Instead of this: ```javascript 'drag' ``` We have this: ```javascript ['drag', 'hover', 'init'] ``` Because vanilla and React Pose are configured to prioritise the `'drag'` pose over `'hover'`, if hover is removed and then re-added, it'll always be added **after** drag. So the value will continue to resolve to `'drag'`. This also means that when both drag and hover states are removed, the value reverts to whichever pose it was in previously. In this case, that's the new special `'init'` pose (which does what it says on the tin). This is in contrast to the more state-machine-like approach previously taken, which expected a constant stream of a new poses. ## Breaking changes Of course, being a major release, there's breaking changes. This release, it's precautionary. Because we're resolving poses in a new way, it's possible that some of your animations work a little differently. Unlikely, but possible. Also, we've changed the special `dragging` pose to just `drag`. This was undocumented, so again this is precautionary, but it's possible I might have let it slip in a CodePen. You can learn how to animate with it, and all the other new event poses, in our new [UI events tutorial](/pose/learn/ui-events/). ## What's next The flurry of activity will continue over the coming weeks and months. We've just opened our [new Patreon and Open Collectives](/support/) which will help support Popmotion's continued development. We've also just released the long-requested [Pose examples](/pose/examples/) page, which we're going to be filling up with all-new examples. On the [features board](https://github.com/Popmotion/popmotion/projects/1) the most requested card is the ability to animate between different value types, and to `auto`. There'll also be tutorials, both articles and video, for commonly-requested implementations like carousels and route transitions. ================================================ FILE: packages/popmotion/docs/blog/20180829-pose-3-1-animate-between-anything.md ================================================ --- title: "Pose 3.1: Animate between any unit type" description: A look at the latest Pose features, changes, and a hint of what's next published: 20180830 author: mattperry --- # Pose 3.1: Animate between any unit type This week we released [Pose 3.1](/pose), Animate Between Anything. It's a much-requested feature that enables you to animate `x`, `y`, `width`, and `height` between any unit type. Previously this capability was only available via FLIP (by setting `flip: true`), but in some cases you may want to be animating `width` and `height` directly. And of course FLIP doesn't work with `x` and `y` transforms. Now, you can animate `'100vh'` to `'50%'`, or `'calc(50vw - 50%)'` to `0`. You can also animate `width` and `height` to and from `'auto'`. It works with any valid CSS style, and the animations themselves happen in pixels so spring animations feel consistent between every transition. When the animation finishes, the defined style is applied. So your designs will continue to be responsive. ## Still to come This will probably be the last big new feature for a few weeks. I'll be working on more tutorials and examples, as well as a bug burn down. I'll also be taking another look at React Native Pose. Compared to Pose, it's still fairly limited. This owes in part to the restrictions in the underlying React Animated library. In part, its the difficulty in animating colors, and the ease with which an animation can be delegated to the JavaScript thread. I'll be looking into potentially porting it to [Reanimated](https://github.com/kmagiera/react-native-reanimated) to enable native-driven gestures, and to try and bring the library to feature-party with Pose. ================================================ FILE: packages/popmotion/docs/blog/20180904-introducing-pose-for-vue.md ================================================ --- title: Introducing Pose for Vue description: An introduction to the declarative animation library for Vue published: 20180409 author: mattperry --- # Introducing Pose for Vue For as long as there's been a flavour of Pose for React, there's been requests for a Pose for Vue. Over the last week I've been tinkering with Vue and figuring out the best way to bring Pose's declarative animations and interactions to this fast-growing framework. Today, I'm pleased to announce the first release of [Pose for Vue](/pose)! Let's take a look at this new library. ## Posed components As with Pose for React, the core of the library is the `posed` object. ```javascript import posed from 'vue-pose'; ``` It's used for making posed HTML and SVG components, components that will magically animate from a single prop change. No `transition` needed! ```javascript export default { components: { Box: posed.div({ visible: { opacity: 1 }, hidden: { opacity: 0 } }) }, template: `` }; ``` Pose will automatically figure out an animation based on the types of prop you're animating. But, for those of you who haven't used Pose before, it's worth mentioning that you can have full control over transitions, and Pose offers full access to [Popmotion Pure's](/pure) tween, spring, decay, physics and keyframes animations. ## Interactions Of course, all the normal Pose features are here. Making a component respond to pointer interactions is a matter of a couple of props: ```javascript export default { components: { Box: posed.div({ draggable: true, hoverable: true, init: { scale: 1 }, hover: { scale: 1.2 }, drag: { scale: 1.1 } }) }, template: `` }; ``` ## Animating children One of Pose's most popular features is how simple it becomes to orchestrate animations throughout the DOM. Pose for Vue is no different. Poses set on a parent component flow through the DOM automatically, so any children components with the same poses defined will animate too. Properties like `delayChildren` and `staggerChildren` allow total control over when child animations get fired. ```javascript export default { components: { Sidebar: posed.ul({ visible: { x: 0, beforeChildren: true, staggerChildren: 50 }, hidden: { x: '-100%', afterChildren: true } }), Item: posed.li({ visible: { opacity: 1, y: 0 }, hidden: { opacity: 0, y: 20 } }) }, template: ` ` }; ``` ## Enter/exit transitions There's even a special version of Vue's `transition` component. Called `PoseTransition`, it automatically creates opacity animations for children DOM elements, but posed components can be used to define custom transitions. ```javascript export default { components: { PoseTransition, Modal: posed.div({ enter: { opacity: 1, y: 0 }, exit: { opacity: 0, y: 100 } }) }, template: ` ` } ``` These "enter" and "exit" poses flow through children as with other posed components, so you can use this to orchestrate some complex animations. ## And the rest That's to say nothing of the powerful [passive values](/pose/learn/vue-passive) or [FLIP](/pose/learn/vue-flip) features, that are both here as expected. ## What's missing Currently, Pose for Vue doesn't have `transition-group` support. Vue's in-built `transition-group` is excellent, with all the FLIP-powered goodness that Pose for React offers with the `PoseGroup` component. Unfortunately, `transition-group` doesn't offer JavaScript hooks for the move animations. Which means it would take serious time investment to offer the same feature with Pose for Vue. If Pose for Vue proves popular, I'll be able to take another look in the coming weeks. ## What's next Over the coming weeks we'll be doubling-down on making Popmotion libraries easier to contribute to and more robust. That means introducing [CircleCI](https://circleci.com/), and improving test coverage around the DOM Pose flavours. Until then, [get started with Pose for Vue](/pose/learn/vue-get-started)! ================================================ FILE: packages/popmotion/docs/blog/20181101-react-pose-4-forwardref.md ================================================ --- title: "Pose for React 4.0: Custom components made simple(r)" description: Pose previously used special properties to animate custom components. 4.0 simplifies this by enforcing forwardRef. published: 20181101 author: mattperry --- # Pose for React 4.0: Custom components made simple(r) Today, we release Pose for React 4.0. As per Pose tradition, the major release brings with it no big-splash features or complicated migration, just one simple breaking change that should clarify the API going forward. ## What's the problem? Pose for React was written in a time before `React.forwardRef()`. To enable user access to the internal `ref` property, it took the `innerRef` naming convention from [Styled Components](https://www.styled-components.com/). This extra API is a small point of confusion. But it also meant that to animate a custom component, Pose needed developers to forward either an `innerRef` (for SC and similar) or `hostRef` (proposed but unrealised React standard) to the custom component's inner DOM element. And when `forwardRef` was released, we added support for that, too. This all led to a messy story around animating custom components and the extra ref props flagged errors in stricter linting and typing environments. As CSS-in-JS libraries like SC and upcoming Emotion 10 support `forwardRef`, it's time to clean this up in Pose too. From today: 1. Posed components will accept `ref` to grab inner DOM nodes (`innerRef` works but is **deprecated** and will throw a warning in development mode). 2. `forwardRef` is the **single** canonical way to animate custom components. ## What do I need to do? ### Fetching DOM refs `innerRef` will continue working until `5.0`. But now when you want to grab a posed component's DOM reference from outside, you should use `ref`: ```javascript const Button = posed.button(config); class MyComponent extends React.Component { setRef = ref => this.ref = ref; render() { return ` }; ``` ================================================ FILE: packages/popmotion-pose/docs/examples/vue/vue-flip.md ================================================ --- title: FLIP description: An live example of making FLIP animations in Pose for Vue category: vue --- # FLIP The FLIP technique, [fully explained here](https://aerotwist.com/blog/flip-your-animations/), is a way of animating expensive, layout-breaking animations like `width` and `top` by using quick transforms. In Pose for Vue, performing a FLIP animation is as simple as providing a pose the `flip: true` property. ```javascript Box: posed.div({ fullscreen: { width: '100vw', height: '100vh', transition: tween, flip: true }, thumbnail: { width: 100, height: 100, transition: tween, flip: true } }) ``` Click on this box to see it in action: ================================================ FILE: packages/popmotion-pose/docs/examples/vue/vue-magic-animations.md ================================================ --- title: Magic animations description: An live example of magic animations in Pose for Vue. category: vue --- # Magic animations Even with no `transition` defined, Pose for Vue will automatically create snappy and responsive transitions between to states. ```javascript posed.div({ left: { x: -100 }, right: { x: 100 } }) ``` ================================================ FILE: packages/popmotion-pose/docs/examples/vue/vue-medium-style-image-zoom.md ================================================ --- title: Medium-style image zoom description: Pose for Vue makes it easy to write Medium-style image zoom. category: vue --- # Medium-style image zoom By leveraging Pose's FLIP capabilities, and the `applyAtStart` and `applyAtEnd` properties, it becomes trivial to implement [Medium](https://medium.com)-style image zooming. ```javascript const Frame = posed.div({ zoomedOut: { applyAtEnd: { display: 'none' }, opacity: 0 }, zoomedIn: { applyAtStart: { display: 'block' }, opacity: 1 } }); const Image = posed.img({ zoomedOut: { position: 'static', width: 'auto', height: 'auto', flip: true }, zoomedIn: { position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, flip: true } }); ``` ================================================ FILE: packages/popmotion-pose/docs/examples/vue/vue-passive-values.md ================================================ --- title: Passive values description: An live example of making passive values in Pose for Vue category: vue --- # Passive values A passive value is a value that isn't animated directly. It only changes when another value changes. ```javascript posed.div({ draggable: 'x', passive: { opacity: ['x', interpolate( [-200, -100, 100, 200], [0, 1, 1, 0] )] } }) ``` ================================================ FILE: packages/popmotion-pose/docs/examples/vue/vue-ui-events-dragging.md ================================================ --- title: "UI events: Drag" description: An live example of making an element draggable with a single prop in Pose for Vue category: vue --- # UI events: Drag Making an element draggable in Pose for Vue is a simple matter of setting `draggable` to `true`. ```javascript posed.div({ draggable: true }) ``` To restrict to a single axis, use either `'x'` or `'y'`. ```javascript posed.div({ draggable: 'x' }) ``` You can use the special `drag` and `dragEnd` poses to animate the element while dragging is in progress: ================================================ FILE: packages/popmotion-pose/docs/examples/vue/vue-ui-events-focus.md ================================================ --- title: "UI events: Focus" description: An live example of making an element focusable with a single prop in Pose for Vue category: vue --- # UI events: Focus Making an element focusable in Pose for Vue is a simple matter of setting `press` to `true`, with associated poses: ```javascript posed.input({ focusable: true, init: { color: '#aaa', outlineWidth: '0px', outlineOffset: '0px', scale: 1 }, focus: { color: '#000', outlineWidth: '12px', outlineOffset: '5px', outlineColor: '#AB36FF', scale: 1.2 } }) ``` ================================================ FILE: packages/popmotion-pose/docs/examples/vue/vue-ui-events-hover.md ================================================ --- title: "UI events: Hover" description: An live example of making an element hoverable with a single prop in Pose for Vue category: vue --- # UI events: Hover Making an element hoverable in Pose for Vue is a simple matter of setting `press` to `true`, with associated poses: ```javascript posed.div({ hoverable: true, init: { scale: 1, boxShadow: '0px 0px 0px rgba(0,0,0,0)' }, hover: { scale: 1.2, boxShadow: '0px 5px 10px rgba(0,0,0,0.2)' } }) ``` ================================================ FILE: packages/popmotion-pose/docs/examples/vue/vue-ui-events-press.md ================================================ --- title: "UI events: Press" description: An live example of making an element pressable with a single prop in Pose for Vue category: vue --- # UI events: Press Making an element pressable in Pose for Vue is a simple matter of setting `press` to `true`, with associated poses: ```javascript posed.div({ pressable: true, init: { scale: 1 }, press: { scale: 0.8 } }) ``` ================================================ FILE: packages/popmotion-pose/docs/learn/get-started.md ================================================ --- title: Get started description: Get started with the Pose animation system for HTML, SVG, React and React Native --- # Get started ## Choose your adventure: ### [React](/pose/learn/popmotion-get-started) ### [React Native](/pose/learn/native-get-started) ### [Vue](/pose/learn/vue-get-started) ================================================ FILE: packages/popmotion-pose/docs/learn/how-to/css-variables.md ================================================ --- title: CSS variables description: How to declaratively animate CSS variables category: how-to --- # Animate CSS variables [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables) are variables that can be defined on a parent element and then referenced throughout its children: ```css body { --brand-color: #f00; } a { color: var(--brand-color); } ``` The benefit of CSS variables vs variables in CSS pre-processors like SASS is that they're **dynamic**, which means they can be updated at runtime. So in the above example, to change the link color we'd simply change the value of `--brand-color`. Because these values can be changed, they can be animated. In this tutorial we'll demonstrate how to animate multiple elements on a page by animating a single CSS variable. ## CSS First, open this [CodePen](https://codepen.io/popmotion/pen/mxGZNx?editors=0110) to follow along with the tutorial. As a simple example, we're going to animate a `distance` property. Add that to `body`: ```css body { --distance: 0px; } ``` Now, on the `.box` rule add a `transform` that uses this `distance`: ```css transform: translateX(var(--distance)); ``` ## JavaScript In all the previous Pose tutorials, if we've wanted to change a property of an element, we've selected the element itself. With CSS variables, we're instead going to animate the parent element where the variable is set. Which, in this case, is `body`: ```javascript const body = document.querySelector('body') ``` Now define the pose config with a single pose, called `right`: ```javascript const config = { right: { '--distance': '100px' } } ``` Bring the two together by creating a new poser: ```javascript const bodyPoser = pose(body, config) ``` To trigger the animation, set the pose to "right": ```javascript bodyPoser.set('right') ``` And that's it! All four boxes will animate to the right. ## Gotchas There are a couple of points to keep in mind when you animate CSS variables. ### Pose can't infer types In the example above, we explicitly set `--distance` to `'100px'`. Usually when animating `x`, we'd simply use `100` as a raw number and Pose would infer that we should use pixels. When you're animating CSS variables, **Pose doesn't know what you're going to do with the variable**. So we need to manually tell it if we want specific measurements. ### CSS variables sometimes contain whitespace In [this CodePen](https://codepen.io/popmotion/pen/JLaBNG?editors=0010), we're animating using the `--color` variable as initially read directly from the CSS. Oddly, this read value is sometimes returned with extra whitespace. If you then set the value with the extra whitespace, it breaks! In this example we're using `from` as `from.trim()` to fix this, but there is an [open issue in the Stylefire](https://github.com/Popmotion/stylefire/issues/16) to have this fixed. ================================================ FILE: packages/popmotion-pose/docs/learn/how-to/framer-x.md ================================================ --- title: "Use Pose in Framer X (WIP)" description: Add declarative animations to your Framer X prototypes category: how-to --- # Use Pose in Framer X (WIP) Framer X beta invites are going out and it's already possible to use Pose in Framer X prototypes. It's early days at the moment so integration isn't seamless but it's already possible to add UI events like `drag`, `hover` and `press`. You can also already use Framer X to test animations by exposing pose logic via the UI. But there are currently limitations, and we'll take a look at those, too. ## Install Pose First we need to add Pose to our Framer project. With our project open in Framer X, go to File > Show Project Folder. ![Framer X Show Project](/guides/framer-x-show-project.png) You should have the `container` folder highlighted. Open this folder in your terminal of choice. > If you're using VS Code, drag and drop the highlighted folder onto the VS Code dock icon. Then, in VS Code's file browser, right click in the project root and choose "Open in Terminal". This folder is a typical yarn package, so you can add Pose by running `yarn add react-pose`. The current limitation with this process is you have to repeat it for every project you want to use Pose in. I'm also unclear as to whether yarn dependencies are saved to the project file itself, as `Show Project Folder` opens a directory in the app cache. ## Create a posed Framer component At the bottom of the Framer X Component panel, there's a button labelled "New Component". Click that, give your component a name, choose "from code" and press "Create and edit". ![Create a new component](/guides/framer-x-create-component.png) You'll be greeted with a TypeScript file containing some simple React code. The only Framer X-specific code in here is a static property called `propertyControls`, which we'll explore in a moment. First, import Pose and make a simple hoverable component: ```javascript import posed from 'react-pose'; const Container = posed.div({ hoverable: true, init: { scale: 1 }, hover: { scale: 1.2 } }); ``` Replace the render function with the following: ```javascript return {this.props.text}; ``` Save, and return to the Framer X editor. In the components panel you'll see your new component. You can add this to the Framer X editor by dragging and dropping it in place. Click the play icon in the top and hover over your new component. It'll react to your hovering as expected. ![New Framer interactions!](/guides/framer-x-a.gif) ## Expose pose controls Go back to your editor and highlight your component. Notice in the right-hand inspector there's a section named after your component with a text input labelled "Text". If you replace "Hello world!", your component will update with the input text. Go to the source code of your component and take a look at the `render` function. It's outputting `this.props.text`. Now look at `propertyControls`. It has a single property, `text`, with a title of "Text". This is how you expose control over `props` via the Framer X UI. We're going to use this to control Pose animations. First, cmd-click on `propertyControls`' `PropertyControls` type. This will take you through to its type definition, and we can take a look at some of the control types available to us. Let's use a simple control, a `Boolean`. Add this to `propertyControls`: ```javascript isVisible: { type: ControlType.Boolean, title: "Is visible" } ``` We also need to add a default for this property to `defaultProps`: ```javascript static defaultProps = { text: "Hello World!", isVisible: false }; ``` And a type for the property to the `Props` interface: ```javascript interface Props { text: string; isVisible: boolean; } ``` Now, this control will be available to use in the inspector panel. But we still need to use this prop in our pose logic. Add two new poses, `visible` and `hidden`: ```javascript const Container = posed.div({ hoverable: true, init: { scale: 1 }, hover: { scale: 1.2 }, visible: { opacity: 1 }, hidden: { opacity: 0 } }); ``` And set `Container`'s `pose` prop to either of these poses based the value of `isVisible`: ```javascript {this.props.text} ``` Now, back in our editor, the "Is visible" toggle control will make our component fade in and out. ## Dynamic pose props We can use the Framer UI to affect the actual transition used, too. Repeating the steps above, add a new `fadeDuration` property to the right-hand inspector. This time, we want it to be a `Number`. By cmd-clicking `PropertyControls` again we can see that `NumberControl` has a `max` property available which, if we don't set, the range slider will max out at `100`. So lets change that to something higher like `10000`: ```javascript fadeDuration: { type: ControlType.Number, max: 10000, title: "Fade duration" } ``` Then make a new fade transition creator: ```javascript const fadeTransition = ({ fadeDuration }) => ({ type: 'tween', duration: fadeDuration }); ``` And apply this to our `visible` and `hidden` poses: ```javascript const Container = posed.div({ hoverable: true, init: { scale: 1 }, hover: { scale: 1.2 }, visible: { opacity: 1, transition: fadeTransition }, hidden: { opacity: 0, transition: fadeTransition } }); ``` Finally, ensure we're passing the `fadeDuration` prop to `Container`: ```javascript {this.props.text} ``` Now, you can change the duration of the transition with the new "Fade duration" number slider in the inspector panel. Play with the visibility toggle to check it out! ![Close down that feedback loop!](/guides/framer-x-b.gif) ## Conclusion This level of integration is already possible, but in the near future it'd be great to figure out a way of triggering local state/prop changes from other components within the Framer prototype itself. I'd also like to be able to expose the entire pose API via the Framer UI, as well as figure out `PoseGroup` integration. Framer will be a great tool for closing down the feedback loop when playing around with interactions. Some of the above may already be possible, and just requires further exploration. [Hit me up](https://twitter.com/popmotionjs) if you have any ideas! ================================================ FILE: packages/popmotion-pose/docs/learn/react/animating-children.md ================================================ --- title: Animating children description: How to orchestrate animations across multiple elements category: react next: ui-events --- > React Pose has been **deprecated** in favour of [Framer Motion](https://framer.com/motion). [Read the upgrade guide](https://www.framer.com/api/motion/migrate-from-pose/) # Animating children Traditionally, coordinating animation across multiple children has been an involved process. Especially with React. With Pose, it's as simple as animating just one. It looks like this: ```javascript const Parent = posed.ul(); const Child = posed.li(); ({items}) => ( {items.map(item => )} ) ``` Whenever a posed component changes `pose`, that change is communicated throughout all of its children posed components via React's context API. So even if they're not direct children, they still update accordingly. This makes it super-simple to, for instance, make page-wide [route transitions](/pose/learn/route-transitions-react-router). ## Setup To demonstrate animating children, we're going to create this sidebar animation (if the animation has already played, refresh the iframe): Follow along by forking this [CodeSandbox](https://codesandbox.io/s/0q10o2xlyl). ## Poses First, we need to describe two poses, "open" and "closed", for both the sidebar and the items within it. At the top of your `index.js`, create your posed components: ```javascript const Sidebar = posed.ul({ open: { x: '0%' }, closed: { x: '-100%' } }); const Item = posed.li({ open: { y: 0, opacity: 1 }, closed: { y: 20, opacity: 0 } }); ``` ## Render In our `render` function, replace `return null` with our `Sidebar`, setting our `pose` prop to either the `open` or `closed` poses defined above: ```javascript return ( ); ``` Now when the `isOpen` is `true`, our sidebar animates out. All we have to do to also animate the `Item` components is add those as children: ```javascript return ( ); ``` We only provide `pose` to `Sidebar`, yet `Item` components also animate! ## Scheduling animations Currently, our children animations are being fired at the exact same time as the parent. But, often we'd prefer the child animations to be delayed or staggered. Luckily, we've got properties for that! ### delay The `delay` property can be used to delay the animation on the **current** poser, without affecting the execution of child animations. So by setting `delay: 300` on the sidebar's `closed` pose, the children will all animate out before the sidebar itself. ```javascript const Sidebar = posed.ul({ open: { x: '0%' }, closed: { x: '-100%', delay: 300 } }); ``` ### delayChildren Conversely, the `delayChildren` property can be used to delay all the children animations. By setting `delayChildren` on the sidebar's `open` pose, we can animate the sidebar out and **then** animate the children in: ```javascript const Sidebar = posed.ul({ open: { x: '0%', delayChildren: 200 }, closed: { x: '-100%', delay: 300 } }); ``` ### staggerChildren Rather than animating all the children in at once, it's possible to stagger them in individually. The `staggerChildren` prop can be used to determine the delay between each one, starting from **after** the `delayChildren` duration: ```javascript const Sidebar = posed.ul({ open: { x: '0%', delayChildren: 200, staggerChildren: 50 }, closed: { x: '-100%', delay: 300 }, initialPose: 'closed' }); ``` ### staggerDirection `staggerDirection` can be used to determine which order we stagger over the children in. It can either be `1` (first to last, default), or `-1` (last to first). ### beforeChildren/afterChildren Setting either `beforeChildren` or `afterChildren` props to `true` will make the parent animation play **before** or **after** any children animations. ================================================ FILE: packages/popmotion-pose/docs/learn/react/custom-transitions.md ================================================ --- title: Custom transitions description: How to use Pose for React to define custom transitions category: react next: dynamic-props --- > React Pose has been **deprecated** in favour of [Framer Motion](https://framer.com/motion). [Read the upgrade guide](https://www.framer.com/api/motion/migrate-from-pose/) # Custom transitions With [automatic animations](/pose/learn/get-started), it's easy to create snappy and playful animations just by defining poses. But there's plenty of instances where we want full control over our animation. For this, we can use the `transition` property. ## Basic usage Just like CSS, every pose can have a [`transition` property](/pose/api/react-config/#config-options-poses-transition). This property describes how each value should transition to its new pose: ```javascript posed.div({ visible: { opacity: 1, transition: { duration: 300 } } }) ``` If we're animating multiple properties, we can **optionally** provide different animations for each by providing a named map. ```javascript posed.div({ visible: { opacity: 1, scaleY: 1, transition: { opacity: { ease: 'easeOut', duration: 300 }, default: { ease: 'linear', duration: 500 } } } }); ``` **By default**, if we define a `transition`, it'll be a `tween`. This is an animation between two values over a specific duration of time. By providing a `type` property, we can select a different animation to use: ## Transitions Pose ships with five types of animation from [Popmotion Pure](/pure). Tween, spring, decay, keyframes, and physics. ### Tween A tween animates from one value to another over a set duration of time. ```javascript transition: { duration: 400, ease: 'linear' } ``` #### Easing The `ease` property can be used to affect the speed of the tween over the course of its duration. This property can be the name of a [Popmotion easing function](/api/easing): - 'linear' - 'easeIn', 'easeOut', 'easeInOut' - 'circIn', 'circOut', 'circInOut' - 'backIn', 'backOut', 'backInOut' - 'anticipate' Or an array of four numbers to create a cubic bezier easing function: ```javascript transition: { ease: [.01, .64, .99, .56] } ``` [Full `tween` documentation](/api/tween) ### Spring Spring animations maintain velocity between animations to create visceral, engaging motion. It makes them perfect for animations that happen as a result of user interaction. By adjusting their `stiffness`, `mass` and `damping` properties, a wide-variety of spring feels can be created. ```javascript transition: { type: 'spring', stiffness: 100 } ``` [Full `spring` documentation](/api/spring) ### Decay Decay reduces the velocity of an animation over a duration of time. It's a perfect match for the special `dragEnd` pose that fires when a user stops [dragging](/pose/learn/ui-events) something, as it can replicate the momentum-scrolling common on smart phones. The end value is automatically calculated by Pose at the start of the animation, but with the `modifyTarget` prop, you can adjust this, allowing you to do things like snap to a grid. ```javascript transition: { type: 'decay', modifyTarget: v => Math.ceil(v / 100) * 100 // Snap to nearest 100px } ``` [Full `decay` documentation](/api/decay) ### Keyframes Keyframes allows you to schedule a series of values to tween between. ```javascript transition: ({ from, to }) => ({ type: 'keyframes', values: [from, 100, to], times: [0, 0.25, 1] }) ``` [Full `keyframes` documentation](/api/keyframes) ### Physics Physics allows you to simulate things like velocity, friction, and acceleration. ```javascript transition: { type: 'physics', velocity: 1000 } ``` [Full `physics` documentation](/api/physics) ## Transition props There are a number of other properties that can be used with any transition: ### Delay If set, will delay the execution of the transition by the specified amount: ```javascript transition: { type: 'physics', delay: 400 } ``` ### Min/Max If set, will ensure values are capped to no less than `min` and no more than `max`. ```javascript transition: { type: 'keyframes', values: [0, 3, 10], min: 2, max: 9 } ``` ### Round If set to `true`, `round` will ensure that values output from the animation will be rounded. ```javascript transition: { type: 'spring', round: true } ``` ================================================ FILE: packages/popmotion-pose/docs/learn/react/dynamic-props.md ================================================ --- title: Dynamic props description: Set props as dynamic functions category: react next: animating-children --- > React Pose has been **deprecated** in favour of [Framer Motion](https://framer.com/motion). [Read the upgrade guide](https://www.framer.com/api/motion/migrate-from-pose/) # Dynamic pose props Each pose property can be set as a function that resolves when the pose is entered: ```javascript const Box = posed.div({ visible: { x: 0, y: (props) => 100, // Resolved on `visible` enter transition: { x: { type: 'tween' }, y: (props) => ({ type: 'spring' }) // Resolved on `visible` enter } } }) ``` By using the provided `props` argument, this allows us to create dynamic properties that will react to changes in your app. ## Props So what are props? They're just component props! Any props you provide to your posed component will be forwarded to these dynamic pose props. For instance, you could use a component's `i` index prop to write a dynamic `delay` prop: ```javascript const Item = posed.li({ visible: { opacity: 1, transition: ({ i }) => ({ delay: i * 50 }) }, props: { i: 0 } }); export default ({ i, isVisible }) => ``` ## Transition props `transition` works a little differently than other pose props. If set as a function, the function is run **once each for every property being animated**. That function is provided a few extra props, automatically generated by Pose: - `from`: The current state of this value - `to`: The target state defined in the pose - `velocity`: If a numerical value, the current velocity of the value - `key`: The name of the current value - `prevPoseKey`: The name of the pose this value was previously set to These props can be used to return a different transition definition based on the state of the value: ```javascript const Sidebar = posed.div({ open: { x: '-100%', transition: ({ velocity, to }) => velocity < 0 ? { to: 0 } : { to } } }); ``` If `transition` is a named map, **some or all** of these can be defined as functions: ```javascript const Sidebar = posed.div({ open: { x: 0, opacity: 1, transition: { x: ({ velocity, to }) => velocity < 0 ? { to: -300 } : { to }, opacity: { type: 'spring' } } } }); ``` ================================================ FILE: packages/popmotion-pose/docs/learn/react/flip.md ================================================ --- title: FLIP description: A look at Pose's powerful FLIP API category: react --- > React Pose has been **deprecated** in favour of [Framer Motion](https://framer.com/motion). [Read the upgrade guide](https://www.framer.com/api/motion/migrate-from-pose/) # FLIP The FLIP technique, [fully explained here](https://aerotwist.com/blog/flip-your-animations/), is a way of animating expensive, layout-breaking animations like `width` and `top` by using quick transforms. Pose provides a couple of methods for performing FLIP: 1. Animate `width`/`top`/etc and adding `flip: true` to a pose 2. Via the [`PoseGroup` component](/pose/api/posegroup) In this tutorial, we'll take a look at each of these. ## width/top The problem with animating size and position properties is that they break layout. Recalculating layout is expensive, which can slow animations to below 60fps. So, when you set a pose with `flip: true` and any of `width`, `height`, `top`, `left`, `right`, or `bottom` values, these will applied at the start of the animation. Pose will measure the size and position of the element before and after, and animate from one to the other using transform properties instead. For instance, we can switch a `div` to fullscreen and back using the following config: ```javascript const Panel = posed.div({ fullscreen: { width: '100vw', height: '100vh', transition: tween, flip: true }, thumbnail: { width: 100, height: 100, transition: tween, flip: true } }); ``` ## PoseGroup React Pose includes a [component called `PoseGroup`](/pose/api/posegroup). When wrapping a group of posed components, it enables two things: 1. `enter`/`exit` poses 2. FLIP-powered re-ordering When an item enters or exits the group, or changes position in it, all the other items automatically measure their position before the change and then FLIP into to the new position: This all happens automatically. Open [this CodePen template](https://codepen.io/popmotion/pen/mxqobd?editors=0010) At the top of the JS, create a new posed component, a list item: ```javascript const Item = posed.li() ``` Now, replace `null` in the render function of `Example` with: ```javascript (
    {this.state.items.map(item => ( ))}
); ``` This component is pre-set to shuffle the items every two seconds. Notice how, without even defining any animations, all the items automatically animate to their new home. Plus, the `flip` pose is enabled. So we can define the animation they use to move position by changing `Item` to: ```javascript const { tween } = popmotion const Item = posed.li({ flip: { transition: tween } }) ``` ================================================ FILE: packages/popmotion-pose/docs/learn/react/install.md ================================================ --- title: Install description: Overview of Pose's installation options. category: react next: popmotion-get-started --- > React Pose has been **deprecated** in favour of [Framer Motion](https://framer.com/motion). [Read the upgrade guide](https://www.framer.com/api/motion/migrate-from-pose/) # Install **React Pose requires React 16.3.0.** ## Package managers (recommended) ### npm ```bash npm install react-pose --save ``` ### yarn ```bash yarn add react-pose ``` ## File include **Note:** The Pose documentation uses the `import` syntax for importing individual modules. **If you use one of the following installation methods, top-level React Pose exports will be available as the global `posed` and `PoseGroup` variables.** So, when you see in the docs `import posed, { PoseGroup } from 'react-pose'`, you can instead simply use the global `posed` and `PoseGroup` variables. ### Download You can download the latest version of Pose at https://unpkg.com/react-pose/dist/react-pose.js ### Script include You can include it in your HTML with this `script` tag: ``` ``` ## CodePen You can fork the [React Pose playground on CodePen](https://codepen.io/popmotion/pen/mxmrPZ?editors=0010), which is set up with the latest version of React Pose. You an also add React Pose to any existing CodePen project by clicking on Settings > JavaScript and then pasting `https://unpkg.com/react-pose/dist/react-pose.js` into the "Add External JavaScript" field. ================================================ FILE: packages/popmotion-pose/docs/learn/react/passive.md ================================================ --- title: Passive values description: Learn how to create passive values that only change when others do category: react next: flip --- > React Pose has been **deprecated** in favour of [Framer Motion](https://framer.com/motion). [Read the upgrade guide](https://www.framer.com/api/motion/migrate-from-pose/) # Passive values Sometimes we don't want to explicitly define a state for a value, we might just want it to change whenever another value does. For instance, we might want an element to disappear as it moves beyond certain boundaries: For this, we can use **passive values**. In this tutorial we'll see how to define them, and how to make them respond to changes in parent values too. ## Defining a passive value Open the [this draggable example](https://codepen.io/popmotion/pen/dmWWdp?editors=0010) and replace the posed component config with this: ```javascript const Box = posed.div({ draggable: 'x' }); ``` The dragging motion of the element is locked to the `x` axis. We can actually lock movement to the diagonal by defining `y` as a `passive` value. Passive values are defined a tuples, like this: ```javascript const Box = posed.div({ draggable: 'x', passive: { y: ['x', v => v] } }); ``` The first item in the tuple is the name of the value we want to link to. In this case, that's `'x'`. The second item is a mapping function. This takes the output of the linked value and returns our passive value. In this example, we're simply returning `x`, and creating this motion: By using this mapping function we can start creating new effects. For instance, returning the negative of `x` creates diagonal movement in the opposite direction: ```javascript y: ['x', v => -v] ``` Or by using `Math.sin` we can make wavey behaviour: ```javascript y: ['x', v => v * Math.sin(v * 0.01)] ``` ## Changing non-numerical values So far, we've mapped two pixel values. But we can set any kind of value with any other. Long-time users of Popmotion will recognise the signature of the mapping function. It accepts one value, and returns another. Which means we can compose this function using [Popmotion's transformers](/api/transformers). For instance, instead of `y` let's create a function that will map `x` to `backgroundColor`. For this we'll need to import four functions from `popmotion.transform`: ```javascript const { pipe, clamp, interpolate, blendColor } = popmotion.transform ``` Our steps will be: 1) Convert the output of `x` from pixels to a `0` to `1` range 2) Clamp that output to within `0` and `1` 3) Use that progress number to blend between two colors Which means our function will look like this: ```javascript backgroundColor: ['x', pipe( interpolate([-200, 200], [0, 1]), clamp(0, 1), blendColor('#FF1C68', '#09f') )] ``` ## Linking to ancestors We can also link a passive value to a value in one of the poser's ancestors. Let's revist our [sidebar example](https://codepen.io/popmotion/pen/LdybdN?editors=0010) from earlier. Currently, we're actively animating the children by setting poses on both the parent and the children. But, it's possible to change the opacity of the items as the `x` of their sidebar parent changes. To do this, we pass `true` as the third and final argument of the tuple. Add a slower `transition` to `sidebarConfig.open` to help us see this in effect. ```javascript transition: (props) => tween({ ...props, duration: 1000 }) ``` Now, replace `itemConfig` with this: ```javascript const Box = posed.li({ passive: { opacity: ['x', pipe( parseFloat, interpolate([-100, 0], [0, 1]) ), true] } }); ``` As you can see, we're passing in a third parameter to the passive tuple, `true`. This says "listen to the `x` value, but do so on my immediate parent". ### Distant ancestors Using `true` is fine if we want to look just one part up the ancestor chain. But it's also possible to go much further up using `label`. By explicitly naming our posers with a `label`, we can refer to any poser in the ancestor chain. Add the label `'sidebar'` to our `sidebarConfig`: ```javascript const Sidebar = posed.ul({ label: 'sidebar', /* other props */ }); ``` Now replace `true` in `itemConfig` with `'sidebar'`. It still works, and it will still work if you decide to put a poser between sidebar and items. ================================================ FILE: packages/popmotion-pose/docs/learn/react/popmotion-get-started.md ================================================ --- title: Get started description: Introduction to Pose for React's declarative animation interface category: react next: custom-transitions --- > React Pose has been **deprecated** in favour of [Framer Motion](https://framer.com/motion). [Read the upgrade guide](https://www.framer.com/api/motion/migrate-from-pose/) # Get started Pose is a declarative motion system that combines the simplicity of CSS transitions with the power and flexibility of JavaScript. In this series of tutorials, we'll learn how to use Pose for React DOM. We'll gradually introduce each of its features, starting with this simple `opacity` animation: ## Setup The easiest way to play around with Pose is to [fork this CodeSandbox playground](https://codesandbox.io/s/qz0zyqwnqq). For local development, all installation options can be found on the [install](/pose/learn/install) page. ## The "Hello World" animation In Pose for React, we create animated components by importing `posed` from `react-pose`: ```javascript import posed from 'react-pose'; ``` `posed` can create animated versions of any HTML or SVG element. Use it to create a `div`: ```javascript const Box = posed.div(); ``` Change the `render` function to return an instance of `Box` instead of `div`: ```javascript return ; ``` With Pose, we define possible states, or **poses**, that the `Box` can be in. It looks a lot like CSS: ```javascript const Box = posed.div({ visible: { opacity: 1 }, hidden: { opacity: 0 } }); ``` Now to animate `Box` between its `visible` and `hidden` poses, we just pass it a `pose` prop: ```javascript return ( ); ``` The box is now animating between the two poses! ## But wait, where did we define the animation? Short answer: we didn't. More helpful answer: By default, Pose **doesn't require you to explicitly define the animations** used to transition between two states. Instead, it automatically creates an animation based on the properties being animated. These animations have been designed to create snappy and playful interfaces. Physical motion uses `spring` to maintain velocity between animations, whereas properties like `opacity` use a `tween`. However, there will always be situations where we want greater control. For that, we can define [custom transitions](/pose/learn/custom-transitions). ================================================ FILE: packages/popmotion-pose/docs/learn/react/react-exit-enter-transitions.md ================================================ --- title: Enter/exit transitions description: Learn how to animate React components as they mount and unmount with Pose for React's PoseGroup component category: react next: passive --- > React Pose has been **deprecated** in favour of [Framer Motion](https://framer.com/motion). [Read the upgrade guide](https://www.framer.com/api/motion/migrate-from-pose/) # Enter/exit transitions Animating components when they unmount has been historically tricky with React. [Pose for React's `PoseGroup` component](/pose/api/posegroup) makes it trivial to animate one or more children components as they enter and exit. In this tutorial, we'll see how it can be used to make this modal animation: ## Setup To start, fork [this CodeSandbox](https://codesandbox.io/s/842823w17j). It's set up with a `setInterval` that switches the visibility of two components, the modal, and its background overlay. ```javascript render() { return ( this.state.isVisible && [
,
] ); } ``` Currently, both components snap in and out, so let's make them smoothly animate instead! ## Import Pose for React First, we need to import Pose for React. This time, we're going to be importing both `posed` and the `PoseGroup` component: ```javascript import posed, { PoseGroup } from 'react-pose'; ``` ## Add PoseGroup To start triggering animations when components enter and exit, we need to wrap them in `PoseGroup`. `PoseGroup` needs to stay rendered at all times, and its children can be added and removed via logic. So wrap our `isVisible` check like so: ```javascript return ( {this.state.isVisible && [
,
]} ); ``` ## Create posed components As the `shade` and `modal` children are currently plain `div` components, they won't animate. We need to replace them with posed components. After the block of imports, add: ```javascript const Modal = posed.div(); const Shade = posed.div(); ``` Then replace the two `div`s with our new components: ```javascript , ``` ## Animate! By default, `PoseGroup` will fire a component's `enter` pose when they enter (transitioning from its `exit` pose), and `exit` when they exit. So let's give our new `Shade` and `Modal` components `enter` and `exit` poses: ```javascript const Modal = posed.div({ enter: { y: 0, opacity: 1 }, exit: { y: 50, opacity: 0 } }); const Shade = posed.div({ enter: { opacity: 1 }, exit: { opacity: 0 } }); ``` Now, your modal and its shade will animate in and out! As usual you can play around with these poses to tweak the animation. For instance you can add a `delay` to `Modal`'s `enter` pose to make it animate in a little after the `Shade`. Or provide it a custom `transition` to `exit` faster: ```javascript const Modal = posed.div({ enter: { y: 0, opacity: 1, delay: 300 }, exit: { y: 50, opacity: 0, transition: { duration: 200 } } }); ``` ================================================ FILE: packages/popmotion-pose/docs/learn/react/react-tutorial-medium-style-image-zoom.md ================================================ --- title: "Tutorial: Medium-style image zoom" description: How to make Medium-style image zooming with Pose for React category: react --- > React Pose has been **deprecated** in favour of [Framer Motion](https://framer.com/motion). [Read the upgrade guide](https://www.framer.com/api/motion/migrate-from-pose/) # Tutorial: Medium-style image zoom [Medium](https://medium.com) have an beautiful zoom effect on their images. When clicked, they pop out of the page as a white background fades in behind them. Then, if clicked again, or if a user scrolls away, they pop back into place. Take a look: In this tutorial, we'll learn how to achieve this same effect using Pose for React. ## Setup To get started, fork this [CodeSandbox template](https://codesandbox.io/s/y2k00vx22x). It contains a mock article that contains a couple of images. These are being rendered via the component we're going to work on, `ZoomImage`. Open `components/ZoomImage.js`, and let's get started! ## State First, we need to create `state` so we know whether the image is zoomed or not. At the top of the `ZoomImage` class, add the following: ```javascript state = { isZoomed: false }; ``` Of course, this state is useless on its own. We're going to need a couple of functions to set the zoom status. On the next line, add the following `zoomIn` and `zoomOut` methods: ```javascript zoomIn() { this.setState({ isZoomed: true }); } zoomOut() { this.setState({ isZoomed: false }); } ``` Finally, we want to toggle the zoomed state when someone clicks the image container (as this will also later contain the white background): ```javascript
this.state.isZoomed ? this.zoomOut() : this.zoomIn()} style={{ width: imageWidth, height: imageHeight }} > ``` Now, when the image is clicked, the component will change zoom status. But we're not responding to this in our `render` function. Let's make some animations! ## Image zoom animation When the image zooms in, it needs to animate from its place in the document, smoothly into the center of the screen. To do this, we're going to use Pose's FLIP capabilities. You can read the gritty details about FLIP in this [blog post by Paul Lewis](https://aerotwist.com/blog/flip-your-animations/). In essence, it's a way of *performantly* animating between two states that would otherwise be expensive, for instance where `position`, `top`, `width`, or other layout-changing properties have changed. In Pose, you simply have to add `flip: true` to a pose, and it'll automatically perform the usually complicated steps to perform this animation. Import Pose for React: ```javascript import posed from 'react-pose'; ``` Now make a posed `img` component: ```javascript const Image = posed.img(); ``` We're going to provide the component two poses, one for each zoom state: `zoomedIn` and `zoomedOut`. Our `zoomedIn` pose is going to set `position: fixed` and every positional prop to `0`. This will pop the content out of the layout and lock it to the viewport. In our `styles.css` file, `img` has a style of `margin: auto` which centers the image when it's being stretched across the screen in this way. ```javascript const Image = posed.img({ zoomedIn: { position: 'fixed', top: 0, left: 0, bottom: 0, right: 0, flip: true } }); ``` `zoomedOut` sets `position: static` to pop it back into the DOM, as well as setting `width` and `height` to `auto` to make it fill its layout container: ```javascript const Image = posed.img({ zoomedIn: { position: 'fixed', top: 0, left: 0, bottom: 0, right: 0, flip: true }, zoomedOut: { position: 'static', width: 'auto', height: 'auto', flip: true } }); ``` We've now got our posed `img` component fully configured. Replace the `img` component in the `render` function with it: ```javascript ``` To animate `Image` between the two poses, we need to provide it a `pose` property. At the top of the `render` function, set our `pose`: ```javascript const { isZoomed } = this.state; const pose = isZoomed ? 'zoomedIn' : 'zoomedOut'; ``` And provide it to `Image`: ```javascript ``` Now, when we click our image, it zooms in and out! I find the automatically generated animation a little bouncy for this purpose. We can define a new `transition` with a `ease` curve generated at [Lea Verou's cubic bezier generator](http://cubic-bezier.com/#.08,.69,.2,.99). ```javascript const transition = { duration: 400, ease: [0.08, 0.69, 0.2, 0.99] }; ``` Provide this as a `transition` prop to both poses, and the animation becomes a little slicker. ## Background animation That's the (usually) difficult bit out of the way. It's looking pretty good but the Medium example fades a background in behind the image as it zooms in and out. Make a new posed component called `Frame`: ```javascript const Frame = posed.div(); ``` In our `styles.css` add a new rule for `.frame`. We're going to make the background of this frame white, and set `translateZ(0)` to ensure its fade animation is hardware-accelerated: ```css .frame { position: fixed; top: 0; left: 0; right: 0; bottom: 0; display: none; background: white; transform: translateZ(0); } ``` And now add `Frame` as a sibling of `Image`, also passing it the `pose` prop: ```javascript ``` Now we can animate it! We just want to fade the overlay in and out, so first add those poses: ```javascript const Frame = posed.div({ zoomedIn: { opacity: 1 }, zoomedOut: { opacity: 0 } }); ``` By itself this won't do anything, as we've got `display` set to `none` in the CSS. For this we can use the `applyAtStart` and `applyAtEnd` props. They allow you to define styles to set at the start and at the end of the pose transition, respectively. ```javascript const Frame = posed.div({ zoomedIn: { applyAtStart: { display: 'block' }, opacity: 1 }, zoomedOut: { applyAtEnd: { display: 'none' }, opacity: 0 } }); ``` Now your background will fade in and out behind the image as it zooms in! ## Scroll to zoom out The original Medium image zoom has a nice feature where if a user starts scrolling, the image zooms out back into its original place. We can accomplish the same thing by adding a `'scroll'` event listener to `zoomIn`: ```javascript zoomIn() { window.addEventListener('scroll', this.zoomOut); this.setState({ isZoomed: true }); } ``` By itself, this isn't going to work. When `this.zoomOut` is called, it'll be in the execution context of the event caller rather than our React component. We can bind `zoomOut` to our component by changing it to an arrow function: ```javascript zoomOut = () => { this.setState({ isZoomed: false }); }; ``` Finally, we need to remove the event listener when a user does zoom out: ```javascript zoomOut = () => { window.removeEventListener('scroll', this.zoomOut); this.setState({ isZoomed: false }); }; ``` ## Conclusion Here's our finished example: There's plenty of fun things you can do to improve accessibility and aesthetics. Have a think about: - Closing the image via the `esc` key - Changing the background animation. You could even incorporate SVGs. - Adding a "scroll delay" where a user has to scroll a minimum distance before we close the image. - Changing the cursor to show a zoom in or zoom out icon. ================================================ FILE: packages/popmotion-pose/docs/learn/react/route-transitions-reach-router.md ================================================ --- title: "Tutorial: Reach Router" description: How to make route transition animations with React Pose and Reach Router category: react --- > React Pose has been **deprecated** in favour of [Framer Motion](https://framer.com/motion). [Read the upgrade guide](https://www.framer.com/api/motion/migrate-from-pose/) # Route transitions with Reach Router > **Note:** This tutorial is for **Reach** Router. Users of **React** Router will want to use the [React Router tutorial](/pose/learn/route-transitions-react-router). Route transitions in React are notoriously fiddly. With [Pose](/pose) and the accessibility-first [Reach Router](https://reach.tech/router), they can be pretty simple. We're going first learn how to make a simple fade transition between two routes. Then, as Pose has the ability to coordinate animations throughout the component tree, we'll show how to animate each route differently, with content staggering in and out. Here's what we'll be making: ## Sandbox We've created a [CodeSandbox](https://codesandbox.io/s/mzx1jz521p) example preloaded with React Pose and Reach Router, for you to fork and follow along. It's set up in a standard way according to the [Reach Router docs](https://reach.tech/router), so if you're not already familiar with Reach Router, it's worth reading the overview there. ## Fade transition So, let's animate! The first animation we'll add is a simple fade transition. When a user clicks a link, we want to fade the existing content out, and the new content in. We'll start by importing React Pose. We're going to be using both `posed` and `PoseGroup`. So at the end of your imports, add: ```javascript import posed, { PoseGroup } from 'react-pose'; ``` Now, let's create a posed component with our two visual states, `enter` and `exit`. After the line above, add: ```javascript const RoutesContainer = posed.div({ enter: { opacity: 1 }, exit: { opacity: 0 } }); ``` We can now use this to wrap our `Router` component: ```javascript {({ location }) => ( {children} )} ``` To animate this component between the two `enter` and `exit` states, we can use the `PoseGroup` component. `PoseGroup` tracks the entering and exiting of child components (as well as reordering), and will animate them in an out. Any components exiting will only be physically removed from the tree once they've finished their `exit` animation. ```javascript {({ location }) => ( {children} )} ``` Ah, but wait! If you try and click between pages, there's still no animation. What's up? Without passing `RoutesContainer` a `key`, `PoseGroup` doesn't know that it has a new child, so it can't animate anything. Let's use Reach Router's `location.key` prop as our key: ```javascript ``` Now, when you change routes, the content will fade in and out! The two pieces of content currently fade on top of each other. By adding a small delay to the `enter` state, we can optionally separate the animations: ```javascript const RoutesContainer = posed.div({ enter: { opacity: 1, delay: 300 }, exit: { opacity: 0 } }); ``` ## Content transitions With that animation in place, we can go a step further and animate the entering and exiting content. First, add a `beforeChildren: true` property to the `RoutesContainer` `enter` pose. This will ensure that it finishes fading in before we animate any of its children: ```javascript const RoutesContainer = posed.div({ enter: { opacity: 1, delay: 300, beforeChildren: true }, exit: { opacity: 0 } }); ``` Let's animate our first page. Open `pages/about.js`. You'll see that we've pre-made two posed components, `Container` and `P`, and used those to markup the `About` component. In the markup, the `P` components are children of `Container`, and `Container` is a child of `RoutesContainer`. So when `RoutesContainer` changes to `enter` and `exit` poses, these will flow through each of the posed components in turn, allowing us to animate them. Add `enter` and `exit` poses to `P`: ```javascript const P = posed.p({ enter: { x: 0, opacity: 1 }, exit: { x: 50, opacity: 0 } }); ``` Now, when you enter and exit the `About` page, all the paragraphs will animate in and out. But they all animate in together. It'd be nice to stagger these animations instead. That's why we've made `Container` a posed component too. It's not going to do animation itself, it's just going to control the animation of its children with the `staggerChildren` property: ```javascript const Container = posed.div({ enter: { staggerChildren: 50 } }); ``` Try entering and exiting the `About` page again. The paragraphs stagger in. We can try the same trick on the `Home` page. Open `pages/home.js` and replace the `ListContainer` and `Item` posed components with this: ```javascript const ListContainer = posed.ul({ enter: { staggerChildren: 50 }, exit: { staggerChildren: 20, staggerDirection: -1 } }); const Item = posed.li({ enter: { y: 0, opacity: 1 }, exit: { y: 50, opacity: 0 } }); ``` This time, `ListContainer`'s `exit` pose has a new property, `staggerDirection`. Setting this to `-1` reverses the stagger, so elements animate out from the bottom up. ## Conclusion We've learned how to use Pose with Reach Router to do a quick and simple fade transition, as well as animating across children to provide unique effects for every page. We've also seen how posed components can be used not only to animate, but to sequence the animations of their children. ================================================ FILE: packages/popmotion-pose/docs/learn/react/route-transitions-react-router.md ================================================ --- title: "Tutorial: React Router 4" description: How to make route transition animations with React Pose and React Router category: react --- > React Pose has been **deprecated** in favour of [Framer Motion](https://framer.com/motion). [Read the upgrade guide](https://www.framer.com/api/motion/migrate-from-pose/) # Route transitions with React Router > **Note:** This tutorial is for **React** Router. Users of **Reach** Router will want to use the [Reach Router tutorial](/pose/learn/route-transitions-reach-router). Route transitions in React are notoriously fiddly. With [Pose](/pose) and [React Router](https://reacttraining.com/react-router/), they can be pretty simple. We're going first learn how to make a simple fade transition between two routes. Then, as Pose has the ability to coordinate animations throughout the component tree, we'll show how to animate each route differently, with content staggering in and out. Here's what we'll be making: ## Sandbox We've created a [CodeSandbox](https://codesandbox.io/s/m3z4pm0myx) example preloaded with React Pose and React Router, for you to fork and follow along. It's set up in a standard way according to the [React Router docs](https://reacttraining.com/react-router/web/guides/philosophy), so if you're not already familiar with React Router, it's worth reading the overview there. ## Fade transition So, let's animate! The first animation we'll add is a simple fade transition. When a user clicks a link, we want to fade the existing content out, and the new content in. We'll start by importing React Pose. We're going to be using both `posed` and `PoseGroup`. So at the end of your imports, add: ```javascript import posed, { PoseGroup } from 'react-pose'; ``` Now, let's create a posed component with our two visual states, `enter` and `exit`. After the line above, add: ```javascript const RoutesContainer = posed.div({ enter: { opacity: 1 }, exit: { opacity: 0 } }); ``` We can now use this to wrap our `Switch` component: ```javascript {/* ...routes */} ``` To animate this component between the two `enter` and `exit` states, we can use the `PoseGroup` component. `PoseGroup` tracks the entering and exiting of child components (as well as reordering), and will animate them in an out. Any components exiting will only be physically removed from the tree once they've finished their `exit` animation. ```javascript {/* ...routes */} ``` Ah, but wait! If you try and click between pages, there's still no animation. What's up? Without passing `RoutesContainer` a `key`, `PoseGroup` doesn't know that it has a new child, so it can't animate anything. Let's use React Router's `location.key` prop as our key: ```javascript ``` Now, when you change routes, the content will fade in and out! The two pieces of content currently fade on top of each other. By adding a small delay to the `enter` state, we can optionally separate the animations: ```javascript const RoutesContainer = posed.div({ enter: { opacity: 1, delay: 300 }, exit: { opacity: 0 } }); ``` ## Content transitions With that animation in place, we can go a step further and animate the entering and exiting content. First, add a `beforeChildren: true` property to the `RoutesContainer` `enter` pose. This will ensure that it finishes fading in before we animate any of its children: ```javascript const RoutesContainer = posed.div({ enter: { opacity: 1, delay: 300, beforeChildren: true }, exit: { opacity: 0 } }); ``` Let's animate our first page. Open `pages/about.js`. You'll see that we've pre-made two posed components, `Container` and `P`, and used those to markup the `About` component. In the markup, the `P` components are children of `Container`, and `Container` is a child of `RoutesContainer`. So when `RoutesContainer` changes to `enter` and `exit` poses, these will flow through each of the posed components in turn, allowing us to animate them. Add `enter` and `exit` poses to `P`: ```javascript const P = posed.p({ enter: { x: 0, opacity: 1 }, exit: { x: 50, opacity: 0 } }); ``` Now, when you enter and exit the `About` page, all the paragraphs will animate in and out. But they all animate in together. It'd be nice to stagger these animations instead. That's why we've made `Container` a posed component too. It's not going to do animation itself, it's just going to control the animation of its children with the `staggerChildren` property: ```javascript const Container = posed.div({ enter: { staggerChildren: 50 } }); ``` Try entering and exiting the `About` page again. The paragraphs stagger in. We can try the same trick on the `Home` page. Open `pages/home.js` and replace the `ListContainer` and `Item` posed components with this: ```javascript const ListContainer = posed.ul({ enter: { staggerChildren: 50 }, exit: { staggerChildren: 20, staggerDirection: -1 } }); const Item = posed.li({ enter: { y: 0, opacity: 1 }, exit: { y: 50, opacity: 0 } }); ``` This time, `ListContainer`'s `exit` pose has a new property, `staggerDirection`. Setting this to `-1` reverses the stagger, so elements animate out from the bottom up. ## Conclusion We've learned how to use Pose with React Router to do a quick and simple fade transition, as well as animating across children to provide unique effects for every page. We've also seen how posed components can be used not only to animate, but to sequence the animations of their children. ================================================ FILE: packages/popmotion-pose/docs/learn/react/ui-events.md ================================================ --- title: UI events and interactions description: Trigger animations based on UI events like drag, press, hover and focus category: react next: react-exit-enter-transitions --- > React Pose has been **deprecated** in favour of [Framer Motion](https://framer.com/motion). [Read the upgrade guide](https://www.framer.com/api/motion/migrate-from-pose/) # UI events and interactions Pose can be used to power and animate interactions. Currently, it supports the following events: drag, press, hover and focus. In this tutorial, we'll take a look at each. ## Drag With Pose, making an element draggable is as simple as passing `draggable: true` to the config: ```javascript const Box = posed.div({ draggable: true }); ``` `true` sets both axis to draggable, but we can select a single axis to drag by setting it to `'x'` or `'y'`. ### Boundaries We can add boundaries to the range of motion with the `dragBounds` property. It accepts `top`, `left`, `bottom` and/or `right`, measured in pixels or percentages: ```javascript const Box = posed.div({ draggable: 'x', dragBounds: { left: '-100%', right: '100%' } }); ``` ### Poses When a posed component is draggable, two new poses are fired. While dragging is active, the `drag` pose takes effect. ```javascript const Box = posed.div({ draggable: true, init: { scale: 1 }, drag: { scale: 1.2 } }); ``` **Note:** A current limitation with the `drag` pose is you must leave `transition` **undefined**. When dragging finishes, by default all values revert to their previous pose. Optionally, a `dragEnd` pose can be defined: ```javascript const Box = posed.div({ draggable: true, init: { scale: 1 }, drag: { scale: 1.2 }, dragEnd: { scale: 0.5 } }); ``` You can use this pose to animate `x` and `y`, too. For instance, you could make the object spring back to its origin: ```javascript const Box = posed.div({ draggable: true, init: { scale: 1 }, drag: { scale: 1.2 }, dragEnd: { x: 0, y: 0, transition: { type: 'spring' } } }); ``` ### Events We can respond to drag start, drag end, and value change events to trigger updates in our UI. You can provide callbacks to the `onDragStart` and `onDragEnd` props. These fire with the originating mouse or touch events. To track changes in `x` and `y`, the `onValueChange` prop accepts a map of callbacks, one for each animating value. ```javascript const Box = posed.div({ draggable: 'x' }) export default ({ onStart, onEnd, onDrag }) => ( ); ``` ## Press With `pressable` set to `true`, you can respond to mouse and touch down events with the `press` pose: ```javascript const Box = posed.div({ pressable: true, init: { scale: 1 }, press: { scale: 0.8 } }); ``` By default, when pressing ends, values will return to their previous pose. You can **optionally** set a `pressEnd` pose to override this. ### Events We can respond to press start and end events with the `onPressStart` and `onPressEnd` callbacks: ```javascript const Box = posed.div({ pressable: true, init: { scale: 1 }, press: { scale: 0.8 } }); export default ({ onStart, onEnd }) => ( ); ``` You might prefer to use this over React's in-built `onMouseDown`/`onMouseUp` etc events because Pose's event handling method avoids the annoying situation where a user presses an element, and only stops pressing once they've moved their pointer outside the element. Those `onMouseUp`/`onTouchEnd` callbacks only fire if the pointer is still on the triggering element, whereas `onPressEnd` will fire anywhere in the page. ## Hover Components can respond to hovers by settings `hoverable` to `true`. This will enable use of the `hover` pose: ```javascript const Box = posed.div({ hoverable: true, init: { scale: 1, boxShadow: '0px 0px 0px rgba(0,0,0,0)' }, hover: { scale: 1.2, boxShadow: '0px 5px 10px rgba(0,0,0,0.2)' } }); ``` By default, when hovering ends, values will return to their previous pose. You can **optionally** set a `hoverEnd` pose to override this. ## Focus Focusable elements can animate into the `focus` pose by setting `focusable: true`: ```javascript const Box = posed.div({ focusable: true, init: { color: '#aaa', outlineWidth: '0px', outlineOffset: '0px', scale: 1 }, focus: { color: '#000', outlineWidth: '12px', outlineOffset: '5px', outlineColor: '#AB36FF', scale: 1.2 } }); ``` By default, when hovering ends, values will return to their previous pose. You can **optionally** set a `blur` pose to override this. ================================================ FILE: packages/popmotion-pose/docs/learn/react-native/native-animating-children.md ================================================ --- title: Animating children description: How to orchestrate animations across multiple elements category: react-native next: native-dragging --- # Animating children Traditionally, coordinating animations across multiple children has been a delicate, involved process. With Pose, animating multiple components is as simple as animating one. It looks like this: ```javascript export default ({items}) => ( {items.map(item => )} ) ```