Decide order of CSS Transitions - javascript

Funky problem i'm having, although i bet theres a slick way to solve it.
The circumstance is that i have a table that has three column sort states: completely unsorted, in which case i dont want any icon present, sorted ascending, in which i want an up arrow, and sorted descending, in which i want a down arrow.
clicking the column header should take you through these three states.
starts off => click => ascending => click => descending => click => back to off
This all works fine and dandy, except for the fact that i want to use the same Caret element, and then use css transition's to fade it in from opacity: 0 to opacity: 1 , then on click rotate it 180deg to show a down arrow for descending, then finally when clicked again to remove the sort, i want it to fade out WITHOUT ROTATING BACK TO 0 DEG
That last part is where the problem lies.
EDIT
I have only recreated the behavior in the sandbox, but i am really using react-table, so there are only three possible states since it is controlled by the package:
initial state: {showCaret: false, flipped: false}
first click: {showCaret: true, flipped: false}
second click: {showCaret: true, flipped: true}
third click, back to initial: {showCaret: false, flipped: false}
the state changes are controlled by react-table, so i cant setTimeout on the flipped variable.
I am looking for a purely CSS way to achieve this goal without manipulating the way the state changes, if possible
END EDIT
I've attached a codesandbox to demonstrate. First click Show Caret, then Flip Caret, then Hide Caret. The css is set up basically the same as mine is currently in my actual project too.
https://codesandbox.io/embed/admiring-rain-svsc9?fontsize=14&hidenavigation=1&theme=dark

It sounds like what you want is for the arrow to disappear, but not to rotate back to its starting orientation as it disappears.
Since you are handling and tracking all this with React state, you could just set those two states separately, timed .3s apart (since that is your CSS transition time).
You could do this in a number of ways. To demonstrate it, in this fork of your example I have you just setting the on/off visibility to off, and then separately, in componentDidUpdate, I have it watching for whenever it's turned off, at which point it waits 300ms (.3s) and then sets the rotate state back.
componentDidUpdate(oldProps, oldState) {
if (oldState.showCaret && !this.state.showCaret) {
//it was just hidden
setTimeout(() => {
this.setState({
flipped: false
});
}, 300);
}
}
https://codesandbox.io/s/sparkling-pine-9igec
EDIT, with CSS only solution
https://codesandbox.io/s/pensive-wilbur-opxzf
/* flipped taking care of rotating the img tag */
.image {
transition: transform 0.3s linear 2s;
}
.flipped {
transform: rotate(180deg);
transition: transform 0.3s;
}

Change
onClick={() => this.setState({ showCaret: false, flipped: false })}
To
onClick={() => this.setState({ showCaret: false })}
and it should work.

Related

How do I apply CSS transitions (e.g. fade-ins) to a list of items in React so their animations are sequenced one after the other on render?

