According to react documentation only way to animate is by usingReactCSSTransitionGroup. Is there anyway to do animation using only Javascript?
Thanks.
Typical way to do a fadeIn animation is:
define a css-class that animates fadeIn in someway
in componentDidMount(), add a setState() method that adds the faded-in class to your component
your component will then re-render, with the new class. Applying any css animations that you have defined (in css of course)
PS: With simple javascript, you can only add animation when the component appears, or when it updates.
When your component leaves (so if you would want a fade-out animation), it is much, much harder to do that with javascript only.
Finally i found this npm package It has all the basic animation.
for fadeIn animation
constructor(props) {
super(props);
this.state = {
opacity: 0
}
// react state animation wrapper
this._animate = new ReactStateAnimation(this)
}
componentDidMount () {
this._fadeIn();
}
_fadeIn = () => {
this._animate.linearIn('opacity', 0.9, 300);
};
pretty simple!
Related
When the user opens the cart overlay, scrolling of the background shouldn't be possible. I have searched and i can't seem to find a workable way around it.
Here's the code.
class CartMenu extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
};
this.dropdownRef = React.createRef();
}
componentDidMount() {
this.props.setCartCloseFunc(this.closeMenu);
}
setOpenOrClosed() {
this.setState(
(prevState) => ({ open: !prevState.open }),
() => {
this.props.onCartButtonClicked(this.state.open);
}
);
}
This is a snippet of the code. Is there a workaround?
Get the root html element of your app (usually with id="root")
Then when your cart is open, set the root element a overflow: hidden css
And when it's false, set it back to unset
As I understand you problem, you are in a React environment.
What you need here is to make the body not scrollable anymore. Fortunately for you, body isn't in the scope of React, only what's in div#app is (if I remember the basics of React).
You basically need to toggle the overflow: hidden state of the body element, by using JS, or CSS and JS.
Only JS
Using only JS, you need to use this:
// to block the scroll
document.body.style.overflow = "hidden";
// to make it scrollable again
document.body.style.overflow = "unset";
JS and CSS combined
If you prefer using a class toggle and CSS, you can use the classList API.
// to block the scroll
document.body.classList.add('no-scroll');
// to make it scrollable again
document.body.classList.remove('no-scroll');
with this CSS
body.no-scroll {
overflow: hidden;
}
Good to know
I know some version of mobile OS doesn't like this way.
If you want, you can do the same with this small library: body-scroll-lock
I hope I could help đź‘Ť
I'm struggling with react-spring to fade out a loading screen and then unmount it.
The component unmounts but without animation and I can't figure why. I created a sandbox to illustrate:
https://codesandbox.io/s/nkxjxwo2xl
import React from 'react'
import ReactDOM from 'react-dom'
import { Transition } from 'react-spring'
class Loader extends React.PureComponent {
state = { loaded: false }
componentDidMount() {
setTimeout(() => {
this.setState({ loaded: true })
}, 1000)
}
render() {
const { percentage } = this.props
const styles = {
height: '100vh',
width: '100vw',
background: 'tomato'
}
return (
<Transition native from={{ opacity: 1 }} leave={{ opacity: 0 }}>
{!this.state.loaded &&
(style => (
<div style={Object.assign({}, styles, style)}>
<div>{Math.round(percentage)} %</div>
</div>
))}
</Transition>
)
}
}
ReactDOM.render(<Loader percentage={0} />, document.getElementById('root'))
There are no react-spring tags yet if anyone could create one, I think it would be helpful.
Yep, you fixed it yourself, the animated.div component was missing. I would recommend the use of it, though. Even if your view is small, it will still be rendered out 60 times per second by React otherwise (meaning it will go through all component phases 60 times + render). With native set it renders once and the animation will be applied in a requestAnimationFrame-loop directly in the dom (via instance.style.setProperty, it completely skips React - which makes a difference once your app gets bigger.
I found the solution if anyone finds this question.
Using the native attribute, you'll need to use animated.div (or any animated component) in order to have it animated. Or simply remove the native attribute. In my case, it is a loader which is not often displayed so I went for the easier way by simply removing the native attribute.
More info on the Spectrum react-spring community
I'm trying to setup a Marquee in React if a piece of text is greater than its container but I can't get the correct width of the container, even after the component has rendered.
I read in another answer React “after render” code? that you have to use requestAnimationFrame which I'm trying and it's still not working.
If I log the width of the container it shows a width of 147px which is set using min-width in the stylesheet but the correct width should be 320px which is set using a media query when the screens min-width is 600px.
This is a child component, the parent is rendered inside an iFrame if it makes any difference and the iFrame's width is well over 600px.
The JS:
module.exports = React.createClass({
componentDidUpdate: function () {
// Setup marquee
this.initMarquee();
},
render: function () {
// Setup marquee
this.initMarquee();
var artistName = this.props.artist.artistName;
var trackName = this.props.track.trackName;
return (
<div className="MV-player-trackData no-select" ref="mvpTrackData">
<div className="MV-player-trackData-marquee-wrap" ref="mvpMarqueeWrap">
<div className="MV-player-trackData-marquee" ref="mvpMarquee">
<a className="MV-player-trackData-link no-select" href={this.props.storeUrl} target="_blank">
<span id="mvArtistName">{artistName}</span> – <span id="mvTrackName">{trackName}</span>
</a>
</div>
</div>
</div>
)
},
initMarquee: function () {
if ( typeof requestAnimationFrame !== 'undefined' ) {
//store a this ref, and
var self = this;
//wait for a paint to setup marquee
window.requestAnimationFrame(function() {
self.marquee();
});
}
else {
// Suport older browsers
window.setTimeout(this.marquee, 2000);
}
},
marquee: function () {
var marquee = React.findDOMNode(this.refs.mvpMarquee);
var marqueeWrap = React.findDOMNode(this.refs.mvpMarqueeWrap);
// If the marquee is greater than its container then animate it
if ( marquee.clientWidth > marqueeWrap.clientWidth ) {
marquee.className += ' is-animated';
}
else {
marquee.className = marquee.className.replace('is-animated', '');
}
}
});
The CSS:
.MV-player-trackData-marquee-wrap {
height: 20px;
line-height: 20px;
min-width: 147px;
overflow: hidden;
position: relative;
width: 100%;
#media only screen and (min-width : 600px) {
min-width: 320px;
}
}
I've tried a number of different solutions including laidout and react-component-width-mixin but neither of them work. I tried react component width mixin because in another part of my app I'm trying to get the value of window.innerWidth but that also returns 0 after rendering, unless I set a timeout for around 2 seconds, unfortunately though sometimes 2 seconds isn't long enough due to data loading and other callbacks so this can brake easily.
Any help is really appreciated. Thanks.
Update:
One of the answers correctly pointed out i should be calling this.initMarquee(); inside componentDidMount which I was doing, unfortunately I pasted the wrong code from when I was testing to see if it made a difference calling it inside render. The correct code looks like this:
componentDidMount: function () {
// Setup marquee
this.initMarquee();
},
Unfortunately this doesn't work either, I still receive the incorrect width for marqueeWrap.
Update: 24/06/2015
Just to clarify, this is the marquee effect I'm trying to achieve, only when the text is bigger than its container as it's pointless scrolling it when it is not bigger.
Also here is a link to a Github Issue from the React team speaking about why React renders before the browser paints. - So as that is the case, I want to know how do I reliably get the width of the element in question.
One possible problem that can occur is that your CSS has not loaded yet when componentDidMount fires. This can happen with webpack and including the css in your bundle that also contains your js even if you have included the css before your component in the project.
There are several issues, as you've pointed out, in dealing with the virtual DOM. You definitely don't want to be attempting to use jQuery to manipulate DOM nodes and React is likely to scream at your for attempting to.
There's a library called React-Context which would do exactly what you're asking. You would expose its api to your wrapper component and then be able to listen to events on components within the virtual dom.
This library is a little dusty, however it should work with the code sample you shared.
You should not call this.initMarquee(); in the render() method.
In general, you should not work with the DOM at all in the render() method.
Try to call this.initMarquee(); in the componentDidMount method.
(and I really don't understand the usage of requestAnimationFrame in this case)
I have a list of items with scores, ordered by scores, rendered by react.js as a vertically-oriented list of rectangular items (highest scores at top). Hovers and other clicks on the individual items may show/hide extra info, changing their vertical height.
New information arrives that changes the scores, slightly, making some items rank higher and others lower after a re-sort. I'd like the items to simultaneously animate to their new positions, rather than appear in their new locations instantly.
Is there a recommended way to do this in React.js, perhaps with a popular add-on?
(In a similar past situation using D3, a technique I've used was roughly:
Display the list with item DOM nodes in their natural order, with relative positioning. With relative positioning, other small changes – CSS or JS-triggered – to individual items' extent shift others as expected.
Mutate all the DOM nodes, in a single step, to have their actual relative-coordinates as new absolute coordinates – a DOM change that causes no visual change.
Re-order the item DOM nodes, within their parent, to the new sort order – another DOM change that causes no visual change.
Animate all nodes' top-offsets to their new calculated values, based on the heights of all preceding items in the new ordering. This is the only visually-active step.
Mutate all item DOM nodes back to non-offset relative-positioning. Again, this causes no visual change, but the now-relatively-positioned DOM nodes, in the natural ordering of the underlying list, will handle internal hover/expand/etc style changes with proper shifting.
Now I'm hoping for a similar effect in a React-ish way...)
I just released a module to tackle exactly this problem
https://github.com/joshwcomeau/react-flip-move
It does a few things differently than Magic Move / Shuffle:
It uses the FLIP technique for hardware-accelerated 60FPS+ animations
It offers options to "humanize" the shuffle by incrementally offsetting delay or duration
It handles interruptions gracefully, no weird glitch effects
Bunch of other neat stuff like start/finish callbacks
Check out the demos:
http://joshwcomeau.github.io/react-flip-move/examples/#/shuffle
React Shuffle is solid and up to date. It's inspired by Ryan Florences Magic Move demo
https://github.com/FormidableLabs/react-shuffle
I realise this is a bit of an old question and you've probably found a solution by now, but for anyone else coming across this question, check out the MagicMove library by Ryan Florence. It's a React library to handle the exact scenario you describe: https://github.com/ryanflorence/react-magic-move
See it in action: https://www.youtube.com/watch?v=z5e7kWSHWTg#t=424
Edit: This is broken in React 0.14. See React Shuffle as an alternative, as suggested by Tim Arney below.
I didn't want to use a third-part library for my animations so I implemented the "FLIP" animation technique using React lifecycle methods getSnapshotBeforeUpdate() and componentDidUpdate().
When the list item's props.index changes the old position is captured in getSnapshotBeforeUpdate() and in componentDidUpdate() the css transform: translateY() is applied so that the item appears to be animating from old position to current position.
class TreeListItem extends Component {
constructor(props) {
super(props);
this.liRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps) {
if (prevProps.index !== this.props.index) {
// list index is changing, prepare to animate
if (this.liRef && this.liRef.current) {
return this.liRef.current.getBoundingClientRect().top;
}
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.index !== this.props.index && snapshot) {
if (this.liRef && this.liRef.current) {
let el = this.liRef.current;
let newOffset = el.getBoundingClientRect().top;
let invert = parseInt(snapshot - newOffset);
el.classList.remove('animate-on-transforms');
el.style.transform = `translateY(${invert}px)`;
// Wait for the next frame so we
// know all the style changes have
// taken hold.
requestAnimationFrame(function () {
// Switch on animations.
el.classList.add('animate-on-transforms');
// GO GO GOOOOOO!
el.style.transform = '';
});
}
}
}
render() {
return <li ref={this.liRef}>
</li >;
}
}
class TreeList extends Component {
render() {
let treeItems = [];
if (this.props.dataSet instanceof Array) {
this.props.dataSet.forEach((data, i) => {
treeItems.push(<TreeListItem index={i} key={data.value} />)
});
}
return <ul>
{treeItems}
</ul>;
}
}
.animate-on-transforms {
/* animate list item reorder */
transition: transform 300ms ease;
}
Late to the party here, but we just released AutoAnimate which allows you to add animations for elements entering, leaving, or moving within a DOM element with a single line of code. Works with React, Vue, or any other Javascript framework and is less than 2kb in size.
Open-source and MIT licensed, hope it's helpful!
The best way I can think of to accomplish this off the top of my head would be to first make sure that you give every one of your elements a key as described here:
http://facebook.github.io/react/docs/multiple-components.html#dynamic-children
Next I would define CSS3 animation similar to this:
Animating lists with CSS3
Basically in your render function you would calculate where the element is suppose to go, ReactJS will place the element where it's suppose to be (make sure you give each element the same key every time! This is important to make sure ReactJS reuses your DOM element properly). In theory at least, the CSS should take care of the rest of the animation for you outside of reactjs/javascript.
Disclamer... I've never actually tried this ;)
I just found this library: https://www.npmjs.com/package/react-animated-list, Its very amazing, You just have to pass the children:
import { AnimatedList } from 'react-animated-list';
import { MyOtherComponent } from './MyOtherComponent';
const MyComponent = ({myData}) => (
<AnimatedList animation={"grow"}>
{otherComponents.map((c) => (
<MyOtherComponent key={c.id} />
))}
</AnimatedList>
)
Demo: https://codesandbox.io/s/nifty-platform-dj1iz?fontsize=14&hidenavigation=1&theme=dark
Boom it will work for you.
Because the position of an element depends heavily on the DOM engine, I find it really hard to implement tweening and animating in a pure way within React components. If you want to do it cleanly, I think you need at least: a Store to hold the current position of an element, a Store to hold the next position of an element (or combine it with the previous store), and a render() method that applies the offset margins per element until in the final frame, the next positions become current. How you calculate the next positions in the first place is also a challenge. But given that the elements only swap positions, you could use the Store with the current positions to swap element #0 with element #1, by swapping their offsets in the next positions Store.
In the end it's easier to just use an external library to manipulate the elements' margins after they're rendered.
Just use ReactCSSTransitionGroup.
It's built into the React with Addons package and is perfect for this use case! Just make sure that the component on which you apply the transition has already mounted to the DOM. Use the CSS that is in the linked example for a nice fading transition.
I've created a simple component on reactjs (kind of a value-picker: plunker)
Now, I want the upper and lower parts of the control to be hidden (opacity=0) and animate to opacity=1 when user hovers the central div (.range-picker-selection) with mouse.
Since there's no way I can operate with DOM directly from functions of my component (reactjs discourages this) I guess I should change the state ( to something like state.expanded = true) and rely on reactjs to reflect the change.
But how can I achieve the animation effect?
With jQuery I would have used element.animate({opacity:0}) but now I don't have access to DOM
Something like this?
http://plnkr.co/edit/5tLWNTtpsSWBUCgnKwQO?p=preview
I updated the state to have a hovered attribute and two mouse enter / leave methods to set the value which are bound the .range-picker-selection element in the render function
<div className="range-picker-selection" onMouseEnter={this.onMouseEnterHandler} onMouseLeave={this.onMouseLeaveHandler}>
onMouseEnterHandler: function () {
this.setState({
hovered: true
})
},
onMouseLeaveHandler: function () {
this.setState({
hovered: false
})
},
I also updated the render function to set the opacity to 0 or 1 accordingly for the elements.
style={{opacity: this.state.hovered ? 1 : 0}}
Finally, the actual animation is done with CSS animations
transition: opacity 1s;
I hope this helps