How do you cancel and reset css animations in react? - javascript

What I am trying to do
I have a sprite sheet and I am using it for some simple animations. I have a "player" that has two animations. One for attacking and one for standing. The current behavior is as follows: "stand" is the default animation. When the player is clicked, the "attack" animation is played and switched back to "stand."
For the special case that the player is clicked while attacking, I want the animation "attack" animation to reset.
My code
// React Component
class Player extends React.Component {
constructor(props) {
super(props);
this.state = {
timeout: null
};
}
attack(e) {
if (this.state.timeout) { clearTimeout(this.state.timeout); }
var $el = $(e.target);
$el.css({
backgroundPosition: '0 220px',
animationName: 'attack',
width: '300px'
});
this.state.timeout = setTimeout(function() {
$el.css({
backgroundPosition: 'left top',
animationName: 'stand',
width: '250px'
});
}, 2000);
}
render () {
return (
<div onClick={this.attack.bind(this)} className="player main">{this.props.name}</div>
);
}
}
/* The css */
.player {
width: 250px;
height: 220px;
background: url('assets/player.png') left top;
animation: stand 1.2s steps(3) infinite;
animation-direction: alternate;
}
#keyframes stand {
100% {
background-position: -750px 0;
}
}
#keyframes attack {
100% {
background-position: -900px 220px;
}
}
What I tried to do
I knew that since I setTimeout the "stand" animation, I would have to cancel it and make a new setTimeout to go after the "stand" animation. However, it still looks a bit funky. I tried to use forceUpdate() to see if I could just rerender and reset everything and then start the "attack" animation, but it doesn't seem to do anything.
I have another idea using jquery to remove and reattach the dom element but I feel like it would get messy and I believe I might be approaching this problem the wrong way. Do you guys have any ideas?

For complex animations, you can use https://github.com/chenglou/react-motion which can learnt from https://egghead.io/playlists/react-react-animation-using-react-motion and https://egghead.io/lessons/react-react-motion-introduction-to-the-spring-component
For simple ones, I would rather add and remove classes from element to enable the animation and reset it.
And if I am understanding it correctly, you are using jQuery inside react. Well to update the CSS, you should not use it inside react.
There should be more strong reasoning to use jQuery in react because updating inline css is very simple in react.
Following is the way I would attempt to do it:
// React Component
class Player extends React.Component {
constructor(props) {
super(props);
this.state = {
direction: null,
timeout: null
};
}
attack(e) {
//following line can be used to identify multiple targets
//let target = (e.target);
if (this.state.timeout !== null) {
window.clearTimeout(this.state.timeout)
}
let timeout = setTimeout(() => {
this.setState({
direction: null,
timeout: null
});
}, 8000)
let direction = null;
if (e.ctrlKey) {
direction = 'anticlockwise'
} else {
direction = 'clockwise'
}
if (this.state.direction !== direction) {
this.setState({
direction, timeout
});
}
}
render() {
let classNames = ["player", "main"];
let styles = {}
if (this.state.direction !== null) {
if (this.state.direction === 'clockwise')
styles.zoom = 0.8
else
styles.zoom = 1.2
classNames.push(this.state.direction)
}
return ( < div onClick = {
this.attack.bind(this)
}
style={styles}
className = {
classNames.join(' ')
} > {
this.props.name
} < /div>
);
}
}
ReactDOM.render(<Player / > , document.getElementById('game'));
.player {
width: 100px;
height: 100px;
margin: 50px;
zoom: 1;
transition: all linear 0.3s;
display: inline-block;
background-color: #ccc;
animation-timing-function: linear;
}
.clockwise {
animation: clockwise 5s infinite;
}
.anticlockwise {
animation: anticlockwise 5s infinite;
}
#keyframes clockwise {
0% {
transform: rotate(0deg)
}
50% {
transform: rotate(180deg)
}
100% {
transform: rotate(360deg)
}
}
#keyframes anticlockwise {
0% {
transform: rotate(360deg)
}
50% {
transform: rotate(180deg)
}
100% {
transform: rotate(0deg)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="game"></div>

Related

JS Class remove not working properly, Dev Tools problem

my purpose is that to animate the DIV Box everytime when browser Tab is active (means i switch the tab to other & back to this tab), As i am adding addEventListener & its working but one time only, not everytime.
But when i open Developer tools in chrome it's work everytime.
Check this video to better understand my problem: https://youtu.be/9Uvm__ln6zE
JS Class remove not working properly, Dev Tools problem
document.addEventListener("visibilitychange", () => {
if(document.visibilityState === "visible" ){
var element = document.getElementById("topHeading");
element.classList.add("r1");
}
else{
var element = document.getElementById("topHeading");
element.classList.remove("r1");
}
});
.r1 {
background-color: lightgrey;
width: 300px;
height: 50px;
border: 6px solid green;
padding: 10px;
text-align: center;
visibility: visible;
}
.r2 {
background-color: red;
}
.r1 {
animation: bounceInRight 1400ms both
}
#keyframes bounceInRight {
from,
60%,
75%,
90%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
from {
opacity: 0;
transform: translate3d(3000px, 0, 0);
}
60% {
opacity: 1;
transform: translate3d(-25px, 0, 0);
}
75% {
transform: translate3d(10px, 0, 0);
}
90% {
transform: translate3d(-5px, 0, 0);
}
to {
transform: translate3d(0, 0, 0);
}
}
<div id="topHeading">
This text is the content of the box..
</div>
Thanks in advance
This is a speed problem. When you add the class, the browser immediatly assumes no transition is needed, because the element already has the class. You can fix this by delaying your classList.add a little:
var element = document.getElementById("topHeading");
document.addEventListener("visibilitychange", () => {
var isVisible = document.visibilityState === "visible";
setTimeout(function() {
element.classList[isVisible ? 'add' : 'remove']("r1");
});
});
Fixed Codepen
Re-applying the class doesn't work. You can get it to work by removing the element from the dom and then adding it again or by introducing a delay. You can read more about restarting css animations at https://css-tricks.com/restart-css-animation/
document.addEventListener("visibilitychange", () => {
var element = document.getElementById("topHeading");
if(document.visibilityState === "visible" ) {
element.classList.add("r1");
} else {
element.parentNode.replaceChild(element.cloneNode(true), element);
}
});
Or you can add the animation class to the html element and write less JS:
document.addEventListener("visibilitychange", () => {
var element = document.getElementById("topHeading");
if(document.visibilityState === "visible" ) {
element.parentNode.replaceChild(element.cloneNode(true), element);
}
});

