Angular2 move animation - animation

Angular 1 handles enter, leave and move animations. The Angular 2 documentation describes how to do enter and leave animations (void => * and * => void), but how can one implement move animations in Angular 2?

Read Angular's official guide for animations if you haven't already.
You define animation states and the transitions between them. For instance:
animations: [
trigger('heroState', [
state('inactive', style({
backgroundColor: '#eee',
transform: 'scale(1)'
})),
state('active', style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.1)'
})),
transition('inactive => active', animate('100ms ease-in')),
transition('active => inactive', animate('100ms ease-out'))
])
]
inactive and active can be replaced with any arbitrary strings and you can have as many unique states as you wish, but there must be a valid transition to each one or else the animation won't happen. void is a special case for when elements aren't yet attached to the view and * is a wildcard, applying to any of the defined states.
EDIT:
Hmm... well, for one thing, you might be able to use this Sortable library. It claims to support Angular 2 and is pure Javascript (no jQuery) so theoretically, it should work well but I have not used it myself.
Otherwise, I am certain it would be possible purely inside Angular 2, but it would probably require some fairly clever code. Relative motion (irrespective of a component or element's particular position) is easy with transform: translateY() property. The problem is that Angular 2 animation states only apply if the component is in that state so if you give it a translateY(-20px) to move an element up a position, it's not going to keep that position if you want to want to move it up again.
See this plunker for the solution I have come up with.
template: `
<div #thisElement>
<div class="div-box" #moveState="state">Click buttons to move <div>
</div>
<button (click)="moveUp()">Up</button>
<button (click)="moveDown()">Down</button>
`,
I defined animation states for 'moveUp' and 'moveDown' that ONLY apply during the actual animation and a 'static' state that is applied when the component isn't moving.
animations: [
trigger('moveState', [
state('moveUp', style({
transform: 'translateY(-30px)';
})),
state('moveDown', style({
transform: 'translateY(30px)';
})),
state('static', style({
transform: 'translateY(0)';
})),
transition('* => moveUp', animate('100ms ease-in')),
transition('* => moveDown', animate('100ms ease-out')),
transition('* => static', animate('0ms linear'))
])
]
For the function that actually initiates the animation, it applies the 'moveUp' or 'moveDown' state and then starts a timeout that triggers a callback after an amount of time equal to the length of the transition. In the callback, it sets the animation state to 'static' (the transition to the 'static' state is set to 0 ms so we don't actually animate it moving back to a static position). Then we use Renderer to apply a translation for where we want it to ultimately end up (calculated using a position property that would define it's position relative to where it was initially, not it's position in the array). The Renderer applies its styles separately from the animation so we can apply both without them conflicting with each other.
export class MyComponent {
state = 'static';
#ViewChild('thisElement') thisBox: ElementRef;
position: number = 0;
//...
moveUp() {
this.state = 'moveUp';
this.position--;
setTimeout(() => {
this.state = 'static';
this.renderer.setElementStyle(this.thisBox.nativeElement, 'transform', 'translateY(' + String(this.position * 30) + 'px)');
}, 100)
}
moveDown() {
this.state = 'moveDown';
this.position++;
setTimeout(() => {
this.state = 'static';
this.renderer.setElementStyle(this.thisBox.nativeElement, 'transform', 'translateY(' + String(this.position * 30) + 'px)');
}, 100)
}
//...
}
This is only an example of how you can animate moves without having to define states for each possible position it could be in. As far as triggering the animations on array manipulation, you'll have to figure that out for yourself. I would use some kind of implementation with EventEmitters or Subjects to send events to the components that would then decide on whether or not they need to animate or not.

Related

In React Native - How to move View element in parallely while ScrollView scrolls

I'm developing a TimeLine component. There are Views list inside horizontal ScrollView that represents half an hour blocks. And I have a Component called TimeRangeSelector, which I use to select a range of time in TimeLine ScrollView. So while I scroll the ScrollView I need to move the TimeRangeSelector in parallel without any lag. Below is the TimeLine component. You can see the 30mins blocks are filled inside ScrollView. The yellow color one is the TimeRangeSelector which is a Animated.View. And the left position of the TimeRangeSelector is set using the scroll position. Each time ScrollView moves im setting the state as below.
<ScrollView
horizontal={true}
onScroll={this.onScrollFunc}
>
{timelineElements}
</ScrollView>
onScrollFunc = (event, gesture) => {
this.setState({
contentOffsetX: event.nativeEvent.contentOffset.x,
scrollStopped: false
});
};
And Im passing the scrollBarPosition as props to the TimeRangeSelector and setting its left position style as shown in below code
<TimeRangeSelector
scrollBarPosition={this.state.contentOffsetX}
/>
let styles= [
{
position: "absolute",
left: this.props.scrollBarPosition,
backgroundColor: backgroundColor,
marginTop: 20,
marginLeft: 1,
width: this.state.selectionWidth,
height: 100
}
];
But the issue is when I scroll the ScrollView the TimeRangeSelector moves with it, but it has a delay.. when I scroll faster the distance between where it should be and where it is, is becoming high. Can anyone give me some ideas according to your knowledge.
My assumption: According to my understanding, I think the lag is because of it takes several instances to reach the setState in and set the ScrollBarPosition as below steps.
ScrollView moved 1 frame.
ScrollView fires the onScroll function and emmits the event with new x point.
Then in the onScroll function it sets the State.
As I understand it takes some time to happen all these steps from JavaScript thread all the way to Native Thread and come back to JS thread again.
You should use something like const onScroll = Animated.event([{ nativeEvent: { contentOffset: { x: animatedValue } } }], { useNativeDriver: true }); with const animatedValue = new Animated.Value(0). This way the animation is only done on the native level without going back and forth through the JS thread.
This animated value can only be used effectively with opacity and transform style properties, but it should help you with your problem.
You can read more about this, in this awesome article.

ReactCSSTransitionGroup rendering list of items

So I am trying to display a transition with ReactCSSTransitionGroup on load of the home page. It is to display a list of items below the jumbotron. This items come from Redux store. They are passed from a container component called home_index.js that is aware of Redux Store. The component itself 'poll-list.js' contains poll-links, the items to be transitioned in; it (and its child) is a DUMB component. Meaning, it has no state nor is it aware of redux store. (Can these components even use ReactCSSTransitionGroup?)
In any case, I cannot, for the life of me, get this fade in transition to work. I know I must be doing something wrong...but cannot figure it out.
const appearTransition = {
transitionName: "fade",
transitionLeave: false,
transitionEnter: false,
transitionAppear: true,
transitionAppearTimeout: 2500
};
let polls;
if (props.pollsList) {
polls = props.pollsList.map((poll, ind) => {
return (
<PollLink
className='poll-link'
title={poll.title}
index={ind}
id={poll.id}
key={ind}
/>
)
});
}
return (
<div className='poll-list'>
<ReactCSSTransitionGroup {...appearTransition}>
{props.pollsList.length > 0 ? polls : null}
</ReactCSSTransitionGroup>
</div>
)
}
And here is the CSS (note I am not doing a Leave, and I have set Appear to true, i just want this to happen on initial load of the home page)
.fade-appear {
opacity: 0.01;
#include prefix(transition, (opacity 2500ms), webkit ms moz o);
}
.fade-appear.fade-appear-active {
opacity: 1;
}
Also, putting ReactCSSTransitionGroup at this level, in this element, makes my flexbox get all weird. The components are no longer in columns of 4 (or however many based on screen width), they are in one single column down the center...
Where do I put the transition group HOC?
Hierarchy:
<HomeIndex>
<Poll-List>
<Poll-Links>
You can simply add this style in your css file, try to remove the hyphen between class name and make it in camelcasing:
.fadeAppear {
opacity: 1;
transition: opacity 0.3s ease-in;-webkit-transition: opacity 0.3s ease-in;
}
import your css file in your redux code,
#import mycss from '../path/style.css'
and add the given class in css in wherever needed.
as:
return (
<div className='poll-list'>
<ReactCSSTransitionGroup className={mycss.fadeAppear} >
{props.pollsList.length > 0 ? polls : null}
</ReactCSSTransitionGroup>
</div>
)
Hope this works!

Animate only new components

I am experimenting with the new animation API in Angular 2, and have the following challenge:
I have a parent component that displays a set of child components by using a *ngFor. The ngFor references a simple data array in the parent component.
Requirements:
When the parent component is rendered with its initial children, the parent and children should be rendered instantly (without animation)
When new child components are added (because of a new object appended to the data array), the new child should be animated (e.g. bounce in from the left).
How can I configure the animation to handle this?
The basic question is: How can a child component know if it is rendered as part of the initialization of its parent or later?
Some possible solutions:
- I can set a boolean variable directly on the data object that says that the new object is created after the view and should be animated. This property could be checked by the component. However, I don't want to introduce this kind of view logic in the data model.
- I can use the lifecycle hooks in the parent component to set a property in the parent that says that the parent is rendered and that all subsequent (new) children should be animated. However, I haven't been able to do this as it seems that all lifecycle hooks are executed before the children components are instantiated.
Other solutions?
Br
Anders
You can use a property in parent
initialLoad = true;
ngAfterViewInit() {
this.initialLoad = false;
}
https://github.com/angular/angular/issues/7239#issuecomment-227369239 contains an example (with Plunker):
#Component({
selector: 'app',
template: `
<button (click)="items.push(items.length)">Add</button>
<ul>
<template ngFor let-item [ngForOf]="items" let-i="index">
<li style="display: block;" #flyInOut="'in'" (click)="onClick(i)" *ngIf="!!item">{{ i }} - {{ item }}</li>
</template>
</ul>
`,
animations: [
trigger('flyInOut', [
state('in', style({ opacity: 1, transform: 'translateX(0) scaleY(1)' })),
transition('void => *', [
style({
opacity: 0,
transform: 'translateX(-100%)'
}),
animate('1s ease-in')
]),
transition('* => void', [
animate('1s 10 ease-out', style({
opacity: .5,
transform: 'scaleY(0.8)'
}))
])
])
]
})
export class App {
private items = [];
constructor() { }
onClick(index) {
//this.items.splice(index,1);
delete this.items[index];
}
}
There is currently an issue with *ngFor that was fixed in https://github.com/angular/angular/pull/10287 but the fix has yet to land.

