Open akre54 opened 9 years ago
Yep! The next next version will recognize special key names in your object, such as height
and [insert your whatever path attribute here]. height
, for example, will interpolate in the domain of integers since browsers round the number anyway. Got an API in mind for path?
Awesome to hear! I was thinking more like an arbitrary value interpolator, so that as long as you return the values in a way your own code can use you'd be fine.
<path>
's d
attribute takes a string in the form "M50,100...", where the numbers are distinct values meant to be read separately. I'd like to be able to use d3.interpolateString
to interpolate each value piecewise. This could work similar for 12px
-> 24px
or other united strings.
Now either react-motion could add its own interpolateString function, or it could allow the interpolator to be overridden, but by default use interpolateNumber behind the scenes. My preference is towards the latter.
cc @threepointone who's interested in this.
I can try to whip something up on Monday if nobody gets to it before then.
Basically I'd like:
render() {
var prevD = line(this.state.prevData);
var d = line(this.props.data);
<Motion defaultValue={{val: prevD}} endValue={{val: d}} interpolator={d3.interpolateString}>
{interpolated => <path d={interpolated.value} />
</Motion>
}
The tests on master appear to be broken, but would something like this work?
diff --git a/src/components.js b/src/components.js
index 3e50106..23a3eb9 100644
--- a/src/components.js
+++ b/src/components.js
@@ -151,6 +151,12 @@ export default function components(React) {
children: PropTypes.func,
},
+ getDefaultProps() {
+ return {
+ interpolate: interpolateValue,
+ }
+ },
+
getInitialState() {
const {defaultStyle, style} = this.props;
const currentStyle = defaultStyle || style;
@@ -195,13 +201,14 @@ export default function components(React) {
},
animationRender(alpha, nextState, prevState) {
+ const {interpolate} = this.props;
// `this.hasUnmounted` might be true in the following condition:
// user does some checks in `style` and calls an owner handler
// owner sets state in the callback, triggering a re-render
// unmounts Motion
if (!this.hasUnmounted) {
this.setState({
- currentStyle: interpolateValue(
+ currentStyle: interpolate(
alpha,
nextState.currentStyle,
prevState.currentStyle,
@@ -242,6 +249,7 @@ export default function components(React) {
return {
willEnter: (key, value) => value,
willLeave: () => null,
+ interpolate: interpolateValue,
};
},
@@ -297,10 +305,11 @@ export default function components(React) {
},
animationRender(alpha, nextState, prevState) {
+ const {interpolate} = this.props;
// See comment in Motion.
if (!this.hasUnmounted) {
this.setState({
- currentStyles: interpolateValue(
+ currentStyles: interpolate(
alpha,
nextState.currentStyles,
prevState.currentStyles,
(Tests on master should be passing btw, just checked)
Ah ok, I just pulled down the latest. I'll add some tests then send a pull in a sec.
I'm having a hard time wrapping my head around the val
and currentStyles
parts of the API. Why not have transitions simply be generic interpolators capable of transitioning between arbitrary values?
@akre54 Did you manage to get custom interpolators working, or did you decide to go a different direction?
@akre54 as @ccblaisdell mention also keen to know if you found a solution.
I started changing some things around and got into the weeds before just falling back to using d3 for child animations, which has some pretty severe drawbacks.
We're still really interested in this (mostly for animating between reparented components), but I haven't had time in a while to look into it.
Thanks for the update @akre54 👍
You can use custom interpolators just fine with React Motion by treating styles as arbitrary values (I'm not a fan of the term styles because of this). For example: http://bl.ocks.org/herrstucki/27dc76b6f8411b4725bb. Of course it would be nice if RM would support them directly.
Also, interpolators should be specific to each style property. One interpolator per component wouldn't work. I could imagine two ways:
a) A map of interpolators:
<Motion
defaultStyle={{color: '#f00'}}
style={{
color: spring('#0f0')
}}
interpolate={{
color: interpolateHcl
}}
>{...}</Motion>
b) or as part of the spring config:
<Motion
defaultStyle={{color: '#f00'}}
style={{
color: spring('#0f0', {
interpolate: interpolateHcl
})
}}
>{...}</Motion>
I prefer the second way (that way it can be extracted into a wrapper function or composed).
Hi, any update about interpolating SVG path? Thanks a lot!
This would be great to have. I think I would lean way more towards an interpolate
key for spring options.
I would also wager that most people would love to be able to just drop in compatible interpolators as plugins at a higher level and have them work out of the box. D3 has tons of interpolators available, we could honestly just make an i/o to utilize them.
As easy as it would be to provide the interpolator per spring config, it would be so much cooler if we were able to handle interpolators automagically. Imagine utilizing a similar algorithm as https://github.com/d3/d3-interpolate#interpolate, using d3 interpolate itself haha!
Thoughts? This is a large weakness of React-Motion that is talked about most often. If we could nail this, I think you could have a lot of people considering its usage in many other scenarios.
Am I right to assume that we would need to build a standardized interpolator interface to replace the operations located here: https://github.com/chenglou/react-motion/blob/master/src/Motion.js#L173?
I was able to work up a prototype abstraction of <Motion />
to work with d3-interpolators. By no means is it optimized, but it's a pretty cool start. It works by mapping non-numeric value changes to auto-incrementing integers, then it coerces those integers back to a percentage between the old and new value. Then it applies the d3-interpolator of choice behind the scenes, defaulting to the standard auto interpolator.
I would love to get some feedback on this, as it seems like this could be a start to supporting non-numeric values.
Right now I'm using it to interpolate colors, paths, and strings in d3.
(Update to support interrupted updates)
import React from 'react'
import { Motion, spring } from 'react-motion'
import { interpolate } from 'd3-interpolate'
export default React.createClass({
oldValues: {},
newInters: {},
currentStepValues: {},
stepValues: {},
stepInterpolators: {},
render () {
const {
style,
children,
...rest
} = this.props
const MagicSpring = (value, config) => {
if (typeof value !== 'number') {
return {
value,
config,
interpolator: (config && config.interpolator) ? config.interpolator : interpolate
}
}
return spring(value, config)
}
const resolvedStyle = style(MagicSpring)
for (let key in resolvedStyle) {
if (
// If key is a non-numeric interpolation
resolvedStyle[key] &&
resolvedStyle[key].interpolator
) {
// Make sure the steps start at 0
this.currentStepValues[key] = this.currentStepValues[key] || 0
if (
// And the value has changed
typeof this.newInters[key] === 'undefined' ||
resolvedStyle[key].value !== this.newInters[key].value
) {
// Save the new value
this.newInters[key] = resolvedStyle[key]
// Increment the stepInterValue for this key by 1
this.stepValues[key] = this.currentStepValues[key] + 1
// Set up the new interpolator
this.stepInterpolators[key] = this.newInters[key].interpolator(
this.oldValues[key],
this.newInters[key].value
)
}
// Return the spring with the destination stepValue and spring config
resolvedStyle[key] = spring(this.stepValues[key], this.newInters[key].config)
// console.log(resolvedStyle[key])
}
}
return (
<Motion
{...rest}
style={resolvedStyle}
>
{values => {
const newValues = {}
for (let key in values) {
if (this.stepValues[key]) {
// Save the currentStepValue
this.currentStepValues[key] = values[key]
// Figure the percentage
let percentage = this.currentStepValues[key] - this.stepValues[key] + 1
// Save the current value and replace the value in the interpolated object
this.oldValues[key] = newValues[key] = this.stepInterpolators[key](percentage)
}
}
return children({
...values,
...newValues
})
}}
</Motion>
)
}
})
@tannerlinsley that looks amazing! @chenglou is that something you would accept as a start to a PR? This seems extremely powerful. I'm willing to help where I can 😁
I've just updated my snippet above to support interrupted updates. All this means is that it should function exactly like react motion does, it will always shift inertia towards the new value from where the current value is :)
@chenglou, honestly it would be so nice to have access to the percentage from the last issued float to the destination float. The wrapper above works pretty well for the simple <Motion />
component, but quickly becomes unwieldy if the same concept is applied to <TransitionMotion />
. There is so much duplicate tracking of values that it seems like a fair ask to include in the core. Really, anything that would expose more flexibility. Thoughts?
Sorry super busy right now. Leaving a comment here. Will check again next week.
I got really stuck working on adding interpolation to the TransitionMotion component via the wrapper before I had to dig deeper into the source. The architecture I needed to achieve this flexible interpolation took me on a journey that ended up porting as much of the animation cycle and physics stepper over to a new repo called react-move. Anyone interested can take a look at the source there. It supports interpolating anything that d3 can handle, duration/easing motion, and staggering. It does not yet support chain staggering (eg. the chat heads example) yet. It also does not yet account fo velocity reversal for numbers, but should soon. @chenglou, we should chat very soon.
I am trying to animate an SVG
path
element'sd
attribute usingd3.interpolate
. It looks like react-motion only supports numerical values at the moment, and it also looks like there isn't a way to set a custom interpolator function. Any plants to fix either of these?