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>
Related
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 am writing a ReactJS component for the first time. I have a tooltip which needs to have a dynamic delay value on mouseenter and mouseleave events. I am currently using a hover approach in CSS with transition-delay. This solution is working for me however, I need to be able to setState and update each of the transition-delay (see below) through my component. I need to be able to accomplish this with pure ReactJS/Javascript (no JQuery etc).
Here is a sample of my code:
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -60px;
opacity: 0;
transition-delay: 2s;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
transition-delay: 1s;
}
How can I access each of these transition-delay properties from the component and change the value with setState?
Thanks for your help
Update: I have figured out how to update the CSS property through JS. I now need to be able to reset the state. Please see my comment from below.
Here is some additional code:
constructor(props) {
super(props);
this.state = {
userInput: '',
transitionDelay: '0s'
}
handleMouseEnterDelay() {
var mouseIn = document.getElementById('tooltip');
var delayIn = mouseIn.style.transitionDelay = '0s';
this.setState({
transitionDelay: {delayIn}
})
}
handleMouseLeaveDelay() {
var mouseLeave = document.getElementById('tooltiptext');
var delayLeave = mouseLeave.style.transitionDelay = '4s';
this.setState({
transitionDelay: {delayLeave}
})
So what I need is that after each hover event i need transitionDelay to take the values defined in the function. I.e. after first mouseenter/leave event it stays to 4s, so the second time I go to hover(enter) then it is a 4s delay for both enter and leave. I need the enter delay to go back to 0s as defined in the handleMouseEnterDelay function.
Is there a way which I can have two 'transitionDelay' values in setState? I tried a nested object i.e. in:{transitionDelay}, out:{transitionDelay} but i couldn't access it while setting state.
Maybe something like...
constructor() {
this.state = {
style: {
transitionDelay: '1s'
}
};
this.toggleDelay = this.toggleDelay.bind(this);
}
toggleDelay(state) {
this.setState({ style: { transitionDelay: state ? '2s' : '1s' } });
}
...
render() {
return (
<div className="tooltip" style={this.state.style} onMouseEnter={() => this.toggleDelay(true)} onMouseLeave={() => this.toggleDelay(false)}>
.....
</div>
);
}
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'm having some trouble with a transition on a transform. I'm making a tic-tac-toe game like in the freecodecamp front-end challenges: https://codepen.io/freeCodeCamp/full/KzXQgy.
I've been able to create most layout things no problem, but am having an issue with my transition on a div that shows which players' turn it is after hitting the reset button. Right now I'm just working on two player mode, so I click two players, then X or O, and then the tic-tac-toe board shows up and a div transform: translateY(-50px) to indicate whether it's Player 1 or Player 2's turn (based on a random number variable I set up). The first time through the div transition's perfectly. Then I hit the Reset All div and it takes me back to the beginning to choose how many players again. And the div transitions the transform: translateY(0) perfectly back to it's starting position. Where I'm struggling is, now when I cycle through the options again, if it's Player 1 or Player 2's turn again, the transition never happens and the div just transforms -50px up with the translateY.
I've tried everything I could think of, JS setting up the transition, resetting the transition, moving the transition to be on different classes, adding and removing a class that only has a transition on it. Can't figure it out, but the weird thing is, whenever I hit the "Reset All", the transform transitions back to 0px normally. Here's my codepen: https://codepen.io/rorschach1234/pen/dZMaJg?editors=0111. I know it's still very rough, but just can't figure out this transition problem. Really appreciate any help. Thanks everyone!
My relevant Html:
<div class="container">
<div class="turns">
<div class="turns__left turns__box"></div>
<div class="turns__right turns__box"></div>
</div>
</div>
My relevant CSS:
.turns {
width: 100%;
display: flex;
position: absolute;
justify-content: space-around;
top: 0;
left: 0;
z-index: -1;
&__box {
width: 40%;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 1.3em;
font-family: sans-serif;
transition: transform 1s .3s ease;
}
&__left {
background-color: $color-turn-left;
//transition: transform 1s .3s ease;
}
&__right {
background-color: $color-turn-right;
//transition: transform 1s .3s ease;
}
}
My Javascript:
let numOfPlayers = document.querySelectorAll(".player .choices h2");
let playerScreen = document.querySelector(".player");
let markerScreen = document.querySelector(".markers");
let singlePlayer = true;
let backBtn = document.querySelector(".markers__back");
let gameScreen = document.querySelector(".game");
let playerOne; let playerTwo; let activePlayer;
let turnBoxes = document.querySelectorAll(".turns__box");
let resetBtn = document.querySelector(".scoreboard__reset");
game();
function game() {
playerModeSelection();
markerSelection();
}
function boardChange(active, inactive) {
inactive.style.opacity = "0";
inactive.style.zIndex = "0";
active.style.opacity = "1";
active.style.zIndex = "1";
}
//One or Two player Selection & transition to Marker Selector
function playerModeSelection() {
for (let i = 0; i < numOfPlayers.length; i++) {
numOfPlayers[i].addEventListener("click", function() {
if(i === 1) {
singlePlayer = false;
}
boardChange(markerScreen, playerScreen);
})
}
}
function markerSelection() {
//Back Button Functionality
backBtn.addEventListener("click", function() {
boardChange(playerScreen, markerScreen);
})
//Listen for choice of X or O
for (let i = 0; i < markers.length; i ++) {
markers[i].addEventListener("click", function() {
boardChange(gameScreen, markerScreen);
if (i === 1) {
playerArr = ["O", "X"];
}
//Starts Two Player Game; Here begin is the function that calls transition
if(!singlePlayer) {
twoPlayerMode();
}
})
}
}
function twoPlayerMode() {
activePlayer = Math.floor(Math.random() * 2);
turnBoxes[activePlayer].textContent = "Go Player " + (activePlayer + 1) + "!";
turnBoxes[activePlayer].style.transform = "translateY(-50px)";
resetBtn.addEventListener("click", function() {
boardChange(playerScreen, gameScreen);
turnBoxes[activePlayer].style.transform = "translateY(0px)";
})
}
I am using a spritesheet and keyframes to animate the image on a button when it is clicked.
When the button is clicked I want the frames to run in one direction and leave the button on the last image in the spritesheet, and when it is clicked again I want the same frames to run backwards, leaving the button on the first image on the spritesheet.
I am currently trying to use jquery to change the class on the button to an animating class when it is clicked, but this doesn't seem to be working.
fiddle: http://jsfiddle.net/CGmCe/10295/
JS:
function animate(){
$('.hi').addClass('animate-hi');
}
CSS:
.hi {
width: 50px;
height: 72px;
background-image: url("http://s.cdpn.io/79/sprite-steps.png");
}
.animate-hi {
animation: play 2s steps(10);
}
#keyframes play {
from { background-position: 0px; }
to { background-position: -500px; }
}
Make sure you are using an animation-capable browser. For me this works in Firefox.
The following might be just what you wanted:
http://jsfiddle.net/CGmCe/10299/
Code:
function animateButton() {
var button = $('.hi');
if (button.hasClass('animate-hi')) {
button.removeClass('animate-hi').addClass('animate-hi-reverse');
} else if (button.hasClass('animate-hi-reverse')) {
button.removeClass('animate-hi-reverse').addClass('animate-hi');
} else {
button.addClass('animate-hi');
}
};
$(document).ready(function() {
$('.hi').on("click", function() {
animateButton();
});
});
.hi {
width: 50px;
height: 72px;
background-image: url("http://s.cdpn.io/79/sprite-steps.png");
}
.animate-hi {
animation: play 2s steps(10);
}
.animate-hi-reverse {
animation: play-reverse 2s steps(10);
}
#keyframes play {
from {
background-position: 0px;
}
to {
background-position: -500px;
}
}
#keyframes play-reverse {
from {
background-position: -500px;
}
to {
background-position: 0px;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<img src="http://s.cdpn.io/79/sprite-steps.png" />
<button class="hi" type="button"></button>
Make a counter variable which checks if the button is clicked or not.
And based on the counter value add class to the element for example:
var counter=0;
$('.btn').on('click',function(){
if(counter=0)
{
$('.hi').addClass('animate-hi');
counter = 1;
}
else
{
counter = 0;
$('.hi').removeClass('animate-hi');
}
});
Make sure to declare the counter variable outside the function. Else every time its value initialized to 0.