Angular2 Animations :: Easings not working as expected

I'm working on a collapsable component, one that you can click to roll-up/down to show/hide details. The component is as follows:
// component.ts
import {Component, Directive, Input} from '#angular/core';
import {trigger, state, style, transition, animate} from '#angular/core'
#Component({
selector: 'expandable',
template: '<div *ngIf="isExpanded" #animate="'slide'"><ng-content></ng-content></div>',
animations: [
trigger('animate', [
state('slide', style({ overflow: 'hidden', height: '*' })),
transition('slide => void', [animate('200ms ease-out', style({ height: 0 }))]),
transition('void => slide', [style({ height: 0 }), animate('200ms ease-out', style({ height: '*' }))])
])
]
})
export class SimExpandable {
private _expanded: boolean = false;
#Input('expanded') set expanded(value: string | boolean) {
this._expanded = (value === 'true' || value === true);
}
get isExpanded(): boolean { return this._expanded }
}
The component works fine, partially. The animations, however, are not perfect. I've configured the component to use ease-out animation but in reality, the component animates linearly.
I've tried using ease-out, easeOut, ease-out-cubic, easeOutCubic, ease-in-out, easeInOut, bounce, and a lot of other permutations but the component still animates linearly. I REALLY need to use ease-out for my component. Any help would be really appreciated.
CSS properties transition and animation allow you to pick the easing
function. Unfortunately, they don’t support all easings and you must
specify the desired easing function yourself (as a Bezier curve).
Easings.net
It would seem that there are 4 default types of easing that should work.
linear
ease
ease-in
ease-out
ease-in-out
These work directly, however the differences are subtle
For more effective types of easing, you have to use a bezier-curve which allows you to create your own easing. For example the below is "easeInOutBack"
cubic-bezier(0.680, -0.550, 0.265, 1.550)
When using with Angular animate function
animate("1500ms 2000ms cubic-bezier(0.680, -0.550, 0.265, 1.550)", style({transform: "translateX(-100%)"}))
You can navigate to this bezier curver generator which should provide you with the ability to create your own easings.
Animate is no longer a part in angular/core.. so you have to import it, its part of the module ngAnimate .. so to use the $animate service you need to import js/lib/angular-animate.js libs.
bower install --save angular-animate