Slideshow's animation doesn't work

In this Vue component, I tried to make a slideshow.
The logic works this way:
1) Making an array of all sources of pictures I want to put in(array: pictures)
2) Set a variable(Count) to 0, so that it starts from the start.
3) Putting v-bind:src="pictures[count]" on an img tag so that it changes the source by the variable(count)
4) Putting functions on 2 buttons that returns in images or goes forward in images.
<template>
<div>
<img v-bind:src="pictures[count]" alt="">
<button #click="Switching(1)">Forward</button>
<button #click="Switching(-1)">Back</button>
<p>{{this.count + 1}}</p>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
pictures: [
"http://www.hotel-bb.com/image.htm?id=93652",
"https://s-ec.bstatic.com/images/hotel/max1280x900/681/68184730.jpg",
"https://images.all-free-download.com/images/graphiclarge/hd_picture_of_the_beautiful_natural_scenery_03_166249.jpg",
"https://images.all-free-download.com/images/graphiclarge/the_beach_at_dusk_couple_of_picture_165871.jpg"
],
stop: false
};
},
methods: {
Switching: function(n) {
this.count += n;
if (this.count > this.pictures.length - 1) {
this.count = 0;
} else if (this.count < 0) {
this.count = this.pictures.length - 1;
}
}
}
};
</script>
<style scoped>
#keyframes slideInFromLeft {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(0);
}
}
img {
max-width: 800px;
height: 500px;
animation: 1s ease-out 0s 1 slideInFromLeft;
}
</style>
It works properly. The problem is that when I put an animation on it, it doesn't work except for the first picture that loads, when I switch by the two buttons, the pictures don't get animated.
I tried transition: 0.5s, animation and Keyframes and nothing works. I'm guessing that it is because the pictures don't appear but they get loaded and src gets changed. How I can make it work?
For building a slideshow on Vue JS, I suggest Vueper Slides. It will do all that for you.
Unless it is for a learning purpose.

How to animate or use transitions on a message pop up with javascript and css?

