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
Related
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>
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!
I'm writing code that makes it so that when an element is given a class, it flashes briefly. To do this, I've created an animation from its "highlighted" appearance to its "unhighlighted" appearance, which is applied when the element is given the .highlight class.
The trouble is that the .highlight class is usually only applied for a very short moment - it's removed well before the animation finishes. The result of this is that the element will use its "unhighlighted" appearance immediately once the class is removed. But my goal is that it will finish the animation, gradually transitioning to the unhighlighted appearance, even though the class that applies that animation was removed.
Below is some code that represents the situation I'm dealing with. Try clicking the button once, then click it again before the animation has finished; note that the animation is cancelled and the "unhighlighted" appearance is immediately used.
#foo {
background: blue;
color: white;
}
#keyframes unhighlight {
from {
background: red;
}
to {
background: blue;
}
}
#foo.highlight {
animation-duration: 5s;
animation-name: unhighlight;
}
<p id="foo">
Hello!
</p>
<button onclick="document.getElementById('foo').classList.toggle('highlight')">
Click
</button>
Since in practice I'm writing in the context of React, I'd prefer to avoid involving JavaScript in the solution here (e.g. only removing the .highlight class once it's detected that the animation has finished) - it would be difficult to incorporate into my existing code (really).
You can remove .highlight class using timer. I understand you have not added JavaScript tag but you are already using JavaScript to add and remove class.
See the Snippet below:
var timer = 0;
var stopAnimation = false;
var animationTimer = 5;
function playStopAnimation(){
console.log(document.getElementById("foo").classList.contains("highlight"));
if(document.getElementById("foo").classList.contains("highlight")){
if(timer != animationTimer){
stopAnimation = true;
}else{
document.getElementById('foo').classList.toggle('highlight');
stopAnimation = false;
timer = 0;
console.log("Highlight removed");
}
}else{
document.getElementById('foo').classList.toggle('highlight');
stopAnimationFn(animationTimer);
}
}
const stopAnimationFn = (n)=>{
for (let i = 1; i <= n; i++) {
setTimeout(() =>{
console.log(i);
timer = i;
if(stopAnimation && timer==animationTimer){
document.getElementById('foo').classList.toggle('highlight');
stopAnimation = false;
timer = 0;
console.log("Highlight removed");
}
}, i * 1000)
}
}
function timerSet(i) {
setTimeout(function(){
timer=i;
console.log(timer);
},1000);
}
#foo {
background: blue;
color: white;
}
#keyframes unhighlight {
from {
background: red;
padding-left:0;
}
to {
background: blue;
padding-left:500px;
}
}
#foo.highlight {
animation-duration: 5s;
animation-name: unhighlight;
}
<p id="foo">
Hello!
</p>
<button onclick="playStopAnimation()">
Click
</button>
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/
Animating in and up is working but I cant understand why it won't slide down and out?
Instead of sliding down is just closes instantly.
Demo here: http://codepen.io/anon/pen/dYbaJB
JS:
$(function() {
$("#btn").click(function () {
if ($("#notification").hasClass("in")) {
$("#notification")
.removeClass("in")
.addClass("out");
} else {
$("#notification")
.removeClass("out")
.addClass("in");
}
});
});
Simply change your CSS to:
#notification.out
{
-webkit-animation-name: slideOutDown;
animation-name: slideOutDown;
bottom: 20px;
}