vuejs animation issue on removing item of list - css

I am trying to remove a list item with some animation, issue is if the removed item is the last one it works fine, but if I remove some item other than the last one animation is not working properly, see the fiddle here: https://jsfiddle.net/49gptnad/1003/
js code:
Vue.component('hello', {
template: '<transition name="bounce"><li>{{ind}} <a #click="removeit(ind)">remove</a></li></transition>',
props: ['ind'],
methods: {
removeit(ind) {
this.$emit('removeit')
}
}
})
var vm = new Vue({
el: '#vue-instance',
data: {
list: [1,2,3,4,5,6,7,8,9,10]
},
methods: {
removeit (extra, index) {
this.list.splice(index, 1)
}
}
});
html
<div id="vue-instance">
<ul>
<hello v-for="(item,index) in list" :ind="item" #removeit="removeit('extra', index)"></hello>
</ul>
</div>
css
.bounce-enter-active {
animation: bounce-in .7s;
}
.bounce-leave-active {
animation: bounce-in .7s reverse;
}
#keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.20);
}
100% {
transform: scale(1);
}
}

Ok, in the first place you should use transition-group, read more.
<div id="vue-instance">
<ul>
<transition-group name="bounce" tag="p">
<hello v-for="item in list"
:key="item" // You should use your property, (item.ID)
:ind="item"
#removeit="removeit('extra', item-1)">
</hello>
</transition-group>
</ul>
</div>
You need to define :key and you should avoid index and use your property, for example item.ID. It causes some trouble if you use it for removing item.
I have adjusted few things, you can check it here: https://jsfiddle.net/49gptnad/1006/

Related

React Transition Group: CSSTransition works on enter, not on exit

I have a React app that is using the CSSTransition component from react-transition-group: when the component appears, everything behaves as expected (a 0.5s transition from opacity: 0 to opacity: 1), however when the component exits, the transition is not applied and it just immediately disappears. Can anybody help me figure out why?
Render method in component:
render(){
const countries = geoUrl.objects.ne_50m_admin_0_countries.geometries;
const { handleEnter, handleList, list } = this.props;
return (
<CSSTransition
classNames="transition"
transitionAppearTimeout={50000}
timeout={500000}
key={ list }
in={ list } // this is a boolean value passed from the parent component, it is initially set to false but changes to true when this component is rendered
unmountOnExit
appear
>
<div className="overlay" id="list">
<div className="wrapper" ref={this.setWrapperRef}>
<aside className="list">
<a className="close" href="#home" onClick={handleList}>×</a>
<ul className="countryList">
{ countries.sort((a, b) => (a.properties.NAME > b.properties.NAME) ? 1 : -1).map(geo =>
geo.properties.COUNTRY ?
<li className="listItem" key={ `${geo.properties.ISO_A3}${geo.properties.name}` }><a href="#country" onClick={() => {
const { NAME, DISH, DESCRIPTION, PHOTO, RECIPE } = geo.properties;
handleEnter(NAME, DISH, DESCRIPTION, PHOTO, RECIPE);
}}>{ geo.properties.NAME }</a></li>
: null
)}
</ul>
</aside>
</div>
</div>
</CSSTransition>
);
}
CSS:
.transition-appear {
opacity: 0.01;
}
.transition-appear.transition-appear-active {
opacity: 1;
transition: opacity .5s ease-in;
}
.transition-enter {
opacity: 0;
}
.transition-enter-active {
opacity: 1;
transition: opacity .5s ease-in;
}
.transition-exit {
opacity: 1;
}
.transition-exit-active {
opacity: 0;
transition: opacity .5s ease-in;
}
I also struggled with the transition out, but have come up with a solution. I can't tell what your final result should look like in the UI, whether you want to transition in between conditionally rendered elements on the page, or just a transition when the component mounts and unmounts, but I think you want to do the former.
To transition between two or more elements on the same page, you will need to wrap your <CSSTransition> tags in <TransitionGroup> tags (and import it alongside CSSTransition). When you do this, you will need to provide a unique key property to the <CSSTransition> tag, it can't just be a boolean like it looks like you have. You also will need to slightly modify your CSS.
.transition-enter {
opacity: 0;
}
.transition-enter.transition-enter-active {
opacity: 1;
transition: opacity 0.5s ease-in;
}
.transition-enter-done {
opacity: 1;
}
.transition-exit {
opacity: 1;
}
.transition-exit.transition-exit-active {
opacity: 0;
transition: opacity 0.5s ease-in;
}
.transition-exit-done {
opacity: 0;
}
And the Transition tags:
import {CSSTransition, TransitionGroup} from "react-transition-group"
//...the rest of your code
return (
<TransitionGroup>
<CSSTransition
key={foo} //something unique to the element being transitioned
classNames="transition"
timeout={500}
>
<div className="overlay" id="list">
<div className="wrapper" ref={this.setWrapperRef}>
<aside className="list">
<a className="close" href="#home" onClick={handleList}>×</a>
<ul className="countryList">
{ countries.sort((a, b) => (a.properties.NAME > b.properties.NAME) ? 1 : -1).map(geo =>
geo.properties.COUNTRY ?
<li className="listItem" key={ `${geo.properties.ISO_A3}${geo.properties.name}` }><a href="#country" onClick={() => {
const { NAME, DISH, DESCRIPTION, PHOTO, RECIPE } = geo.properties;
handleEnter(NAME, DISH, DESCRIPTION, PHOTO, RECIPE);
}}>{ geo.properties.NAME }</a></li>
: null
)}
</ul>
</aside>
</div>
</div>
</CSSTransition>
</TransitionGroup>
)
Please note: You do not need appear, in, or unmountOnExit properties for this method, but you will have issues with duplicate elements appearing in the DOM during the transition, becuase react-transition-group actually clones the element and deletes the old one (which is why it needs a unique key). The only way to achieve a cross-fade transition is to take your elements out of the document flow with position absolute, so they overlap as one transitions in, and the other out.
I can't test your exact code, because there is not enough information, but I put together a very basic codesandbox that demonstrates the method with 2 conditionally rendered elements:
https://codesandbox.io/s/long-sea-9rtw9?file=/src/Test.js