I currently have the message displaying and disappearing as I want but the transition isn't working, This is what I tried.
const alertMsg = document.querySelector('.alert');
contactForm.addEventListener('submit', formSubmitted);
function formSubmitted(e) {
//other stuff
alertMsg.style.display = 'block';
setTimeout(() => {
alertMsg.style.display = 'none';
}, 5000);
}
.alert {
transition: all 0.5s ease-out;
}
<div class="alert">Your message has been sent, I will get back to you as soon as possible.</div>
The message just instantly disappears and reappears, how can I use the transition currently to make some sort of animation?
This is my first question so sorry if I missed any information out, I will add any more if needed. Thanks
You can't transition (or animate) the display property. the display property is either on or off there's nothing to transition or animate.
What you can do is animate opacity and alter the display property at start and end.
something like:
#keyframes showBlock {
from { display: block; opacity: 0; }
to { opacity: 1; }
}
#keyframes hideBlock {
from { opacity: 1; }
to { opacity: 0; display: none; }
}

Why do we need to clear the animation when running it multiple times in JavaScript?

Yesterday I asked a question (original question) that was promptly answered, but even though a solution was found, I don't understand why this is working the way it is. I can duplicate this solution for other things I need to do but before I continue on I would like to understand why this works the way it does.
So basically I made three functions that called each other. The 1st called the second upon "animationend" and the second called the third upon an "animationend" and the finally the third function called the first to start the cycle all over again - BUT My original code though lacked;
document.getElementById("rightBoxTwo").style.animation = "none";
which was needed in-order for the third function to call the first so the cycle starts all over again. Without the above code in each function the three functions would work only once and then stop. The answer that StackOverFlow user; ScientiaEtVeritas gave me included a CodePen which had a working example of what I needed and a brief explanation
So, I think you have several options: What could work is that you
reset the the animation of rightBox in function runTwo with
animation: none. If you assign scrollTextTwo 10s back to the
rightBox it should start again. Equivalent for the other ones.
So finally my question is WHY does the animation need to be cleared, and why does the .style.animation = "none"; accomplish this?
below is the working code after a solution was presented...
<body onload="runOne()">
function runOne() {
var x = document.getElementById("rightBox");
x.addEventListener("animationend",runTwo);
document.getElementById("rightBox").style.animation = "scrollTextTwo 10s";
document.getElementById("rightBoxTwo").style.animation = "none";
}
function runTwo() {
var x = document.getElementById("rightBoxTwo");
x.addEventListener("animationend",runThree);
document.getElementById("rightBoxTwo").style.animation =
"scrollTextTwo 10s";
document.getElementById("rightBoxThree").style.animation = "none";
}
function runThree() {
var x = document.getElementById("rightBoxThree");
x.addEventListener("animationend",runOne);
document.getElementById("rightBoxThree").style.animation =
"scrollTextTwo 10s";
document.getElementById("rightBox").style.animation = "none";
}
The simplest reason is because setting the animation to the same thing twice (or more times) in a synchronous manner like a for loop is the same as doing it once:
let box = document.getElementById('box');
// animation happens once
for (let i = 0; i < 5; i++) {
box.style.animation = 'fade .5s';
}
#keyframes fade {
from {
opacity: 1
}
to {
opacity: 0
}
}
#box {
height: 200px;
width: 200px;
margin: 50px;
background: #018bbc;
}
<div id="box"></div>
The behavior is the same even if you delay the animation so each time it runs after a possible render:
let box = document.getElementById('box');
// animation still happens once
for (let i = 0; i < 5; i++) {
setTimeout(function() {
box.style.animation = 'fade .5s';
}, i * 1000);
}
#keyframes fade {
from {
opacity: 1
}
to {
opacity: 0
}
}
#box {
height: 200px;
width: 200px;
margin: 50px;
background: #018bbc;
}
<div id="box"></div>
But if I reset the animation before each step, the engine has to re-set the animation, which in a way means to "install the animation again", meaning it will be animated again:
let box = document.getElementById('box');
box.addEventListener('animationend', function() {
box.style.animation = 'none';
});
// animation now happens every time
for (let i = 0; i < 5; i++) {
setTimeout(function() {
box.style.animation = 'fade .5s';
}, i * 1000);
}
#keyframes fade {
from {
opacity: 1
}
to {
opacity: 0
}
}
#box {
height: 200px;
width: 200px;
margin: 50px;
background: #018bbc;
}
<div id="box"></div>
You don't really need javascript for something like this. Keyframes let you define styles by percent complete. Using that you can time 2 animations for a similar result:
#keyframes progress {
0% { width: 0px;}
50% { width: 600px;}
100% {width: 600px;}
}
#keyframes progress2 {
0% { width: 600px;}
49% { width:600px;}
50% { width: 0px;}
100% {width: 600px;}
}
div {
width:600px;
height:50px;
background-color:black;
}
#rightBox {
animation: progress 4s infinite;
}
#rightBoxTwo {
animation: progress2 4s infinite;
}
<div id="rightBox"></div>
<div id="rightBoxTwo"></div>

