I am using CSSTransitionGroup from react-transition-group and I have a page displaying several objects, which go through some filters (every filter changes the state of the array - cant do it otherwise). I want to activate leave transition only on one element (when the timer of the object gets to 0).
Is there a way to achieve it without activating the leave when i filter?
EDIT:
Each object in my array is an auction that contains several information including a timer. i want to avoid the activation of the leave animation when i search my db and activate it only when the timer finishes.
home component:
eachAuction(item, i) {
return <Auction key={i} index={i} auctionfinished={this.deleteAuction}
offerBid={this.offerBid} data={item} />
}
render:
<div>
<CSSTransitionGroup
transitionName="auction"
transitionAppear={true}
transitionAppearTimeout={700}
transitionEnterTimeout={700}
transitionLeaveTimeout={500}>
{this.state.auctionsArr.map(this.eachAuction)}
</CSSTransitionGroup>
</div>
EDIT: What about specify the leaving class inside the element?
eachAuction(item, i) {
return <Auction {timer===0 ?? 'className="leaving"'} ... />
}
Then you specify the CSS effects:
.auction-leaving div { display: none }
.auction-leaving div.leaving { /* animation start */ }
.auction-leaving-active div.leaving { /* animation end*/ }
Related
i've made this banner like screen that appears when my site is loaded, but here's the thing, i don't want no scrollbar while this opening animation it's happening, i only want to show the other components (the scrollbar and the whole site) once the gsap animation finishes, how could i proceed? thanks! (i tried to create a function to control those global elements, is it a way?)
So if I understand correctly you need the Banner to be displayed until the site is loaded. Maybe you are making some API calls or in general, you are planning to show the banner for let's say 3 sec and post that you want your actual components to be displayed.
You can try below approch:
export const APP = (): JSX.Element => {
const [isAnimationInProgress, SetAnimationState] = React.useState(true);
React.useEffect(() => {
// You can have your page load API calls done here
// Or wait for 'X' seconds
// Post that set the AnimationState to false to render actual components
setAnimationState(false);
})
return (
{
isAnimationInProgress && <Banner />
}
{
!isAnimationInProgress && <ActualComponent />
}
)
}
Regarding scrollbars, including overflow: hidden; in style for the banner should do the work if you are getting scrollbars for the Banner component.
On this sandbox, I've recreated the classic sliding-puzzle game.
On my GameBlock component, I'm using a combination of css transform: translate(x,y) and transition: transform in order to animate the sliding game-pieces:
const StyledGameBlock = styled.div<{
index: number;
isNextToSpace: boolean;
backgroundColor: string;
}>`
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: ${BLOCK_SIZE}px;
height: ${BLOCK_SIZE}px;
background-color: ${({ backgroundColor }) => backgroundColor};
${({ isNextToSpace }) => isNextToSpace && "cursor: pointer"};
${({ index }) => css`
transform: translate(
${getX(index) * BLOCK_SIZE}px,
${getY(index) * BLOCK_SIZE}px
);
`}
transition: transform 400ms;
`;
Basically, I'm using the block's current index on the board in order to calculate it's x and y values which change the transform: translate value of the block when it's being moved.
While this does manage to trigger a smooth transition when sliding the block to the top, to the right and to the left - for some reason, sliding the block from top to bottom doesn't transition smoothly.
Any ideas what's causing this exception?
React, lists and keys
What you're seeing is the result of a mount/unmount of the <GameBlock /> components.
Although you're passing a key prop to the component, React is unsure that you're still rendering the same element.
If I have to guess why react is uncertain, I would put the culprit at:
Changing the array sort with:
const previousSpace = gameBlocks[spaceIndex];
gameBlocks[spaceIndex] = gameBlocks[index];
gameBlocks[index] = previousSpace;
having different virtual DOM results using the conditional on isSpace:
({ correctIndex, currentIndex, isSpace, isNextToSpace }) => isSpace ? null : ( <GameBlock ....
Usually in applications, we don't mind a re-mount since it's pretty fast. When we attach an animation, we don't want any re-mounts since they mess with the css-transitions.
in order for react to be certain it's the same node and no re-mount is needed. we should take care that; between renders; the virtual dom stays mostly the same.
we can achieve that not doing anything fancy in the render of the list, and passing down the same keys between renders.
Pass isSpace down
Instead of changing the the rendered DOM nodes, we want the list render to always return an equal amount of nodes, with the exact same keys for each Node, in the same order.
simply passing 'isSpace' down and styling as display:none; should do the trick.
<GameBlock
...
isSpace={isSpace}
...
>
const StyledGameBlock = styled.div<{ ....}>`
...
display: ${({isSpace})=> isSpace? 'none':'flex'};
...
`;
Making sure to not change the arraysort
React considers the gameBlocks array to be modified, the keys are in a different order. Thus triggering unmount/mount of the rendered <GameBlock/> components.
We can make sure that react considers this array to be unmodified, by only changing the properties of the items in the list and not the sort itself.
in your case, we can leave all properties as is, only changing the currentIndex for the blocks that are moved/swapped with each other.
const onMove = useCallback(
(index) => {
const newSpaceIndex = gameBlocks[index].currentIndex; // the space will get the current index of the clicked block.
const movedBlockNewIndex = gameBlocks[spaceIndex].currentIndex; // the clicked block will get the index of the space.
setState({
spaceIndex: spaceIndex, // the space will always have the same index in the array.
gameBlocks: gameBlocks.map((block) => {
const isMovingBlock = index === block.correctIndex; // check if this block is the one that was clicked
const isSpaceBlock =
gameBlocks[spaceIndex].currentIndex === block.currentIndex; // check if this block is the space block.
let newCurrentIndex = block.currentIndex; // most blocks will stay in their spot.
if (isMovingBlock) {
newCurrentIndex = movedBlockNewIndex; // the moving block will swap with the space.
}
if (isSpaceBlock) {
newCurrentIndex = newSpaceIndex; // the space will swap with the moving block
}
return {
...block,
currentIndex: newCurrentIndex,
isNextToSpace: getIsNextToSpace(newCurrentIndex, newSpaceIndex)
};
})
});
},
[gameBlocks, spaceIndex]
);
...
// we have to be sure to call onMove the with the index of the clicked block.
() => onMove(correctIndex)
The only things we've changed are is the currentIndex of the clicked block and the space.
sandbox:
sandbox example based on your provided sandbox.
closing thoughts: I think your code was easy to read and understand, good job on that!
Additionally to the excellent answer and explanations #Lars provided, I wanted to share visual proof that certain <GameBlock /> components are indeed unmounted or changed in order, causing the hiccup in the CSS animation.
As you can see, when focussing one of the blocks and sliding down, the element changes its position in the DOM.
I have an input field that I want to hide/show and doing so with a fade and slide transition. I've have two examples that I came up with but both have their drawbacks and I'd like to know if there is a more elegant solution.
I just need one of the two questions to be answered as both of them would solve my problem.
Question 1: Is there a way to trigger multiple transitions for one transition-directive?
Question 2: How to add a class that will trigger an ordinary css-transition after an if-statement put the element in the DOM?
Example 1
Svelte does not allow two transitions on the same element. So one solution is to nest two elements as shown below. Is there instead a way to write a custom transition using both fade and slide transition:myMultiTransition?
{#if active === true}
<span transition:fade>
<span transition:slide>
<input type="text" />
</span>
</span>
{/if}
Example 2
In my other solution I just toggle an active class using a normal css transitions. The problem here is that the <input>-field never leaves the DOM. It's 0px height but it seems wrong to leave it there.
How to cuccessfully show the input field with an {#if active === true} and afterwards add a class that trigger the transition effect? Svelte seems to add the active-class that is supposed to trigger the transition before the element has entered the DOM.
I've tried to use await tick(), onMount, beforeUpdate in various combination with no luck.
When adding the class with a delay with setTimeout it works - but I don't like this solution because it could fail if not the timing is exact and I won't want a delay before the transition start.
<span class:{active}>
<input type="text" />
</span>
<style>
.active {
// Normal transition: opacity 1s etc ...
}
</style>
REPL
https://svelte.dev/repl/89cb7d26d9484d0193b4bc6bf59518ef?version=3.38.3
You can create your own transition function:
<script>
import { cubicOut } from 'svelte/easing';
let visibleDoubleElements = false;
function slidefade(node, params) {
const existingTransform = getComputedStyle(node).transform.replace('none', '');
return {
delay: params.delay || 0,
duration: params.duration || 400,
easing: params.easing || cubicOut,
css: (t, u) => `transform-origin: top left; transform: ${existingTransform} scaleY(${t}); opacity: ${t};`
};
}
</script>
<label>
<input type="checkbox" bind:checked={visibleDoubleElements}>
Svelte transition
</label>
{#if visibleDoubleElements === true}
<input transition:slidefade type="text" placeholder="Double elements" />
{/if}
REPL:
https://svelte.dev/repl/da8880947eff4f32b740a8742d9f817e?version=3.38.3
It might be the easiest to stick with the first solution you already provided: adding a wrapper for each transition.
If you want to reuse a specific combination of transitions it might be worth it to write your own one. At this point you can try to use the implementation from Svelte: Here is an example for Slide + Fade
function fadeSlide(node, options) {
const slideTrans = slide(node, options)
return {
duration: options.duration,
css: t => `
${slideTrans.css(t)}
opacity: ${t};
`
};
}
https://svelte.dev/repl/f5c42c6dc6774f29ad9350cd2dc2d299?version=3.38.3
Generic Solution (Theoretical)
In Svelte the transitions itself don't rely on CSS-transitions. A Svelte transition only provides the style for each transition step. Therefore a generic solution would be to create a merge-transition that takes 2..N transition functions and puts the styles from the individual transition together. Unfortunately this is not always trivial due to conflict situations in CSS.
E.g. combining two transitions... one where the opacity should be 0 and the other with a target opacity of 0.5. Question is: What should the output look like? If 0 is expected then there must be some logic which converts "opacity: 0; opacity: 0.5;" to "opacity: 0;". And there are surely more complex cases.
I'm creating a dashboard page which is full of CSS animations. From Bootstrap stuff (animated progress bars) to custom animations.
When you click some of the elements, a near full-screen modal is triggered, which overlaps all the animations, so I want to temporarily pause them all (because of possible performance issues) by adding/removing a class to one of the top elements, and using CSS to pause all animations when that class is set.
This solution would use only a single line of js, just to toggle the class on opening the modal.
My template looks somewhat like this:
<body>
<div class="modal">
<!-- Modal code -->
</div>
<div class="app">
<!-- Template -->
</div>
</div>
Is it possible to add a class to .app which pauses every CSS animation in every child element?
Note 1:
I know you can use the exact opposite of what I request: namely, have a default .animation-play class to one of the top elements, and prefix every child element with an animation with this class, and then remove this class to pause every animation. Just like:
app.animation-play .somediv .somediv .element {
// animation code
}
app.animation-play .somediv .element {
// animation code
}
app.animation-play .somediv .somediv .somediv .somediv .element {
// animation code
}
But then I have to edit a lot of CSS code, and it doesn't look very nice either.
Note 2:
I'm also open for a JS solution, but I would heavily prefer a pure CSS way of achieving this.
You can use a universal selector to target everything when a class of 'paused' is added to your app wrapper, however many CSS linters still warn against using these due to performance impacts.
To be honest the impact is probably minimal these days and many CSS resets for example use them.
You could use something like:
.app.paused * {
animation: none;
}
EDIT:
Looking through the comments above it seems as though the above selector doesn't have enough precedence to overwrite the animations so '!important' has been added.
.app.paused * {
animation: none !important;
transition: none !important;
}
However this is generally not a great idea, I always try to avoid using '!important' at all costs due to the difficulty in maintaining the stylesheet with these selectors in place. If you can overwrite the animations with a greater precedence then it would be better to do so rather than using '!important'.
EDIT 2:
As you mentioned you were open to JS solutions, here is some JS that should clear all the animations within a given selector. I'm not sure what the performance impact of doing it this way is but I added it here just in case someone else prefers to do it only using JS:
let stopAnimationsWrap = document.querySelector('.app');
let stoppedAnims = [];
// Stop animations
document.querySelector('.stop').addEventListener('click', () => {
let appAllEls = stopAnimationsWrap.querySelectorAll('*');
let allElsAr = Array.prototype.slice.call(appAllEls);
allElsAr.forEach((thisEl) => {
let elClass = thisEl.classList[0];
let cs = getComputedStyle(thisEl, null);
let thisAnimation = cs.getPropertyValue('animation-name');
if (thisAnimation !== 'none') {
stoppedAnims.push([elClass, {
'animationName': thisAnimation
}]);
thisEl.style.animationName = 'none';
}
});
});
// Start animations
document.querySelector('.start').addEventListener('click', () => {
stoppedAnims.forEach((thisEl) => {
let domEl = '.' + thisEl[0];
stopAnimationsWrap.querySelector(domEl).style.animationName = thisEl[1].animationName;
});
});
Fiddle:
https://jsfiddle.net/vu6javb2/14/
.app {
-webkit-animation-play-state: paused; /* Safari 4.0 - 8.0 */
animation-play-state: paused;
}
on hover:
.app:hover {
-webkit-animation-play-state: paused; /* Safari 4.0 - 8.0 */
animation-play-state: paused;
}
I designed a site so that changing two user inputted colors should change the color scheme of the entire site.
What is the best way to accomplish this. I know that I would have to save the items in the database and pull every time the user logged in in order to implement the color scheme with every login.
But at the moment I am more worried about a live change as soon as the user changes the html color value.
I know of an option to where I add a CSS class to every component that would change such as ... .primaryColor and .secondaryColor. And then alter all of the elements with that class. Is there a better way with React or another CSS/Javascript solution?
Also another complication is that it would have to be in a way that when the user loads other components that have not rendered yet, the change is still in affect.
One possible solution is to use the <style> element coupled with dangerouslySetInnerHTML, like this. (Notice the backticks ` around the CSS - it's an interpolated string literal.)
const Theme = props => {
<style dangerouslySetInnerHTML={{__html: `
.styled { color: ${props.userColor} }
`}}
/>
}
Then a component that used the theme would simply be <div className="styled" />
I got the idea for this solution here.
If you use this method, be very careful you're using sanitized variables to create your CSS theme. Otherwise, there's potential problems with injection attacks.
I would use an event listener on the input, read the value, and if it matches whatever you want to trigger the color scheme change, apply the theme value to a data attribute on a root element and use CSS to control the color schemes.
var input = document.getElementById('input'),
body = document.getElementsByTagName('body')[0];
input.addEventListener('keyup',function() {
var val = this.value;
if (this.value == 'foo') {
body.setAttribute('data-theme','secondary');
} else if (this.value == 'bar') {
body.setAttribute('data-theme','primary');
} else {
body.setAttribute('data-theme','');
}
// ajax request to save theme pref in db
})
/* defaults */
body {
color: #333;
}
/* primary theme */
[data-theme="primary"] {
color: red;
}
[data-theme="primary"] p {
background: yellow;
}
/* secondary theme */
[data-theme="secondary"] {
color: blue;
}
[data-theme="secondary"] ul {
background: grey;
}
<input id="input" placeholder="enter 'foo' or 'bar'">
<p>
paragraph
</p>
<ul>
<li>list</li>
</ul>
you can easily do this using js.
just add your class .primaryColor, .secondaryColor with jQuery addClass() Method.
select the element
example :
$(selector).addClass(classname,function(index,currentclass))
more example :https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_html_addclass