I've been searching Stack Overflow and the whole internet for this and couldn't find the right answer, so sorry if this is a duplicate question.
I've got a list:
How do I apply CSS transition to the list of elements, one by one, only on page load in React?
I was able to use ReactCSSTransitionGroup, and it works fine, but it applies the transition to the entire list at the same time.
React:
<ul className="item-list">
<ReactCSSTransitionGroup transitionName="fade" transitionAppear={true}
transitionAppearTimeout={2000}>
{props.items.map((item, i) => (<li><someComponent/></li>)}
</ReactCSSTransitionGroup>
</ul>
...
CSS:
.fade-appear{
opacity: 0;
}
.fade-appear-active{
opacity: 1;
transition: opacity 500ms ease-out;
}
As I previously mentioned, I need to apply the above transition to the list of items one after another.
I think the effect is usually referred to as “staggered,” “cascading,” or “sequenced.”
Rather than using ReactCSSTransitionGroup, you could do this mostly with CSS.
First, I'd animate your cards using animation property and #keyframes instead of transition property. So to start, you could add something like this to your CSS:
CSS
#keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
Javascript
The crux of the solution is to set an animation CSS style on each list item, and use the item index as a multiplier for a specified delay value.
I started by creating an array of objects called items, where each object contains a title and a text field (mainly just needed an array to map for the example).
I also created a couple of constants for abstracting the two numerical values for the animation, duration and delay (note we're only doing math with delay in the example to follow, but it looked cleaner to me to pull out duration as well):
const duration = 1000; // ms
const delay = 500; // ms
Made a template that returns a formatted string to be used as the value of each transition element's animation CSS property:
const animStr = (i) => `fadeIn ${duration}ms ease-out ${delay * i}ms forwards`;
Mapping the data during render time, and setting the CSS animation style based on the index value (i) via animStr:
{items.map((item, i) => (
<li key={i} style={{ animation: animStr(i) }}>
<SomeComponent item={item} />
</li>
))}
The animation will become active as soon as that element is injected into the DOM (as per the CSS animation spec). Syntax is based on the css animation shorthand. Note that the default behavior for the animation is to run once. Adding forwards to the rule causes the animation to retain the properties of the last keyframe when it stops (fully visible).
Edit: Personally, I think it looks better to start the delay index at 1 instead of 0, so you could set your animation value to this:
`fadeIn ${duration}ms ease-out ${delay * (i + 1)}ms forwards`
Working CodeSandbox
Here's a working codesandbox.
Screen Recording
This is what the above code looks like in action. It's a screen recording of the page being reloaded on CodeSandbox.
Another way to solve this would be to use a library. Both of the following libraries can achieve this effect with some added features:
react-drizzle
react-awesome-reveal
If you're committed to keeping ReactCSSTransitionGroup then you could probably just add a custom transition-delay property on each item, similar to the solution in my other answer.
const delay = 500;
And do something like this:
{props.items.map((item, i) => (
<li style={{ transitionDelay: `${delay * i}ms` }}>
<SomeComponent item={item} />
</li>
)}

React Spring: dynamic values in useTransition() when using it alongside react-router

In this example you can see some nice transition between pages triggered by the route change. (took from LevelUp Tutorials' React Animation course, thanks a lot Scott Tolinski).
Now I'd like to make these transitions happen in both directions, depending on which page it's transitioning to (and from), for instance:
Page One -> Page Two (both pages transition from left to right)
Page Three -> Page One (both pages transition from right to left)
etc
From that first example I created this example where the value of x is dynamic and should be evaluated to either 100 or -100, based on the direction of the transition.
I haven't fundamentally understood how useTransition() works, and the documentation is rather limited. The examples look amazing but are quite hard to understand.
This example seems to do a similar thing to what I'm trying to achieve but the code feels like black magic: the y property of each object returned from rows.map() appears to be related to the y value on the functions assigned to enter and update properties because if I remove that I get the error: Cannot read property 'replace' of undefined. How does that work?
This problem has two part.
determining the direction
change the animation
I created an example for solving the second part. When the user click page one I reverse the animation.
const reverse = location.pathname === '/';
const transitions = useTransition(location, location => location.key, {
from: { opacity: 0, transform: `translate3d(${reverse ? '-100%' : '100%'},0,0)` },
enter: { opacity: 1, transform: "translate3d(0,0,0)" },
leave: { opacity: 0, transform: `translate3d(${reverse ? '100%' : '-100%'},0,0)` },
// "initial: null" should only disable the 'from' initial transition, not the subsequent 'leave' transition (on the first manually triggered transition)
initial: null
});
Here is the sandbox: https://codesandbox.io/s/spring-transition-with-routes-215t8
For the first part to determine when to reverse the animation, I would store the path at each click and compare the next one to the previous. There is an example of storing the path here: Check history previous location before goBack() react router v4
I hope it helps.

ReactJS Why is my DOM / `display` only rendering 'display: none'?

Driving me a bit nuts here...
I want to click a Preview button and it generates a Preview Pane for text input. I thought i'll get the div's inline-style linked to React state.
<div className="preview-container" style={{ display: this.state.showPreview ? 'visible' : 'none' }}>
{
this.state.showPreview &&
<Preview
htmlString={value.toString('html')}
showOrHidePreview={this.togglePreview}
/>
}
</div>
But even though i can see state changing when i click the button,
AND i've successfully changed a border colour by linking it to state - for some reason changing display to none just sticks. it wont move off it.
Any ideas?
Updated (fairly standard toggle handler):-
togglePreview = () => {
if (this.state.showPreview === false) {
this.setState({
showPreview: true,
})
} else {
this.setState({
showPreview: false,
})
}
}
Thanks v.much #AdrianoRepetti for the suggestion of display: block rather than visible. Not sure where i picked up display: visible from.
If anyone else happens across this, i also found a hack using opacity: 10 vs opacity 0. Though that was scraping the barrel a bit.
There are several issues with this code:
1.
display: visible is incorrect and will have no effect (ignored by browser). The correct value here is display: block.
2.
You are removing the contents when showPreview is false, so display: none has no effect either.
3.
When showPreview is false you are asking React to render the value false. This is wrong, and you will get error.
4.
The max value of opacity is 1.
5. (potentially)
What is the initial value of showPreview? If undefined, your toggle will set showPreview to false, giving you the impression that it didn't do anything at all.

Fullpage.js. How to disable delay between normalizing elements?

Pacient: http://demo.imatte.us/fomru/landingpage.html
Problem: http://gyazo.com/031fe1c5413550e6e68aceef2740cefc
When window's size is changing, then we can see content of other slide. But after release the window's border, elements moving to right places with animation after small delay. How to disable this delay and animation, and force elements to stay on right positions constantly?
As #flybear pointed out, you would need to modify the plugin for it.
You would also need to change the scrollingSpeed of the plugin by using $.fn.fullpage.setScrollingSpeed(0). But this will only work if you use css3:false.
You should change the current event resize for this one:
//when resizing the site, we adjust the heights of the sections, slimScroll...
$(window).resize(function () {
// rebuild immediately on touch devices
if (isTouchDevice) {
$.fn.fullpage.reBuild();
} else {
$.fn.fullpage.setScrollingSpeed(0);
$.fn.fullpage.reBuild();
$.fn.fullpage.setScrollingSpeed(700); //default one
}
});
If you want to make it work with css3:true as well, you would need to deal with the css3 animations defined in the .fp-easings class. You probably can create another CSS class to overwrite the .fp-easings one defining a transition with 0 seconds:
//when resizing the site, we adjust the heights of the sections, slimScroll...
$(window).resize(function () {
// rebuild immediately on touch devices
if (isTouchDevice) {
$.fn.fullpage.reBuild();
} else {
$.fn.fullpage.setScrollingSpeed(0);
$('.fp-easings').addClass('.fp-no-transitions');
$.fn.fullpage.reBuild();
$.fn.fullpage.setScrollingSpeed(700); //default one
$('.fp-easings').removeClass('.fp-no-transitions');
}
});
CSS
.fp-no-easing {
-webkit-transition: all 0s ease-out !important;
transition: all 0s ease-out !important;
}
Just take into account that on every resize event, which can be fired hundreds of times when you resize the browser's window, will execute the reBuild function of the plugin, which will take care of resizing sections, updating the inner containers and scroll all the sections and slides of your site to fit the new position.
Firing this hundreds of times can cause some problems in slow computers and will slow down the page.

For JavaScript or jQuery, how to make transitionend event not fire?

In the link:
https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Using_CSS_transitions
it is said that:
Note: The transitionend event doesn't fire if the transition is aborted because the animating property's value is changed before the transition is completed.
So I went ahead and tried it on http://jsfiddle.net/HA8s2/32/ and http://jsfiddle.net/HA8s2/33/ using Chrome and Firefox.
example: (click on either the left or right box in jsfiddle)
$(".foo").click(function(evt) {
$(".foo").addClass("hide");
setTimeout(function() {
$(".foo").eq(0).removeClass("hide");
}, 3000);
});
$(".foo").on("transitionend", function(evt) {
console.log("wow! transitionend fired for", evt.target, "at time =", (new Date()).getTime() / 1000);
});
this is with a CSS transition duration for 6 seconds:
transition-duration: 6s;
But both kept the animation. The left box actually "animate to a new value in the middle of the original animation", so it took 9 seconds for the left to finish, while the right box took 6 seconds to finish.
In addition, Firefox only have the two events in http://jsfiddle.net/HA8s2/32/ separated by 2 seconds, instead of 3 seconds.
The question is: how do I make the transitionend stop as described in the docs in mozilla.org? (and not by any other brute force method).
(in other words, I want to find out all the situations that the transitionend will not fire and test it out).
Update: I was able to abort the animation if I add display: none to the box on the left, as on http://jsfiddle.net/HA8s2/34/ and won't be able to abort it if it is visibility: hidden as in http://jsfiddle.net/HA8s2/35/ but these do not really "change" the property's value as the docs says -- it is to add or change another property value.
Couldn't you give it a new class that overrides the transition property, removing it?
Your current code is like:
.myelem { transition: 0.5s all; }
You would add this code:
.alsothis { transition: none; }
When you apply the alsothis class to your element, the new transition property value will override the other one, removing the animation effect.

Categories

Resources