N.B. With the (more complex) setup I'm actually working with I can't use CSS Transitions. I recognise that CSS Transitions would be a
perfectly good solution in the example below.
I'm having a little trouble with
animation-direction: reverse
which I've never used before but doesn't seem to be running the way I might have expected it to.
The easiest solution to my problem would be to write two CSS #keyframes animations and use one or the other.
But for the sake of economy and elegance I would like to use a single animation and play it forwards or backwards.
This example below shows the effect I'm trying to achieve.
When the page loads, pressing either button will fire the intended animation.
However, after one button is pushed, the animation no longer runs and only the end-frame of the forwards or reverse animation is displayed.
What am I doing wrong here?
Working Example:
const square = document.querySelector('.square');
const buttonOutbound = document.querySelector('button.outboundButton');
const buttonReturn = document.querySelector('button.returnButton');
buttonOutbound.addEventListener('click', () => {
square.className = 'square';
square.classList.add('outbound');
}, false);
buttonReturn.addEventListener('click', () => {
square.className = 'square';
square.classList.add('return');
}, false);
.square {
width: 100px;
height: 100px;
margin: 12px;
background-color: red;
transform: translateX(0) scale(1);
}
.square.outbound {
animation: animateSquare 1s linear normal forwards;
}
.square.return {
animation: animateSquare 1s linear reverse forwards;
}
#keyframes animateSquare {
100% {
background-color: blue;
transform: translateX(200px) scale(0.5);
}
}
<div class="square"></div>
<button type="button" class="outboundButton">Outbound animation</button>
<button type="button" class="returnButton">Return animation</button>
The browser considers that animation as complete therefore it does not restart it in order to restart the animation you need to first remove the class and then re-add it however for the browser to recognize this change you need add a slight delay. Adding a setTimeout does the trick even if the timeout is 0 because js is single threaded.
const square = document.querySelector('.square');
const buttonOutbound = document.querySelector('button.outboundButton');
const buttonReturn = document.querySelector('button.returnButton');
buttonOutbound.addEventListener('click', () => {
square.classList.remove('return');
square.classList.remove('outbound');
setTimeout(() => {
square.classList.add('outbound');
}, 0)
}, false);
buttonReturn.addEventListener('click', () => {
square.classList.remove('return');
square.classList.remove('outbound');
setTimeout(() => {
square.classList.add('return');
}, 0)
}, false);
.square {
width: 100px;
height: 100px;
margin: 12px;
background-color: red;
transform: translateX(0) scale(1);
}
.square.outbound {
animation: animateSquare 1s linear normal forwards;
}
.square.return {
animation: animateSquare 1s linear reverse forwards;
}
#keyframes animateSquare {
100% {
background-color: blue;
transform: translateX(200px) scale(0.5);
}
}
<div class="square"></div>
<button type="button" class="outboundButton">Outbound animation</button>
<button type="button" class="returnButton">Return animation</button>
I had a think about this away from my laptop screen and realised that... what was missing from my original set up was one additional static class, describing the presentational state of .square after the .outbound animation has run:
.square.outbounded {
background-color: blue;
transform: translateX(200px) scale(0.5);
}
(N.B. There's no need for a corresponding static class, describing the state of .square after the .return animation has run, since the presentational state that class would describe is already described in the initial styles of .square)
Working Example (with .outbounded class added)
const square = document.querySelector('.square');
const buttonOutbound = document.querySelector('button.outboundButton');
const buttonReturn = document.querySelector('button.returnButton');
buttonOutbound.addEventListener('click', () => {
square.className = 'square outbound';
setTimeout(() => {
square.classList.add('outbounded');
square.classList.remove('outbound');
}, 1000);
}, false);
buttonReturn.addEventListener('click', () => {
square.className = 'square return';
setTimeout(() => {
square.classList.remove('return');
}, 1000);
}, false);
.square {
width: 100px;
height: 100px;
margin: 12px;
background-color: red;
transform: translateX(0) scale(1);
}
.square.outbounded {
background-color: blue;
transform: translateX(200px) scale(0.5);
}
.square.outbound {
animation: animateSquare 1s linear normal forwards;
}
.square.return {
animation: animateSquare 1s linear reverse forwards;
}
#keyframes animateSquare {
100% {
background-color: blue;
transform: translateX(200px) scale(0.5);
}
}
<div class="square"></div>
<button type="button" class="outboundButton">Outbound animation</button>
<button type="button" class="returnButton">Return animation</button>
Related
is there any way to skip to specific time in css animation?
for example: if we have a div moving from left to right in 10 seconds, user change time to 5 and animation jumps to second 5 and continues playing.
Changing animation-delay will do exactly that.
When you change it to negative values, it will jump forward.
const selectElement = document.querySelector('.number');
selectElement.addEventListener('change', (event) => {
const result = document.querySelector('.some-div');
result.style.animationDelay = `${event.target.value}s`;
});
.some-div {
width: 100px;
height: 100px;
background-color: red;
animation-name: example;
animation-duration: 20s;
animation-delay: 0;
}
#keyframes example {
from {
transform: translateX(0px);
}
to {
transform: translateX(400px);
}
}
<div class="some-div"></div>
<input type="number" class="number" />
<br/>
change the value in the input
In my navbar, I have a "Cart" item with a <v-badge /> on it that displays how many items are in the cart. When a user adds or removes to the cart, the number correctly increments and decrements. On state change of that number however, I'd like to be able to "bounce" the badge to provide the user with feedback that the item has been added or removed from the cart. I've been looking at the Vue docs for animations and transitions, I'm just not quite understanding how I'd go about achieving this.
I've attempted wrapping the badge in a <transition /> element and applying some keyframes animations I found on CSS Tricks, however it's still not working.
html:
<v-tabs
class="hidden-sm-and-down"
optional>
<v-tab
v-for="(item, i) in items"
:key="i"
:exact="item.title === 'Home'"
:to="item.to"
:ripple="false"
active-class="text--primary"
class="font-weight-bold nav-link"
min-width="96"
nuxt
text>
<transition
name="ballmove"
enter-active-class="bouncein"
leave-active-class="rollout">
<v-badge
v-if="item.badge && hasCartItems"
color="red"
:content="cartItems"
:value="cartItems"
class="default-badge"
overlap>
{{ item.title }}
</v-badge>
<span v-else>{{ item.title }}</span>
</transition>
</v-tab>
</v-tabs>
scss:
#mixin ballb($yaxis: 0) {
transform: translate3d(0, $yaxis, 0);
}
#keyframes bouncein {
1% { #include ballb(-400px); }
20%, 40%, 60%, 80%, 95%, 99%, 100% { #include ballb() }
30% { #include ballb(-80px); }
50% { #include ballb(-40px); }
70% { #include ballb(-30px); }
90% { #include ballb(-15px); }
97% { #include ballb(-10px); }
}
#keyframes rollout {
0% { transform: translate3d(0, 300px, 0); }
100% { transform: translate3d(1000px, 300px, 0); }
}
#keyframes ballroll {
0% { transform: rotate(0); }
100% { transform: rotate(1000deg); }
}
.rollout {
width: 60px;
height: 60px;
animation: rollout 2s cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
div {
animation: ballroll 2s cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
}
}
.bouncein {
animation: bouncein 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
.ballmove-enter {
#include ballb(-400px);
}
So I ended up going more of an old-school way. I took out the <transition /> wrapper around the <v-badge />, then added a watch function as such:
watch: {
cartItems: function(newValue, oldValue) {
const badge = document.querySelector('.v-badge__badge');
if (newValue !== oldValue) {
badge.classList.add('bounce');
this.delay(500).then(() => {
badge.classList.remove('bounce');
});
}
}
},
and my scss looks like:
#mixin ballb($yaxis: 0) {
transform: translate3d(0, $yaxis, 0);
}
#keyframes bouncein {
0%, 50% { #include ballb(-3px); }
25%, 75%, 100% { #include ballb() }
}
.bounce {
animation: bouncein 500ms cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
This gives the little "shake" of the badge I was looking for, however if someone has a more native Vue way of handling it, I'd absolutely love to see it.
In Vue 2.6.11, here is how I got a bit of a bounce, with some help from this answer from #Bill Criswell.
From his answer I got that I needed the :key on the badge element to force a re-render. The rest is straight out of Vue.js example here.
<transition name="bounce">
<v-badge
:key="item.comments.length"
v-if="item.comments && item.comments.length > 0"
>
<span slot="badge">
{{ item.comments.length }}
</span>
<v-icon>mdi-comment </v-icon>
</v-badge>
</transition>
And the associated SCSS just copied from the Vue example above:
.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
animation: bounce-in 0.5s reverse;
}
#keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
Situation
I have a table with devices and their statuses. When I click on a specific button the rows that have the offline status need to have a highlight for a couple of seconds and then return back to normal.
What I have so far
<tr id="deviceRow" class="user-item" *ngFor="let device of group.devices" (click)="$event.stopPropagation()" [class.highlightOn]="this.offlineHighlight == true && device.onlineState == 'Offline'">
When I click on the button the offlineHighlight boolean becomes true and it adds the highlightOn class which is this.
.highlightOn {
background-color: rgb(255, 68, 65);
-webkit-animation: fade-out 3s ease-out both;
animation: fade-out 3s ease-out both;
}
#-webkit-keyframes fade-out {
0% {
background-color: rgba(255,51,47,1);
}
100% {
background-color: transparent;
}
}
#keyframes fade-out {
0% {
background-color: rgba(255,51,47,1);
}
100% {
background-color: transparent;
}
}
This adds the 'highlight' animation.
After the animation is completed I set the offlineHighlught boolean to false again in the button code.
showOfflineDevices() {
this.offlineHighlight = true;
this.tabIndex = 1;
setTimeout(function(){
this.offlineHighlight = false;
}, 3000);
}
It all works fine until the animation has completed. Standard the table rows have different background colors for each odd even row. When the animation is complete all the rows that had the highlightOn class have a white background color as you can see here.
TL:DR The background color of the table rows need to go back to normal after the animation is completed. The even rows are also white now, which need to be grey.
It's because you set background-color to transparent on fade-out, you can simply use transitions like this (just add and remove class with additional styles, don't override existing styles on fade-out):
setInterval(() => {
$(".color").addClass("selected");
setTimeout(() => {
$(".color").removeClass("selected")
}, 2500);
}, 5000);
div {
transition: background-color .5s ease;
}
div:nth-child(odd) {
background-color: lightgray;
}
div:nth-child(even) {
background-color: gray;
}
.selected {
background-color: green !important;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>1</div>
<div class="color">2</div>
<div class="color">3</div>
<div>4</div>
<div>5</div>
I have a component that gets unmounted after ten seconds, and I just can't seem to get the leave-animations working with React CSSTransitionGroup. The appear classes gets added when the component mounts and those animations work well. However, the leave classes never gets added to the component on unmount. I've found several working jsfiddle examples, but the code doesn't work for me. I'm new to React so I'm hoping that someone can point me in the right direction. I've set the timeouts to be able to see if the classes gets added.
Main component:
this.state = {
renderBlankSlate: true,
//the rest of the initial state..
}
// This unmounts the component
componentDidMount() {
this.interval = setTimeout(() => this.setState({renderBlankSlate: false}), 10000);
}
{ this.state.renderBlankSlate ?
<ReactCSSTransisionGroup
component="div"
transitionName="slide"
transitionEnterTimeout={ 500 }
transitionAppear={ true }
transitionAppearTimeout={ 2000 }
transitionLeaveTimeout={ 5000 }
>
<BlankSlate />
</ReactCSSTransisionGroup>
: null }
CSS:
.slide-appear {
transform: translateX(110%);
height: 0;
opacity: 0;
}
.slide-appear.slide-appear-active {
transform: translateX(0);
height: 100%;
opacity: 1;
transition: all 2s ease-in;
}
.slide-leave {
transform: translateX(0);
}
.slide-leave.slide-leave-active {
transform: translateX(110%);
transition: 5s ease-in;
}
You probably want to add that ternary within the transition group.
<ReactCSSTransitionGroup
component="div"
transitionName="slide"
transitionEnterTimeout={ 500 }
transitionAppear={ true }
transitionAppearTimeout={ 2000 }
transitionLeaveTimeout={ 5000 }
>
{this.state.renderBlankSlate ? <BlankSlate /> : null}
</ReactCSSTransitionGroup>
The reason your leave animation isn't firing is because the Transition group is leaving as well
First, a fiddle: http://jsfiddle.net/AATLz/
The essence here is that there's a set of animations queued using -webkit-transition-delay. First element 0.4s, second 0.8s, third 1.4s, etc. They're queued last to first by default, and first to last when the parent has the 'expanded' class (toggled with that button).
This means that the animation when '.expanded' is added brings the boxes out one by one, and in reverse when the class is removed.
That's dandy. The problems start to arise when the class is toggled mid-animation. If you toggle, say, after the second box has animated, there's a delay before they start animating back, because a couple delay timers are being waited out.
Delays are obviously a bit clunky here.
The two alternatives I have in mind are 1) CSS keyframe animations, which I'm not entirely sure of how to activate on multiple elements in succession, and 2), JS controlled timing - using something like jQuery Transit. I'm not sure which would be more capable/graceful or if I'm missing another option.
Any input would be awesome!
jsfiddle: http://jsfiddle.net/Bushwazi/fZwTT/
Changed it up a bit. Control the timing with js. Animations with css.
CSS:
* {
margin:0;
padding:0;
}
#container {
background: orange;
height: 100px;
position: relative;
width: 100px;
}
.box {
height: 100px;
left: 0;
position: absolute;
top: 0;
width: 100px;
-webkit-transition:all 0.5s ease-in-out 0s;
-moz-transition:all 0.5s ease-in-out 0s;
-o-transition:all 0.5s ease-in-out 0s;
transition:all 0.5s ease-in-out 0s;
-webkit-transform: translate3d(0,0,0);
}
.box-1 {
background: red;
}
.box-2 {
background: green;
}
.box-3 {
background: yellow;
}
.box-4 {
background: blue;
}
.box-1 .box-1 {
left:100px;
}
.box-2 .box-2 {
left:200px;
}
.box-3 .box-3 {
left:300px;
}
.box-4 .box-4 {
left:400px;
}
HTML:
<div id="container" class="box-0" data-status="default" data-box="0">
<div class="box box-1"></div>
<div class="box box-2"></div>
<div class="box box-3"></div>
<div class="box box-4"></div>
</div>
<button id="ToggleAnim">Toggle</button>
JS:
(function(){
var testies = {
to: 0,
init: function(){
$button = $('#ToggleAnim');
$anim_elm = $('#container');
$(function(){
testies.el();
});
},
el: function(){ // el ==> event listeners
$button.on('click', testies.toggleBoxes);
},
toggleBoxes: function(evt){
var status = $anim_elm.attr('data-status'),
pos = $anim_elm.attr('data-box');
window.clearTimeout(testies.to);
// if default ==> build
// if killing ==> build
// if building ==> kill
// if done ==> kill
if(status == 'build' || status == 'done'){
testies.kill();
} else {
testies.build();
}
evt.preventDefault();
},
build: function(){
bpos = $anim_elm.attr('data-box');
if(bpos < 4){
bpos++;
$anim_elm.attr('data-status', "build").attr('data-box', bpos).addClass('box-' + bpos);
testies.to = window.setTimeout(testies.build, 500);
}
if(bpos == 4)$anim_elm.attr('data-status', "done");
console.log('BUILD: ' + bpos);
},
kill: function(){
kpos = $anim_elm.attr('data-box');
if(kpos > 0){
db = kpos - 1;
$anim_elm.attr('data-status', "kill").attr('data-box', db).removeClass('box-' + kpos);
testies.to = window.setTimeout(testies.kill, 500);
}
console.log('KILL: ' + kpos);
if(kpos == 0)$anim_elm.attr('data-status', "default")
}
}
testies.init();
})();