How to fadeIn element while making a transition?

Here is my problem:
http://jsfiddle.net/GDj7v/
$(document).ready(function () {
$("#field-type").change(function () {
$val = $(this).val();
if ($val == "checkbox") {
$("#check-boxes").addClass("shown");
} else {
$("#check-boxes").removeClass("shown");
}
})
});
Please click on the field type and choose checkbox. My nice transition to show hidden options is working. But I would like to HIDE #check-boxes element, so that it would not be so much white space between Field type and Description. I tried display:none on the #check-boxes element and then in Javascript fadeIn and I get very glitchy results.
Is it possible to do it?
Something like this?
$(document).ready(function() {
$('#check-boxes').hide();
$("#field-type").change(function() {
$val = $(this).val();
if($val == "checkbox") {
$('#check-boxes').show(0, function(){
$("#check-boxes").addClass("shown");
});
} else {
$("#check-boxes").removeClass("shown");
}
})
});
http://jsfiddle.net/GDj7v/3/
jQuery has built in methods of fading in/out. For example:
//replace $("#check-boxes").addClass("shown"); with
$("#check-boxes").show(400); //fade in 400 ms, essentially the same as jQuery's fadeIn()
//$("#check-boxes").removeClass("shown"); with
$("#check-boxes").hide(400); //fade out 400 ms, essentially the same as jQuery's fadeOut()
Show docs
Hide docs
Also, for custom animations you may want to look into jQuery's animate function. Take a look at the docs for detailed help.
You can easily change this by setting the margin-bottom property of your .form-group nodes to 0:
.form-group {
margin-bottom: 0;
}
If you want to use slideDown() and slideUp() with opacity you can do this way using animate()
if ($val == "checkbox") {
$("#check-boxes").css('opacity', 0).slideDown('slow').animate({ opacity: 1 },{duration: 'slow' });
} else {
$("#check-boxes").css('opacity', 1).slideUp('slow').animate({ opacity: 0 },{duration: 'slow' });
}
})
Demo Fiddle
Add this styles:
#check-boxes {
transition: all 1.2s ease-in-out;
transform: translateX(250px) rotateX(135deg);
opacity: 0;
display: none;
}
#check-boxes.shown {
transform: translateX(0px) rotateX(0deg);
opacity: 1;
display:block
}
I see what you mean, I was getting inconsistent results. As you'd expect, display isn't animatable which might be the issue when combining transitions with that.
I tried transitioning the height, but that made it look weird.
In the end I used a setTimeout to separate out changing the display property and making the transition:
http://jsfiddle.net/BYossarian/GDj7v/28/
HTML changes:
<div id="check-boxes" class="hidden">
CSS added:
.hidden {
display: none;
}
JS:
$(document).ready(function () {
$("#field-type").change(function () {
var $val = $(this).val(),
checkboxes = $("#check-boxes");
if ($val === "checkbox") {
checkboxes.removeClass('hidden');
setTimeout(function () {
checkboxes.addClass('shown');
}, 10);
} else {
checkboxes.removeClass('shown');
setTimeout(function () {
checkboxes.addClass('hidden');
}, 1210);
}
});
});
EDIT/ALTERNATE SOLUTION: Also, moving the transition property rule into the rules for #check-boxes.shown means the transition only happens in one direction, which clears up the issue I was having with height looking bad:
http://jsfiddle.net/BYossarian/GDj7v/30/
CSS:
#check-boxes {
-webkit-transform: translateX(250px) rotateX(135deg);
transform: translateX(250px) rotateX(135deg);
opacity: 0;
height: 0;
}
#check-boxes.shown {
transition: all 1.2s ease-in-out;
-webkit-transform: translateX(0px) rotateX(0deg);
transform: translateX(0px) rotateX(0deg);
opacity: 1;
height: auto;
}

Categories

Resources