React Native: Triggering Animation on hide

I have an element controlling the rendering of a child element. (A TouchableHighlight that sets some state in its onPress.) In the child element's componentDidMount method I construct an Animated.spring and start it. This works for entry, but I need to do the same animation in reverse to exit (it's like a drawer). componentWillUnmount executes too quickly for Animated.spring to even start working.
How would I handle animating the child's exit?
I have implemented a FadeInOut component that will animate a component in or out when its isVisible property changes. I made it because I wanted to avoid explicitly handling the visibility state in the components that should enter/exit with an animation.
<FadeInOut isVisible={this.state.someBooleanProperty} style={styles.someStyle}>
<Text>Something...</Text>
</FadeInOut>
This implementation uses a delayed fade, because I use it for showing progress indicator, but you can change it to use any animation you want, or generalise it to accept the animation parameters as props:
'use strict';
import React from 'react-native';
const {
View,
Animated,
PropTypes
} = React;
export default React.createClass({
displayName: 'FadeInOut',
propTypes: {
isVisible: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
style: View.propTypes.style
},
getInitialState() {
return {
view: this.props.children,
opacity: new Animated.Value(this.props.isVisible ? 1 : 0)
};
},
componentWillReceiveProps(nextProps) {
const isVisible = this.props.isVisible;
const shouldBeVisible = nextProps.isVisible;
if (isVisible && !shouldBeVisible) {
Animated.timing(this.state.opacity, {
toValue: 0,
delay: 500,
duration: 200
}).start(this.removeView);
}
if (!isVisible && shouldBeVisible) {
this.insertView();
Animated.timing(this.state.opacity, {
toValue: 1,
delay: 500,
duration: 200
}).start();
}
},
insertView() {
this.setState({
view: this.props.children
});
},
removeView() {
this.setState({
view: null
});
},
render() {
return (
<Animated.View
pointerEvents={this.props.isVisible ? 'auto' : 'none'}
style={[this.props.style, {opacity: this.state.opacity}]}>
{this.state.view}
</Animated.View>
);
}
});
I think you have the animation ownership inverted. If you move your animation logic to the parent that is opening and closing the child, the problem becomes much simpler. Rather than beginning the animation on componentDidMount, do it on the click of your TouchableHighlight in addition to, but independent of, whatever prop manipulations on the child you need to do.
Then when the user clicks to close, you can simply reverse the animation as per normal and you don't really even need to unload it. Also this would allow you to have a reusable drawer (the thing that slides up and down) and it's abstracted away from the content within it. So you can have a single drawer mechanism supporting multiple different types of content.

Resources