How to trigger a CSS animation on EVERY TIME a react component re-renders

I want to play an animation on a react component every time it rerenders due to prop change:
react:
function Card({ cardText }) {
return <div className="roll-out">{cardText}<div/>
}
So I did css:
#keyframes rollout {
0% { transform: translateY(-100px); }
100% { transform: none; }
}
.roll-out {
animation: rollout 0.4s;
}
However, the animation only plays once, on the initial render. I want to play it every time <Card /> re-renders due to cardText change. How can I achieve it?
Add a key like this:
function Card({ cardText }) {
return <div key={cardText} className="roll-out">{cardText}<div/>
}
In your code, when the div re-renders, react only changes its inner text. Adding a key will make react think it's a different div when the key changes, so it'll unmount it and mount again.
The trick here is to use a random key field on your card element. React's diffing algorithm considers elements with the same key as the same, so randomizing the key will make react consider each rerendered element as new, so will removed the old element from the DOM and add a brand new one
Here is a demo using #aXuser264 's code as a base.
class Card extends React.Component{
onClick = ()=>this.forceUpdate();
render(){
return <div key={Math.random()} className="roll-out" onClick={this.onClick}> {
this.props.cardText
} </div>
}
}
ReactDOM.render( (<Card cardText="Hey There" />) , document.getElementById('root'))
#keyframes rollout {
0% {
transform: translateY(-100px);
}
100% {
transform: translateY(0);
}
}
.roll-out {
animation: .4s rollout;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
To force element to re-render you can simply change its key prop which will trigger a render making react think its another element
Refer this answer: https://stackoverflow.com/a/35004739
function Card({
cardText
}) {
return <div className = "roll-out" > {
cardText
} </div>
}
ReactDOM.render( (<Card cardText="Hey There" />) , document.getElementById('root'))
#keyframes rollout {
0% {
transform: translateY(-100px);
}
100% {
transform: translateY(0);
}
}
.roll-out {
animation: .4s rollout;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

use react-transition-group as child component receives new props

I have a child component where I would like to use slide-out animation as new props are getting passed to it and I try to use react-transition-group/switch-transition but is not really clear how to use it
The child component render method looks as it follows
return (
<SwitchTransition mode="out-in">
<CSSTransition
classNames="slide"
>
<div className={classnames("fields-group", containerClass)}>
{/* <pre>{JSON.stringify(this.props.fields, null, 2)}</pre>*/}
{fields}
</div>
</CSSTransition>
</SwitchTransition>
);
There are more things you need to do:
CSSTransition should has a prop key. When it changed, the transition will take affect.
You need to add the transition styles by yourself because, React Transition Group is not an animation library like React-Motion, it does not animate styles by itself. reference
So the child component will look something like that:
function Child({ propToAnimate }) {
return (
<>
<h4>Child Component</h4>
<div className="main">
<SwitchTransition mode="out-in">
<CSSTransition
key={propToAnimate}
addEndListener={(node, done) => {
node.addEventListener("transitionend", done, false);
}}
classNames="fade"
>
<div className="button-container">
<div className="animate">
<pre>state: {propToAnimate}</pre>
</div>
</div>
</CSSTransition>
</SwitchTransition>
</div>
</>
);
}
And the styles (for slide animation for example):
.fade-enter .animate {
opacity: 0;
transform: translateX(-100%);
}
.fade-enter-active .animate {
opacity: 1;
transform: translateX(0%);
}
.fade-exit .animate {
opacity: 1;
transform: translateX(0%);
}
.fade-exit-active .animate {
opacity: 0;
transform: translateX(100%);
}
.fade-enter-active .animate,
.fade-exit-active .animate {
transition: opacity 500ms, transform 500ms;
}
https://codesandbox.io/s/switchtransition-child-component-dk4jo

Simple forward and backward CSS animation with Angular ngAnimateSwap directive

I have made a simple swap animation with ng-animate-swap directive, nothing fancy. It works well in forward direction, but fails animating backwards correctly.
The problem is, that the entering slide will not be visible before animation ends.
Check an example in Plunker.
Here is the controller code:
var elem = document.querySelector('.wrap');
var $elem = angular.element(elem);
var slides = [
{ color: "#f00" },
{ color: "#0f0" },
{ color: "#00f" },
];
var current = 0;
$scope.slide = slides[ current ];
// switch to next slide
$scope.nextSlide = function(){
$elem.removeClass('animate-back');
if(slides.length <= ++current){
current = 0;
}
$scope.slide = slides[ current ];
};
// switch to prev slide
$scope.prevSlide = function(){
$elem.addClass('animate-back');
if(--current<0){
current = slides.length-1;
}
$scope.slide = slides[ current ];
};
the HTML:
<div class="wrap" ng-controller="AppCtrl">
<div class="container">
<div ng-animate-swap="slide" class="slide" ng-style="{background:slide.color}"></div>
</div>
<button ng-click="prevSlide()">Previous Slide</button>
<button ng-click="nextSlide()">Next Slide</button>
</div>
the CSS:
.slide.ng-enter-active,
.slide.ng-leave {
transform: translate(0,0);
}
// forward
.slide.ng-enter {
transform: translate(0,-100%);
}
.slide.ng-leave-active {
transform: translate(0,100%);
}
// backward
.animate-back .slide.ng-enter {
transform: translate(0,100%);
}
.animate-back .slide.ng-leave-active {
transform: translate(0,-100%);
}
I think it's simple CSS issue, but can not wrap my head around it.
What am I missing here?
You're right! The problem was the css. The direction was missing for the incoming slide.
".animate-back .slide.ng-enter-active"
This should work.
.animate-back .slide.ng-enter {
transform: translate(0,100%);
}
.animate-back .slide.ng-enter-active {
transform: translate(0,0);
}
.animate-back .slide.ng-leave-active {
transform: translate(0,-100%);
}
Updated Plunker: http://plnkr.co/edit/Uze8e8DmmjlaSqEeBELe?p=preview

Chain ng-repeat item transitions

I have a simple scope of a list of names:
$scope.friends = [
{name:'John'},
{name:'Jessie'},
{name:'Johanna'},
{name:'Joy'},
{name:'Mary'},
{name:'Peter'},
{name:'Sebastian'},
{name:'Erika'},
{name:'Patrick'},
{name:'Samantha'}
];
I perform a simple ng-repeat over these items, and display on my front end. What i want to try to achieve is that each item/name transitions in, one after each other, like a chain.
The closet to the effect i have seen can be found here: http://jsfiddle.net/57uGQ/ however this uses jQuery libraries which i want to avoid.
I am using the ng-animate library if that helps?
See plunker: http://plnkr.co/edit/aGwEe2jlGBTvjoSsiK9p?p=preview
You can use a combination of ng-if, $timeout and the .ng-enter-stagger css class to replicate the effect in the jsfiddle example.
html:
<div ng-controller="repeatController">
<ul>
<li class="animate-repeat" ng-repeat="friend in friends" ng-if="!hidden">
{{friend.name}},
</li>
</ul>
</div>
js:
angular.module('ngRepeat', ['ngAnimate']).controller('repeatController', function($scope, $timeout) {
$scope.friends = [
{name:'John'},
{name:'Jessie'},
{name:'Johanna'},
{name:'Joy'},
{name:'Mary'},
{name:'Peter'},
{name:'Sebastian'},
{name:'Erika'},
{name:'Patrick'},
{name:'Samantha'}
];
$scope.hidden = true;
$timeout(function(){
$scope.hidden = false;
}, 10);
});
css:
.animate-repeat.ng-enter {
transition: 1s linear all;
opacity: 0;
}
.animate-repeat.ng-enter-stagger {
transition-delay: 1.2s;
transition-duration: 0s;
}
.animate-repeat.ng-enter.ng-enter-active {
opacity: 1;
}
Plunker

Resources