Closed ealvarezk closed 6 years ago
i think it needs to know how high the element is. I remember struggling with this since i began coding for the web, many years ago. There are some solutions today, flexbox, grid, etc. for instance 0fr - 1fr is numerical, hence it can be animated. Otherwise, react-animate-height looks interesting but it's hard to understand what it's doing. If it doesn't blow out the weight of this lib and if it's not too hacky, perhaps it would make sense implementing it, but would need some help - PRs definitively welcome.
This might also relate with https://github.com/drcmda/react-spring/issues/9. Might also be possible to use https://github.com/souporserious/react-measure to do the measurement.
I remember long about this issue and long discussion on how to do this with react-motion but don't recall a solution being found. Lastly, there is react-collapse that is supposed to work with react-motion and variable/dynamic height elements.
Thanks for these links, very interesting! If we can manage to break it down here, what must be done, and how, i'd open to add it wherever it makes sense, perhaps the animated-core or a helper. In the react-motion issue they were talking about first rendering the thing, calculating height, then overwriting 'auto'. I haven't been fooling around much with the dom lately, but couldn't you measure dom-nodes even if they weren't mounted?
Basically, yes, to the issue here, and yes, i'll be happy to add it, but looking for help and something like cooking receipe on how to pull it off (best with the exact dom calls and functions needed).
Fished this off stackoverflow:
HTML
<div class="frame">
<p>some text some text some text some text</p>
</div>
CSS
.frame {
width: 120px;
display: none;
}
.offscreen {
position: fixed !important;
left: -9999px !important;
display: inline !important;
}
JavaScript
$('.frame').addClass('offscreen');
alert('The hieght of \'p\' is: ' + $('p').height() + 'px');
$('.frame').removeClass('offscreen');
Another thing that isn't clear right now is that from/to/enter/etc do not necessarily denote styles. So, height: 'auto'
means pretty much nothing at all, it could be anythingReally: 'auto'
. Do we simply assume auto
means getting height? But then what about width in a horizontal Transition.
Maybe it shouldn't be height
per se, but perhaps these values should accept functions ... then we could inject helpers like so:
import { autoHeight, autoWidth } from 'react-spring/dist/addons'
<Spring from={{ something: 0 }} to={{ something: autoHeight }} />
@drcmda I was thinking about how integrating a function for this purpose might work before reading your comment, so might be on to something. I was thinking of an explicit measure
function you could pass where you either pass the DOM node and get the measurements yourself (using node. getBoundingClientRect
or node.clientWidth
/etc or you pass in the dimensions into this function to make it easier (pass in the box
(top/left/width/heigh, basically the results of getBoundingClientRect
).
I think the way react-morph approaches it (for shared element transitions) where you apply the from
and to
props to "mark" which elements to measure is interesting...
<ReactMorph>
{({ from, to, fadeIn, go }) => (
<div>
<a onClick={() => go(1)}>
<strong {...from("title")}>ReactMorph 🐛</strong>
<br />
<p {...from("description")}>Morphing transitions was never so easy!</p>
</a>
<div>
<h1 {...to("title")}>ReactMorph 🦋</h1>
<br />
<h2 {...to("description")}>Morphing transitions was never so easy!</h2>
<a onClick={() => go(0)} {...fadeIn()}>
Back
</a>
</div>
</div>
)}
</ReactMorph>
This isn't really what's being asked here, but the technique might be the same.
Some code worth taking a look at
@ealvarezk @techniq
Made a first rough draft:
It's super messy but the essentials are there. animated-core "accepts" the auto
value, meaning it will just channel through without trying to animate anything. Spring.render will render into a portal when it detects that any value is set to auto, once the portal is created it obtains a ref into the dom node, calls clientWidth/height in there - and theoretically now it would have to mix/inject these values back into its own set of properties and start the animation anew, but i haven't gotten to that part yet.
My worries right now are a. this will tie it to react-dom (what about react-native or other platforms?) and b. it's not as generic as it probably should be.
@drcmda the tie to react-dom would be a bummer. I know for VX we are looking into supporting react-native and <canvas>
along with <svg>
. I know the measurement side will likely have to be DOM-specific, but it would be nice if react-spring could stay agnostic / module (although if you don't use 'auto' I don't guess it would break these other environments).
Any way to pull what you have into the autoHeight
/measure
/etc function and maybe make it part of react-spring-dom
or something?
Thought about it some more, i think there might be a more composable way:
import { Spring, animated } from 'react-spring'
import MeasureOnce from 'react-measure-once'
// Variant #1
const App = (
<Spring native from={{ toggle: 0 }} to={{ toggle: 1 }}>
{props => (
<MeasureOnce>
{({ offscreen, height }) => (
<animated.div
style={{
height: offscreen ? 'auto' : props.toggle.interpolate({ range: [0, 1], output: [0, height] }),
width: 300,
overflow: 'hidden',
}}>
Lorem ipsum dolor sit amet, id mea quas prompta conceptam, timeam accusata efficiendi mea ei, eu vide praesent quo.
</animated.div>
)}
</MeasureOnce>
)}
</Spring>
)
// Variant #2
const App = (
<Spring native from={{ toggle: 0 }} to={{ toggle: 1 }} inject={MeasureOnce}>
{({ offscreen, toggle, height }) => (
<animated.div
style={{
height: toggle.interpolate({ range: [0, 1], output: [0, offscreen ? 'auto' : height] }),
width: 300,
overflow: 'hidden',
}}>
Lorem ipsum dolor sit amet, id mea quas prompta conceptam, timeam accusata efficiendi mea ei, eu vide praesent quo.
</animated.div>
)}
</Spring>
)
MeasureOnce could be a generic component, it just renders Offscreen and injects a flag so that the wrapped component knows that it's being rendered for measurement, from then on it renders for real and injects width and height so that the wrapped component knows its bounds. We could give spring a generic inject
prop, so that it wraps its output into the inject component. Height then becomes a 0-1 toggle, which is way more data-friendly and reflects state better.
This model could be used for many other purposes.
Alright, working nicely!
This is how a auto-height spring toggle could look like:
<Spring native from={{ toggle: 0 }} to={{ toggle: 1 }} inject={MeasureOnce}>
{({ toggle, offscreen, height }) => (
<animated.div
style={{
height: offscreen ? 'auto' : toggle.interpolate({ range: [0, 1], output: [0, height] }),
It's sure a bit more than just saying height: 'auto' and everything works like magic, but then again, magic will never work in all cases (grids, etc.), but this actually could since it's explicit. Maybe the interpolator could get away with some easier defaults, perhaps it could assume that toggle.interpolate(0, height)
applies to a range of 0-1.
The MeasureOnce component, which would be part of the addons isn't much more than this:
class MeasureOnce extends React.PureComponent {
constructor() {
super()
this.width = undefined
this.height = undefined
this.portal = document.createElement('div')
this.portal.style.cssText = 'position:static;visibility:hidden;'
document.body.appendChild(this.portal)
}
measure = ref => {
if (ref) {
this.height = ref.clientHeight
this.width = ref.clientWidth
document.body.removeChild(this.portal)
this.forceUpdate()
}
}
render() {
const { children, instance, ...rest } = this.props
const result = children({ ...rest, offscreen: this.width === undefined, height: this.height, width: this.width })
return this.width === undefined ? ReactDOM.createPortal(<div ref={this.measure}>{result}</div>, this.portal) : result
}
}
Would that be to your liking?
@drcmda killing it as always 😄
All of this could be encapsulated into a <Collapse />
component in userland or as a separate addon/module.
How often is MeasureOnce
injected? I'm assuming once per toggle? I would figure it would need to be so it can re-measure if the children changes size (adds text, reflow layout, etc).
It would be called on every render, basically wrapping it. For the purpose of a Spring it would be enough, because deeper change detection could be done in the Injection class. I'm toying around a bit more meanwhile, and i think there might be a way to do all of this completely automatic, without having to listen to offscreen flags and so on. The injection component would need access to to/from values, then it would render once with the whole state (which includes 'auto') set to "to", then overwrite 'auto' values and let Spring animate.
That sounds great (like animation middleware).
On Sun, Apr 15, 2018, 10:38 AM Paul Henschel notifications@github.com wrote:
It would be called on every render, basically wrapping it. For the purpose of a Spring it would be enough, because deeper change detection could be done in the Injection class. I'm toying around a bit more meanwhile, and i think there might be a way to do all of this completely automatic, without having to listen to offscreen flags and so on. The injection component would need access to to/from values, then it would render once with the whole state (which includes 'auto') set to "to", then overwrite 'auto' values and let Spring animate.
— You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub https://github.com/drcmda/react-spring/issues/30#issuecomment-381411477, or mute the thread https://github.com/notifications/unsubscribe-auth/AAK1RCiKqJeTiKxUMySGG6_xQIeUJpjZks5to1t7gaJpZM4TT-ti .
Something like this should also work for a react morph-like API where the from and to are calculated (off different elements). Might also work to pass from/to into the child render function... But needs more thought.
On Sun, Apr 15, 2018, 10:59 AM Sean Lynch techniq35@gmail.com wrote:
That sounds great (like animation middleware).
On Sun, Apr 15, 2018, 10:38 AM Paul Henschel notifications@github.com wrote:
It would be called on every render, basically wrapping it. For the purpose of a Spring it would be enough, because deeper change detection could be done in the Injection class. I'm toying around a bit more meanwhile, and i think there might be a way to do all of this completely automatic, without having to listen to offscreen flags and so on. The injection component would need access to to/from values, then it would render once with the whole state (which includes 'auto') set to "to", then overwrite 'auto' values and let Spring animate.
— You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub https://github.com/drcmda/react-spring/issues/30#issuecomment-381411477, or mute the thread https://github.com/notifications/unsubscribe-auth/AAK1RCiKqJeTiKxUMySGG6_xQIeUJpjZks5to1t7gaJpZM4TT-ti .
Got it more or less down: https://codesandbox.io/s/pmjomxn60
The middleware looks like this: https://github.com/drcmda/react-spring/blob/inject/src/addons/fixauto.js
It works with transition (and all other primitives) out of the box, already tested it. But transition itself has a bug i haven't noticed before, have to fix that one first as it stands in the way of fluid autoFix.
Looks great. I hope to get some time in the coming weeks to experiment more (curious if I can translate one of my react-flip-move examples using this now, and if shared element transitions are possible as well). Thanks again for all the hard work.
On Sun, Apr 15, 2018, 6:06 PM Paul Henschel notifications@github.com wrote:
Got it more or less down: https://codesandbox.io/s/pmjomxn60
The middleware looks like this: https://github.com/drcmda/react-spring/blob/inject/src/addons/fixauto.js
— You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub https://github.com/drcmda/react-spring/issues/30#issuecomment-381441824, or mute the thread https://github.com/notifications/unsubscribe-auth/AAK1RPI9Ct09wlNVWw2MhetTc5EjCBjfks5to8RKgaJpZM4TT-ti .
@drcmda I had literally a few minutes to play around with this today but wanted to test out the calculation on children change and works great! https://codesandbox.io/s/yk7o70v0wv
Still need to find time for the other examples (really curious about how these will port: https://codepen.io/techniq/pen/qNrYYb / https://codepen.io/techniq/pen/WxaxaR) Hopefully I can get some time this week to give it a shot.
Awesome!
I had some idea for this. animated is made of targets, we're using react-dom atm. It could auto-inject for this target. then nothing would be needed, no addons, no manual injects.
In the last update i've made some preparations for this. middleware for instance can channel through if it doesn't want to mutate (for instance if fixAuto doesn't find auto-values). In that case spring behaves like it always would.
I'll have this wrapped up in the next two days. From then on 'auto' should be solved. BTW, @ealvarezk in the latest stable it's already in there. And it should work flawlessly with Transitions. The inject is under addons/convertAuto. But yeah, injects won't be needed anymore in the next update.
@techniq would you like to try out writing a primitive for flip? I have read a blog about it now, but still scratching my head over what it actually does. I guess i've just never had that problem before.
@drcmda FLIP itself is mostly for performance reasons so you only use composite step style attributes (and do not perform a layout or paint step) which basically boils down to only using transform
and opacity
to get the end result (using scale to change width/height and translate for x/y basically). You can see more details on which style properties cause which of the steps (layout, render, composite) here - https://csstriggers.com/ but that's the gist.
Since react-spring readers in javascript and not CSS, some of the concepts do not apply here, but generally just calculating the from/to positions dynamically.
I'm by no means an expert (I mostly just tinker/hack in this space on occasion) but I'll see what I can do. I'm literally swamped with my work load right now (and baseball season just started up for boys so evenings are consumed) but I'd love to help out as much as I can.
My goal is to try to port those codesandbox examples and see how far I can get. I'm figuring I'll need to write another middleware that instead of just translating 'auto' to a concrete value, calculating x/y/width/height
for both from
and to
(basically an auto for all).
Thanks again for all the awesome work. It's really exiting work.
Btw, I know Elijah Meeks was asking the other day if React Native was supported, and while I haven't tested myself, I was pretty sure you planned to support it:
I'm also curious how <canvas />
will work with it, as for vx
we plan to support it as well as svg and react-native (which is WIP)
@techniq animated comes from react-native, it would work - just needs a few touches. Spring
is tied atm to targets/react-dom. But just loosely because it references some stuff. I think only 3 things are different between the targets: requestAnimationFrame, cancelAnimationFrame, and the element names (div, span, ..., View, Text, ...). In animated you pull the lib like so: import Animated from 'animated/targets/react-dom'
but i wanted to make it easier.
If Airbnb devs are interested, they could help out if they wanted, and pretty much get the lib they need. That's actually the reason i made this, because at work i just couldn't cope any longer with what was already there. If they feel the same, and have react-native experience (i don't), it wouldn't mean that much work to get it up and running, but definitively asking for help here.
Sounds good. I'll pass the word along. Elijah works at Netflix btw (the VX devs mostly work at Airbnb though, except me)
On Tue, Apr 17, 2018, 2:34 AM Paul Henschel notifications@github.com wrote:
@techniq https://github.com/techniq animated comes from react-native, it would work - just needs a few touches. Spring is tied atm to targets/react-dom. But just loosely because it references some stuff. I think only 3 things are different between the targets: requestAnimationFrame, cancelAnimationFrame, and the element names (div, span, ..., View, Text, ...). In animated you pull the lib like so: import Animated from 'animated/targets/react-dom' but i wanted to make it easier.
If Airbnb devs are interested, they could help out if they wanted, and pretty much get the lib they need. That's actually the reason i made this, because at work i just couldn't cope any longer with what was already there. If they feel the same, and have react-native experience (i don't), it wouldn't mean that much work to get it up and running.
— You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub https://github.com/drcmda/react-spring/issues/30#issuecomment-381863705, or mute the thread https://github.com/notifications/unsubscribe-auth/AAK1RMvXOgAafB0Y1pGQKm40jfPZ9k7Cks5tpYzkgaJpZM4TT-ti .
@ealvarezk @techniq i'm closing this for now, it's solved in 4.1.8: https://codesandbox.io/s/pmjomxn60
Nothing needed, no injects, wraps ... just use 'auto'
Will this work with Transition
as well? Like for example for the enter
prop
@janhartmann it should. All primitives basically revolve around Spring
so when that one can do something everything should be able to do it. I'm pretty sure i tried it, too - but i can't remember which sandbox that was.
Hmm, ok. If I do this:
<section className={classes.root}>
<Transition
keys={items.map(item => item.id)}
from={{ opacity: 0, height: 0 }}
enter={{ opacity: 1, height: "auto" }}
leave={{ opacity: 0, height: 0 }}
>
{items.map(item => styles => (
<div style={styles}>
<FeedItem>
{this.getContentType(item)}
</FeedItem>
</div>
))}
</Transition>
</section>
It fails with a ton of errors in console:
Uncaught TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'.
at ref (react-spring.es.js:1125)
at commitAttachRef (react-dom.development.js:9918)
at commitAllLifeCycles (react-dom.development.js:11464)
at HTMLUnknownElement.callCallback (react-dom.development.js:100)
at Object.invokeGuardedCallbackDev (react-dom.development.js:138)
at invokeGuardedCallback (react-dom.development.js:187)
at commitRoot (react-dom.development.js:11594)
at completeRoot (react-dom.development.js:12502)
at performWorkOnRoot (react-dom.development.js:12452)
at performWork (react-dom.development.js:12370)
Ok this might be because of some changes i have made recently. getComputedStyle is new. Do you have a sandbox i could inspect, that would be great!
Okay, I forked the Spring version and modified it for: https://codesandbox.io/s/x988w9pmxq
Thanks! Will take a day, since i'm super busy atm, but tomorrow it'll hopefully be fixed. In the meantime, if it's very pressing, it will most likely start working if you go back a little, something around 5.x.x.
Dont worry, I am in no hurry! Take your time - I understand open source can be tough together with other projects. :-)
@janhartmann should be fixed in 5.1.8
The fixAuto
doesn't work with styled-components
probably due to the way styled-components
handle refs (i.e. innerRef). I forked the code sandbox and added a styled-components
example https://codesandbox.io/s/mzm40lw178
My current work around is to wrap the styled
component with a <div>
element.
@epilande Oh my, i see, this is going to be more complex ... I made a fix in 5.1.9
Your examples started working, i saw a 3 column grid with the elements side by side. If you run into styling issues or wrong height/width, consider this: https://github.com/drcmda/react-spring/blob/master/API-OVERVIEW.md#animating-auto
Just going to throw this in here for anyone wanting a simpler solution, you can animate max-height
or in js maxHeight
from 0 to a number bigger than you'd need, this way the height animates and it will not exceed its auto height.
@idesignpixels Nice trick! That will definitely be in the docs for v9. :)
I was reading the documentation and I couldn't find a way to set auto as enter height prop, as you can see in this example:
I think that will make easier to add elements as children without worring the height of those elements. If it helps you there is another component, that can do that https://github.com/Stanko/react-animate-height