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)";
})
}
Related
I am building an animated hamburger menu with html css js. I now know how to start a css transition with javascript. See https://jsfiddle.net/ralphsmit/byaLfox5/. My problem now is that I need to run more than one transition with a click on my button. I've put my code here https://jsfiddle.net/ralphsmit/v980ouwj/16/.
A short explanation of my code. I have made a button (for the sake of clarity I made it green with a low opacity) and when that button is clicked, the background .dsgn-header-background will appear. Now I also want the two rectangle for the menu to animate into a cross and that the the .dsgn-header-menu-opened-menuitems also fade in.
My question is, how do I modify this js code, so that more than one transition will be started? So all transitions are a different element. You'll find the full code in the JS fiddle above (feel free to edit this).
Javascript:
const background = document.querySelector('.dsgn-header-background');
const button = document.querySelector('.dsgn-header-button');
let open = false;
button.addEventListener('click', onClickPlay);
function onClickPlay(){
if(background.classList.contains('on')){
background.classList.remove('on');
}else{
background.classList.add('on');
}
}
Check this out.
function onClickPlay(){
if(background.classList.contains('on')){
background.classList.remove('on');
element.classList.remove('anotherClassWithDifferentTransitions');
}else{
background.classList.add('on');
element.classList.add('anotherClassWithDifferentTransitions');
}
}
Cheers!
You can try this , The changes is i have added 2 more constant variable which adding on class when menu open and remove on class when menu closes.
const background = document.querySelector('.dsgn-header-background');
const button = document.querySelector('.dsgn-header-button');
const menu_up = document.querySelector('.dsgn-header-rectangle-up');
const menu_down = document.querySelector('.dsgn-header-rectangle-down');
let open = false;
button.addEventListener('click', onClickPlay);
function onClickPlay(){
if(background.classList.contains('on')){
background.classList.remove('on');
menu_up.classList.remove('on');
menu_down.classList.remove('on');
}else{
background.classList.add('on');
menu_up.classList.add('on');
menu_down.classList.add('on');
}
}
hope this will help you .
const content = document.querySelector('.content');
const button = document.querySelector('.dsgn-header-button');
function onClickPlay() {content.classList.toggle('on');}
button.addEventListener('click', onClickPlay);
fiddle: https://jsfiddle.net/s24mbakf/
Add the other elements to your onClickPlay function as you did with demo.
const demo = document.querySelector('.demo');
const demo2 = document.querySelector('.demo2');
const buttondemo = document.querySelector('.buttondemo');
let open = false;
buttondemo.addEventListener('click', onClickPlay);
function onClickPlay(){
if(demo.classList.contains('on')){
demo.classList.remove('on');
demo2.classList.remove('on');
} else {
demo.classList.add('on');
demo2.classList.add('on');
}
}
.demo {
width: 0;
height: 100vh;
background-color: black;
position: absolute;
right: 0;
transition: width 4s;
}
.demo.on {
width: 100vw;
}
.demo2 {
width: 0;
height: 50vh;
background-color: red;
position: absolute;
right: 0;
transition: width 8s;
}
.demo2.on {
width: 100vw;
background-color: yellow;
}
.buttondemo {
position: absolute;
top: 0px;
right: 0px;
width: 100px;
height: 100px;
background-color: green;
}
<div class="demo"><div>
<div class="demo2"><div>
<div class="buttondemo"><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 have a looping animation that rotates a square. When I remove the class that enables the animation, the square instantly goes back to the starting position.
I need to be able to animate the square back to the starting position on a JS event.
Here is a codepen.
I have tried adding a transform to the element, and switching to another keyframe animation but neither worked.
html:
<div class="container">
<div class="animate rotating"></div>
</div>
css:
.container{
position: absolute;
width: 100%;
height: 100%;
background-color: #333;
}
.animate{
width: 100px;
height: 100px;
background-color: red;
position: absolute;
top: 50%;
left: 50%;
margin: -50px 0 0 -50px;
transform: all 2s ease;
}
#keyframes rotating {
0%{transform: rotate(30deg);}
50%{transform: rotate(-30deg);}
100%{transform: rotate(30deg);}
}
.rotating {
animation: rotating 6s ease-in-out infinite forwards;
animation-delay: -2s;
}
jquery:
$('.animate').click(function(){
$(this).removeClass('rotating');
});
There is a little problem with your code:
transform: all 2s ease;
You mistyped transition, however I'm pretty sure this approach isn't going to work. If you're willing to accomplish this using jQuery you can get current rotation and apply to your element:
function getRotationDegrees(obj) {
var matrix = obj.css("-webkit-transform") ||
obj.css("-moz-transform") ||
obj.css("-ms-transform") ||
obj.css("-o-transform") ||
obj.css("transform");
if(matrix !== 'none') {
var values = matrix.split('(')[1].split(')')[0].split(',');
var a = values[0];
var b = values[1];
var angle = Math.round(Math.atan2(b, a) * (180/Math.PI));
} else { var angle = 0; }
return (angle < 0) ? angle + 360 : angle;
}
$('.animate').click(function(){
var el = $(this);
var elRotate = getRotationDegrees(el);
el.css('transform',`rotate(${elRotate}deg)`);
el.removeClass('rotating');
// set new transform here
});
This will freeze your element rotation, you can set your desired rotation at the end and transition will be applied.
I'm looking to scroll through a story, but let the user press (space) for the next phrase. I think it could be a beautiful optional intro on a website.
Already done: I've managed to scroll through an array of strings as the user presses (space):
CodePen: https://codepen.io/anon/pen/PbRLdp
React / JS:
var strings = ["Hi","it's not easy finding a freelancer, is it?", "referrals don't always come", "you need to know it'll get done", "I get it.", "perhaps we should connect"];
var i = 0;
var hitElement = document.querySelector( '.storylines' );
document.body.onkeyup = function(e) {
if( e.keyCode == 32 ) {
addHit();
}
}
var addHit = function() {
if ( i+2 <= strings.length) {
i++
renderStories();
}
}
var renderStories = function() {
hitElement.innerHTML = strings[i];
}
HTML:
<span class="storylines">press (spacebar)</span>
My question: How can I use React to create a transition between phrases? I'm thinking of translating / fading the current span downwards, and fading the new span in (without translating it).
I tried to change your question as little as possible since I'm not that familiar with reactJS.
I would do it only by css. This will allow you to change what each transition focuses on without too much code added (opacity vs. opacity and placement).
var strings = ["Hi", "it's not easy finding a freelancer, is it?", "referrals don't always come", "you need to know it'll get done", "I get it.", "perhaps we should connect"];
var i = 0;
var hitElement = document.querySelector('.storylines');
document.body.onkeyup = function(e) {
if (e.keyCode == 32) {
addHit();
}
}
var addHit = function() {
if (i + 2 <= strings.length) {
i++
renderStories();
}
}
var renderStories = function() {
hitElement.classList.remove('enter');
hitElement.classList.add('hide');
setTimeout(function() {
hitElement.innerHTML = strings[i];
hitElement.classList.remove('hide');
hitElement.classList.add('enter');
}, 250);
}
.storylines {
display: inline-block;
transition: opacity 250ms linear 150ms;
opacity: 0;
}
.storylines.hide {
transition: all 250ms linear;
transform: translateY(15px);
opacity: 0;
}
.storylines.enter {
opacity: 1;
}
<span class="storylines enter">press (spacebar)</span>
NOTICE: click on Expand snippet to not have the spacebar scroll the stackoverflow page :-)
This is the logic flow:
The page starts with the storylines class and the enter class.
This is to have the 1st element already shown.
When the change occurs, remove the enter class, and add the hide class to make the class leave.
hide will make sure the element disappears and moves down. enter is removed so it won't override the opacity of the element.
Also, hide contains a different transition that will animate all changes. This allows the transform to also move the element on exit.
After a timeout, add the enter class and remove the hide class
Here, since we remove the hide class, the transition changes to animate only opacity. So, the element appears in place instead of moving up or down.
NOTICE that the enter animation as a delay value added, so it won't happen in the same time as the hide animation.
transition: opacity 250ms linear 150ms;
Also, you can change the animations using css only, which I think is a good separation of roles.
Try this on your last function:
var renderStories = function() {
hitElement.style.opacity = 0;
setTimeout(function(){
hitElement.innerHTML = strings[i];
hitElement.style.opacity = 1;
}, 600);
}
An this in your CSS:
.storylines {
transition: all 0.6s;
opacity: 1;
}
The JS timeout should be exactly the transition time for your CSS element.
EDIT: With translate
var renderStories = function() {
hitElement.style.opacity = 0;
hitElement.style.transform = "translateY(-100%)";
setTimeout(function(){
hitElement.innerHTML = strings[i];
hitElement.style.opacity = 1;
hitElement.style.transform = "translateY(0%)";
}, 600);
}
CSS
.storylines {
transition: all 0.6s;
opacity: 1;
display: block; /* the element needs to be seen as solid */
}
EDIT 2: Changed the location so the new line appears in the same place:
var renderStories = function() {
hitElement.style.opacity = 0;
hitElement.style.transform = "translateY(-100%)";
setTimeout(function(){
hitElement.style.trasition = "opacity 0.3s, transform 0s"
hitElement.style.transform = "translateY(0%)";
}, 300);
setTimeout(function(){
hitElement.innerHTML = strings[i];
hitElement.style.opacity = 1;
hitElement.style.trasition = "opacity 0.3s, transform 0.3s"
}, 600);
}
CSS:
.storylines {
transition: opacity 0.3s, transform 0.3s;
opacity: 1;
display: block; /* the element needs to be seen as solid */
}
If you want it slower make sure that the 600 is the double of 0.3 or 300 with whatever value you're changing.