Chain ng-repeat item transitions - css

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

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 create a smooth background image transition in React?

I have a header, whose className changes depending on State. Each class has a different background image, specified in the CSS. Everything works fine, but the transitions are quite abrupt without a fade-in effect.
I wrote:
.jumbotron-img-1{
background-image: url("/images/myImg1.jpg");
transition: all 1s ease-in-out;
It works, but it's ugly. There is a zoom, and a distortion of the image before it shows up in its final form. I've watched some tutorials on Google, but nothing was simple and to the point for background-image transition in pure CSS or React.
Any help would be greatly appreciated, thanks!
background-image is not an animatable property. I feel what best serves your purpose is to render multiple headers with all the classnames available stacked over each other with position: absolute; relative to common parent and make only one of them visible using opacity property based on which classname is active in your state and use transition on opacity
Sample working code:
render() {
const {imgClassList} = this.props;
const {activeimgClass} = this.state;
return (
<div className="header-container">
{imgClassList.map(imgClass => {
return (
<div
className={`header ${imgClass} ${(imgClass === activeimgClass)? 'active' : ''}`}
/>)
})}
</div>
)
}
And css be something like:
.header-container {
position: relative;
}
.header{
position: absolute;
top: 0;
left: 0;
opacity: 0
transition: opacity 1s ease-in-out;
}
.header.active {
opacity: 1
}
.img-1 {
background:url('images/img-1')
}
.img-2 {
background: url('images/img-2')
} ... and so on
There's no good way to transition a background image using CSS because it's not an animatable property, per the CSS spec. One way to do this is to just have multiple images on top of one another, each containing a different one of the images you'd like to display, and then cycle through them by transitioning them to opacity: 0 and changing their z-index order.
I made a quick demo showing how you can achieve smooth changes by manipulating opacity and z-index. In pure Javascript, this is done by simply adjusting the styles with DOM manipulation and using setTimeout().
Of course in React you don't want to be doing DOM manipulation, so you can experiment with multiple classes with different opacity levels and transitions to accomplish this. There also seems to be a React component that enables all types of transitions: https://reactcommunity.org/react-transition-group/css-transition
Check out the Javascript solution demo to see how changing the opacity can get a crossfade effect on images:
function backgroundScheduler_1() {
setTimeout(() => {
document.querySelector(".img1").style.opacity = 0;
document.querySelector(".img2").style.opacity = 1;
document.querySelector(".img3").style.opacity = 1;
order(["-3", "-1", "-2"], () => { backgroundScheduler_2() }, 1000);
}, 3000);
}
function backgroundScheduler_2() {
setTimeout(() => {
document.querySelector(".img1").style.opacity = 1;
document.querySelector(".img2").style.opacity = 0;
document.querySelector(".img3").style.opacity = 1;
order(["-2", "-3", "-1"], () => { backgroundScheduler_3() }, 1000);
}, 3000);
}
function backgroundScheduler_3() {
setTimeout(() => {
document.querySelector(".img1").style.opacity = 1;
document.querySelector(".img2").style.opacity = 1;
document.querySelector(".img3").style.opacity = 0;
order(["-1", "-2", "-3"], () => { backgroundScheduler_1() }, 1000);
}, 3000);
}
function order(array, callback, time) {
setTimeout(() => {
document.querySelector(".img1").style.zIndex = array[0];
document.querySelector(".img2").style.zIndex = array[1];
document.querySelector(".img3").style.zIndex = array[2];
callback();
}, time);
}
backgroundScheduler_1();
.background-image {
position: absolute;
top: 0;
left: 0;
opacity: 1;
transition: 1s;
}
.img1 {
z-index: -1;
}
.img2 {
z-index: -2;
}
.img3 {
z-index: -3;
}
<div class="background-container">
<img class="background-image img1" src="https://placeimg.com/640/640/nature"></img>
<img class="background-image img2" src="https://placeimg.com/640/640/animals"></img>
<img class="background-image img3" src="https://placeimg.com/640/640/tech"></img>
<h2 style="color: white;">WOW!</h2>
</div>
I checked NPM momentarily and didn't see anything that promises this exact functionality. Hope this helps!

vuejs animation issue on removing item of list

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/

AngularJS Show hide div with fade in/out effect not working

i am new in angular. so i run lots of small code finding from internet just for learning purpose. so now i was trying to learn animation with angular. bad luck the code i try to run because it is not working.
code snippet
var app = angular.module('myApp', ['ngAnimate']);
app.controller('MainController', ['$scope',
function MainController($scope) {
$scope.animate = false;
$scope.play = function() {
$scope.animate = !$scope.animate;
}
}
]);
here is js fiddle https://jsfiddle.net/tridip/mvL0o2ew/
You forgot to include angular.js file itself. So include it, will work then.
Demo :
var app = angular.module('myApp', ['ngAnimate']);
app.controller('MainController', function($scope) {
$scope.animate = false;
$scope.play = function() {
$scope.animate = !$scope.animate;
}
});
.animate-show,
.animate-hide {
-webkit-transition: all linear 1s;
-moz-transition: all linear 1s;
-ms-transition: all linear 1s;
-o-transition: all linear 1s;
transition: all linear 1s;
}
.animate-show.ng-hide-remove,
.animate-hide.ng-hide-add.ng-hide-add-active {
opacity: 0;
display: block !important;
}
.animate-hide.ng-hide-add,
.animate-show.ng-hide-remove.ng-hide-remove-active {
opacity: 1;
display: block !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.13/angular.min.js"></script>
<script src="https://code.angularjs.org/1.2.13/angular-animate.js"></script>
<body ng-app="myApp" ng-controller="MainController">
<button ng-click="play()">play</button>
<div id='wrapper' ng-show="animate" class="animate-show animate-hide">TEST HERE</div>
</body>
You are receiving an error in the console:
angular.js:12520 TypeError: Cannot read property 'animate-show' of undefined
at lookup (angular-animate.js:365)
at performAnimation (angular-animate.js:688)
at Object.removeClass (angular-animate.js:583)
at Object.ngShowWatchAction [as fn] (angular.js:27760)
at Scope.$digest (angular.js:15896)
at ChildScope.$apply (angular.js:16160)
at HTMLButtonElement.<anonymous> (angular.js:23618)
at defaultHandlerWrapper (angular.js:3346)
at HTMLButtonElement.eventHandler (angular.js:3334)
you need to include properly the correct ng-animage module and it's classes

Highlight recently created by*ngFor element in Angular 2

I have a list of Words, shown or hidden by *ngFor according to their hidden property.
Please check plunker.
The problem is that when there are a huge amount of words in list, it is difficult to find where the new one appeared. So I would like to highlight them.
My idea was to set a .highlight class by default and to remove it after item was added, with smooth transition:
HTML
<div
*ngFor="let item of words | shownWords"
[ngClass]="{'item':true, 'highlihted': item.hidden}">
{{item.value}}
</div>
CSS
.item {
background-color: #ffffff;
transition: background .3s ease-out;
-moz-transition: background .3s ease-out;
-webkit-transition: background .3s ease-out;
-o-transition: background .3s ease-out;
}
.item.highlihted {
background-color: #ea90aa;
}
Unfortunately this doesn't work, as new item is already created without .highlight class.
So, here is the question: how to detect when a new item was added and which item exactly was added? Is it even possible? Or may be there is another way to achieve what I want?
P. S. please tell me if I attached incorrect plunker or something else wrong with my question.
Solution
First of all sorry for incomplete description, I forget to mention that I want to switch off a hightlight after a while.
Please check #Boyan Kostadinov's answer and then return here for a final solution and final plunker:
new toggleHidden():
toggleHidden(item:Word) {
item.hidden= !item.hidden;
if (!item.hidden) {
item.highlihted = true;
setTimeout(() => {
item.highlihted = false;
}, 500);
}
}
This will get you what you want: http://plnkr.co/edit/QxvCte7SvYg3hljBPIlB?p=preview
Explanation:
Added new "highlighted" property to your words class:
export class Word {
value:string;
hidden = false;
highlihted = false;
constructor(value: string) {
this.value = value;
}
}
Changed the toggleHidden function:
#Component({
selector: 'my-app',
pipes: [ShownWords],
template: `
<h2>Click on a word above line to show/hide word below line:</h2>
<div *ngFor="let item of words" (click)="toggleHidden(item)">{{item.value}}</div>
<hr>
<div
*ngFor="let item of words | shownWords"
[ngClass]="{item:true, highlihted: item.highlihted}">
{{item.value}}
</div>
`
})
export class AppComponent {
words: Word[] = [new Word('one'),new Word('two'),new Word('three'),new Word('four'),new Word('five')];
toggleHidden(item:Word) {
this.words.forEach((w:Word) => { w.highlihted = false });
item.hidden= !item.hidden;
if (!item.hidden) item.highlihted = true;
}
}

Resources