setTimeout inside setTimeout inside a setInterval - javascript

I'm trying to make a string that will write itself letter by letter until completing the sentence, and the speed of appearing each letter is based on an input that varies from 1 to 10. At the end of the string, it will blink for 5 seconds until that an alien will appear. My idea was to create a setInterval to add the letters and when the counter added the array size it would return the final animation of the loop with the new setInterval call, and before it was called again it had already been cleared, and called again in a recursion by setTimout callback to maintain the infinite loop. But it's not reaching setTimout, why?
//script.js
const speedInput = document.getElementsByClassName('speed--input')[0];
const alien = document.getElementsByClassName('alien')[0];
const textDiv = document.getElementsByClassName('text')[0];
const textShow = document.getElementsByClassName('text--show')[0];
const textDB = 'We go to dominate the world.';
const textStr = '';
let count = 0;
let speed = speedInput.value * 100;
const textChangeHandle = (count, textStr, textDB) => setInterval(() => {
if (count < textDB.length) {
textStr += textDB[count];
textShow.innerHTML = textStr;
textDiv.style.width = `${40 * count}px`;
count++;
} else {
textShow.style.animation = 'bip 1s linear 1s infinite'
return () => {
setTimeout(() => {
textShow.style.animation = '';
textStr = '';
count = 0;
textShow.style.opacity = 0;
alien.style.opacity = 1;
setTimeout(() => {
alien.style.opacity = 0;
textShow.style.opacity = 1;
textChangeHandle(count, textStr, textDB)
}, 5000)
}, 5000);
clearInterval(textChangeHandle);
}
}
}, speed)
textChangeHandle(count, textStr, textDB);
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style/style.css">
<title>Document</title>
</head>
<body>
<div class="text">
<p class="text--show"></p>
<img class="alien" src="alien.png" alt="Aki é Jupiter karai">
</div>
<div class="speed">
<span>Speed</span>
<input class="speed--input" type="number" min="1" max="10" value="1">
</div>
<script src="script.js"></script>
</body>
</html>
//style.css
*,
*::after,
*::before {
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
body {
font-family: sans-serif;
background-color: #3cd070;
}
.text {
position: fixed;
top: 50%;
left: 50%;
height: auto;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
font-size: 40px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.text .alien {
opacity: 0;
height: 600px;
width: auto;
margin-bottom: 50px;
position: absolute;
}
.text .text--show {
font-size: 40px;
width: 100%;
position: absolute;
display: block;
text-align: center;
-webkit-animation: bip 1s linear 1s infinite;
animation: bip 1s linear 1s infinite;
}
.speed {
position: fixed;
top: 90%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.2);
display: -webkit-box;
display: -ms-flexbox;
display: flex;
padding: 10px 20px;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
}
.speed .speed--input {
border: 0;
outline: 0;
width: 40px;
height: 25px;
margin: 10px;
text-align: center;
}
#-webkit-keyframes bip {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
#keyframes bip {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}

There is also the animation frame api
It will run your function exactly before the next "natural" repaint of the browser.
Additionally and probably useful in your case, it will pass the current time in your callback, so you can take a time detail and animate according to this. That way you can get a smooth animation of equal length for everyone, even when the frame rate is inconsistent.
Consider the following snippet, which will basically cause an infinite loop.
function draw(now) {
requestAnimationFrame(draw)
}
requestAnimationFrame(draw)
Now you only need to remember the time and take the time delta next time the function is called. If enough time has passed, you can write another character. (Normally you would change a value divided by the time delta, but since you have characters, they will be either there or not.
let speed = 300
let timePassedSinceLastChar = 0
let then = null
function draw(now){
now *= 0.001;
const deltaTime = now - then;
then = now;
timePassedSinceLastChar += deltaTime;
if (timePassedSinceLastChar >= speed) {
drawChar()
timePassedSinceLastChar = 0
}
requestAnimationFrame(draw)
}
requestAnimationFrame(draw)
Furthermore, the requestAnimationFrame function returns the id of the requested frame. That allows to cancel the loop.
const frameId = requestAnimationFrame(draw)
cancelAnimationFrame(frameId)
So the final code could look something like this.
const textDB = 'We go to dominate the world.';
let charIndex = 0;
let frameId = null
let then = 0
let sinceDraw = 0
let speed = () => Math.random() * 0.4 + 0.05 // randomize the speed a bit
let $p = document.querySelector('p')
function draw(now) {
// cancel on end of string
if (charIndex >= textDB.length) {
cancelAnimationFrame(frameId)
console.log('done')
return
}
// get the time delta
now *= 0.001; // convert to seconds
const deltaTime = now - then;
then = now;
sinceDraw += deltaTime
// if its time to draw again, do so
if (sinceDraw >= speed()) {
let char = textDB[charIndex]
$p.textContent += char
charIndex++
sinceDraw = 0
}
// request another frame
frameId = requestAnimationFrame(draw)
}
// request the first frame
frameId = requestAnimationFrame(draw)
<p></p>

The issue is that in the else statement, you are returning a function that is never called.
return () => {
setTimeout(() => {
textShow.style.animation = '';
textStr = '';
count = 0;
textShow.style.opacity = 0;
alien.style.opacity = 1;
setTimeout(() => {
alien.style.opacity = 0;
textShow.style.opacity = 1;
textChangeHandle(count, textStr, textDB)
}, 5000)
}, 5000);
clearInterval(textChangeHandle);
}
Just remove the return statment and call the setTimeout function directly.
const textChangeHandle = (count, textStr, textDB) => setInterval(() => {
if (count < textDB.length) {
textStr += textDB[count];
textShow.innerHTML = textStr;
textDiv.style.width = `${40 * count}px`;
count++;
} else {
textShow.style.animation = 'bip 1s linear 1s infinite'
setTimeout(() => {
textShow.style.animation = '';
textStr = '';
count = 0;
textShow.style.opacity = 0;
alien.style.opacity = 1;
setTimeout(() => {
alien.style.opacity = 0;
textShow.style.opacity = 1;
textChangeHandle(count, textStr, textDB)
}, 5000)
}, 5000);
clearInterval(textChangeHandle);
}
}, speed)

I found the solution. With setInterval, the code was creating multiple instances of callbacks and overloading memory.
///script.js
const textEl = document.getElementById('text');
const inputEl = document.getElementById('input');
const alienEl = document.getElementById('alien');
const text = 'We go to dominate the world!';
let idx = 0;
let speed = 300 / inputEl.value;
writeText();
function writeText () {
if(idx === 0) setTimeout(() => textEl.style.opacity = 1, speed)
textEl.innerText = text.slice(0, idx);
idx++;
if (idx > text.length) {
idx = 0;
textEl.style.animation = 'bip 1s linear 1s infinite';
setTimeout(() => {
textEl.style.animation = ''
textEl.style.opacity = 0;
setTimeout(() => {
alienEl.style.opacity = 1;
setTimeout(() => {
alienEl.style.opacity = 0;
setTimeout(writeText, speed);
}, 5000)
}, 1000);
}, 5000)
} else {
setTimeout(writeText, speed);
}
}
inputEl.addEventListener('input', (e) => speed = 300 / e.target.value);

Related

Why does window.requestAnimationFrame not keeping the last transition?

i'm trying to build my own simple fullpage.js or something similar, just so I can learn more about how to use requestAnimationFrame function, however I am stuck into this problem of transform3d value resetting, which behaves like this
You can see in the gif that every time I switch slides, the animation starts always at the first slide
Here is the implementation of the slider
html
<div class="scroller">
<div class="scroller-content">
<section class="scroller-section s1">SLIDE 1</section>
<section class="scroller-section s2">SLIDE 2</section>
<section class="scroller-section s3">SLIDE 3</section>
<section class="scroller-section s4">SLIDE 4</section>
</div>
</div>
css
.scroller {
width: 100vw;
height: 100vh;
overflow-y: hidden;
}
.scroller-section {
width: 100vw;
height: 100vh;
display: flex;
text-align: center;
align-items: center;
justify-content: center;
font-size: 25rem;
}
.scroller-section.s1 {
background-color: #535bf2;
}
.scroller-section.s2 {
background-color: #242424;
}
.scroller-section.s3 {
background-color: #213547;
}
.scroller-section.s4 {
background-color: #747bff;
}
const scrollerContent = document.querySelector(".scroller-content")
const offsets = [0, -100, -200, -300]
let index = 0
window.addEventListener("wheel", (e) => {
if (e.deltaY > 0) {
index = shift(index + 1) // go next
const offset = offsets[index]
createAnimationFrames((delta) => {
scrollerContent.style.transform = `translate3d(0, ${delta * offset}vh,0)`
}, 1000)
} else if (e.deltaY < 0) {
index = shift(index - 1) // go prev
const offset = offsets[index]
createAnimationFrames((delta) => {
scrollerContent.style.transform = `translate3d(0, ${delta * offset}vh,0)`
}, 1000)
}
})
Here is the implementation of createAnimationFrames
const createAnimationFrames = (callback: (delta: number) => void, duration: number) => {
let start = new Date();
let raf = requestAnimationFrame;
const animate = () => {
let now = new Date();
const delta = Math.min((now.getTime() - start.getTime()) / duration, 1);
callback(delta);
if (delta < 1) {
raf(animate);
}
};
raf(animate);
};
Basically it runs the callback every frame request and the callback takes delta as a reference for the progress of the animation.
The thing is that it works normally by just having a simple reassign to the transform attribute and not using the requestAnimationFrame. Any solutions for this?

Create blinking countdown timer in CSS

I am trying to create a blinking countdown timer, starting it at 5 seconds, and disappearing when it gets to 0.
CSS:
.blinky {
transition: 1s opacity;
-moz-transition: 1s opacity;
-webkit-transition: 1s opacity;
}
HTML:
<div id="countdown" class="blinky">
JS:
const cdStart = 5;
countdown.innerHTML = cdStart;
countdown.style.opacity = 0;
for (var i = cdStart - 1; i > 0; i--) {
setTimeout(
(x) => {
countdownTime.innerHTML = x;
countdown.classList.remove('blinky');
countdown.style.opacity = 1;
countdown.classList.add('blinky');
countdown.style.opacity = 0;
},
1000 * (cdStart - i),
i
);
}
What I want this to do is to show 5, fade out, 4, fade out, 3, fade out, 2, fade out, 1, fade out. When a new number is shown on the timer, I want it to show up instantly, and not fade back in. For that reason, I remove the "blinky" class before I set the opacity to 1, then add it back in before setting opacity to 0.
Unfortunately, this doesn't seem to work - 5 shows up and fades out, and then nothing else shows up. If I remove the manipulation of countdown's style (and just set the innerHTML in the for loop), I see that the timer displays properly (counts down from 3 to 1), so that's working.
I thought, maybe the browser is having trouble removing a class and then immediately adding it back in, so I separated those events by a bit:
CSS:
.blinky {
transition: .9s opacity;
-moz-transition: .9s opacity;
-webkit-transition: .9s opacity;
}
JS:
const cdStart = 5;
countdownTime.innerHTML = cdStart;
countdown.style.opacity = 0;
for (var i = cdStart - 1; i > 0; i--) {
setTimeout(
(x) => {
countdownTime.innerHTML = x;
countdown.classList.remove('blinky');
countdown.style.opacity = '';
},
1000 * (cdStart - i),
i
);
setTimeout(() => {
countdown.classList.add('blinky');
countdown.style.opacity = 0;
}, 1000 * (cdStart - i) + 100);
}
This one was closer - I saw 5, fade out, then nothing for a bit, and 1 came in and then faded out.
Is there a more reliable way to get the desired behavior here?
With CSS Animations you can create the fade-out for every second that is changed in the counter.
Add an animation with 5 iterations and listen for the animationiteration and animationend events. These events are fired for every time the animation plays and restarts, and for when the animation is finished.
Change the count and update the textContent of the countdown in both event handlers.
const countdown = document.querySelector('#countdown');
let count = 5;
function updateCount() {
count--;
countdown.textContent = count;
}
countdown.textContent = count;
countdown.classList.add('blinky');
countdown.addEventListener('animationiteration', updateCount);
countdown.addEventListener('animationend', updateCount);
#keyframes blink-out {
0%, 25% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.blinky {
font-size: 48px;
animation: blink-out 1s ease-out forwards 5;
}
<div id="countdown"></div>
Hope I understood correctly :
let clock = document.getElementById('seconds');
let secondsRemaining = 5;
clock.innerText = secondsRemaining;
clock.classList.add('animation');
const myInterval = setInterval(()=>{
secondsRemaining--;
clock.innerText = secondsRemaining;
if(!secondsRemaining)
stopAnimation();
},1000)
function stopAnimation(){
clock.classList.remove('animation');
clearInterval(myInterval);
}
.clock{
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
}
.animation{
animation:1s fade-out ease;
animation-iteration-count:infinite;
animation-fill-mode:forwards;
}
#seconds{
font-size:10rem;
}
#keyframes fade-out{
from{opacity:1}
99%{opacity:0}
to{opacity:1}
}
<body>
<div class="clock">
<span id="seconds"></span>
</div>
</body>
Here is an example using async functions and css transition to control fading.
const wait = ms => new Promise(r => setTimeout(r, ms));
const $ = str => document.querySelector(str);
const btn = $("button");
const number = $(".number");
const DELAY_TIME = 1000;
async function changeNumber(newVal) {
number.classList.add("fade");
await wait(DELAY_TIME);
number.textContent = newVal;
number.classList.remove("fade");
await wait(DELAY_TIME);
}
async function countFrom(n) {
const STARTING_VALUE = n;
number.textContent = STARTING_VALUE;
await wait(DELAY_TIME);
for (let counterVal = STARTING_VALUE - 1; counterVal >= 0; counterVal--) {
await changeNumber(counterVal);
}
}
btn.addEventListener("click", async () => {
btn.disabled = true;
await countFrom(5);
btn.disabled = false;
});
.timer {
display: grid;
place-items: center;
width: 100px;
height: 150px;
border-radius: 20px;
border: 5px solid lightgreen;
}
.number {
font-size: 5rem;
font-family: monospace;
transition: opacity 1s;
}
.number.fade {
opacity: 0;
}
<div class="timer">
<span class="number"></span>
</div>
<br />
<button>count from 5!</button>

How to decrease anime time when it is out of screen

I have a anime whose duration decreases when the black container crosses red, but this creates glitches. Can there be any method to remove the glitch in it.
I tried to delay the changes until path is completed by red but still faces the glitches.
delayInAnimeSub = ourVillanAnimeDuration * (ourVillanFigXValue / window.innerWidth)
animeDelayAmount = Math.abs(delayInAnimeSub.toFixed(2) - 0.2).toFixed(2);
I calculated the leftover distance of red from the left side and get the duration to complete that distance and added that to delay. But it still show glitches.
Here what happens in below snippet
Black box is hero which is controlled by space(to jump), <(to move left), >(to move right).
Red is villan(demon) which have animation to move from right side to left side with some duration in animation.
Whenever hero passes the red, red becomes faster(by trying to reduce animation duration), but with this red has glitches and start from anywhere(not from the place it should be next).
I want to increase speed of red but facing the glitches so tried to delay the change in animation duration until red crosses the screen but that didn't seems to work
let ourHeroFig = document.getElementById("ourHero");
let ourVillanFig = document.getElementById("obstacleBar");
let gameScoreDigits = document.getElementById("gameScoreDigits");
let valueXCoordinate = "";
let obstacleBarCrossed = true;
document.body.addEventListener('keydown', function(e) {
let ourHeroFigXValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('left'));
let ourHeroFigYValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('bottom'));
if (e.code === "ArrowRight") {
valueXCoordinate = ourHeroFigXValue + 100;
} else if (e.code === "KeyA" || e.code === "ArrowLeft") {
if (ourHeroFigXValue > ourHeroFig.offsetWidth + 90) {
valueXCoordinate = ourHeroFigXValue - 100;
} else {
valueXCoordinate = 0;
}
} else if (e.code === "Space") {
ourHeroFig.classList.add("animateHero");
setTimeout(function() {
ourHeroFig.classList.remove("animateHero");
}, 700)
}
changePosition();
})
function changePosition() {
ourHeroFig.style.left = valueXCoordinate + 'px'
}
let delayInAnimeSub = ""
setInterval(
function() {
let ourHeroFigXValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('left'));
let ourHeroFigYValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('bottom'));
let ourVillanFigXValue = parseInt(getComputedStyle(ourVillanFig).getPropertyValue('left'));
let ourVillanFigYValue = parseInt(getComputedStyle(ourVillanFig).getPropertyValue('bottom'));
let gameOverValueX = Math.abs(ourVillanFigXValue - ourHeroFigXValue);
let gameOverValueY = Math.abs(ourVillanFigYValue - ourHeroFigYValue);
if (gameOverValueX < ourVillanFig.offsetWidth && gameOverValueY < ourVillanFig.offsetHeight) {
console.log("yes touched");
ourVillanFig.classList.remove("animateVillan");
obstacleBarCrossed = false;
} else if (obstacleBarCrossed && gameOverValueX < ourVillanFig.offsetWidth) {
ourVillanAnimeDuration = parseFloat(getComputedStyle(ourVillanFig).getPropertyValue('animation-duration'));
delayInAnimeSub = ourVillanAnimeDuration * (ourVillanFigXValue / window.innerWidth)
animeDelayAmount = Math.abs(delayInAnimeSub.toFixed(2) - 0.2).toFixed(2);
console.log(animeDelayAmount, ourVillanAnimeDuration, ourVillanFigXValue)
if (ourVillanAnimeDuration <= 2) {
ourVillanAnimeDuration = 2
}
setTimeout(() => {
ourVillanFig.style.animationDuration = ourVillanAnimeDuration - 0.1 + "s";
}, animeDelayAmount);
}
// console.log(gameOverValueX,gameOverValueY)
}, 10);
#ourHero {
width: 20px;
height: 100px;
background-color: black;
position: fixed;
bottom: 0;
left: 0;
transition: 0.1s;
}
.animateHero {
animation: animateHero 0.7s linear;
}
#keyframes animateHero {
0% {
bottom: 0;
}
50% {
bottom: 350px;
}
100% {
bottom: 0;
}
}
#obstacleBar {
width: 20px;
height: 100px;
background-color: red;
position: fixed;
bottom: 0;
left: 50vw;
}
.animateVillan {
animation: animateVillan 5s linear infinite;
}
#keyframes animateVillan {
0% {
left: 110vw;
}
100% {
left: 0;
}
}
<div id="ourHero"></div>
<div id="obstacleBar" class="animateVillan"></div>
Thanks for help in advance
One way is to use Animation.playbackRate but it is still experimental.
let VillRate = 1
...
// inside the collision check
if (VillRate < 20) {
ourVillanFig.getAnimations()[0].playbackRate += 0.2
VillRate += 0.5
}
In the below snippet, red speed will increase untill a certain point everytime it touches black. You can adjust that behavior as you need.
let ourHeroFig = document.getElementById('ourHero')
let ourVillanFig = document.getElementById('obstacleBar')
let gameScoreDigits = document.getElementById('gameScoreDigits')
let valueXCoordinate = ''
let obstacleBarCrossed = true
let VillRate = 1
document.body.addEventListener('keydown', function(e) {
let ourHeroFigXValue = parseInt(
getComputedStyle(ourHeroFig).getPropertyValue('left')
)
let ourHeroFigYValue = parseInt(
getComputedStyle(ourHeroFig).getPropertyValue('bottom')
)
if (e.code === 'ArrowRight') {
valueXCoordinate = ourHeroFigXValue + 100
} else if (e.code === 'KeyA' || e.code === 'ArrowLeft') {
if (ourHeroFigXValue > ourHeroFig.offsetWidth + 90) {
valueXCoordinate = ourHeroFigXValue - 100
} else {
valueXCoordinate = 0
}
} else if (e.code === 'Space') {
ourHeroFig.classList.add('animateHero')
setTimeout(function() {
ourHeroFig.classList.remove('animateHero')
}, 700)
}
changePosition()
})
function changePosition() {
ourHeroFig.style.left = valueXCoordinate + 'px'
}
let delayInAnimeSub = ''
setInterval(function() {
let ourHeroFigXValue = parseInt(
getComputedStyle(ourHeroFig).getPropertyValue('left')
)
let ourHeroFigYValue = parseInt(
getComputedStyle(ourHeroFig).getPropertyValue('bottom')
)
let ourVillanFigXValue = parseInt(
getComputedStyle(ourVillanFig).getPropertyValue('left')
)
let ourVillanFigYValue = parseInt(
getComputedStyle(ourVillanFig).getPropertyValue('bottom')
)
let gameOverValueX = Math.abs(ourVillanFigXValue - ourHeroFigXValue)
let gameOverValueY = Math.abs(ourVillanFigYValue - ourHeroFigYValue)
if (
gameOverValueX < ourVillanFig.offsetWidth &&
gameOverValueY < ourVillanFig.offsetHeight
) {
if (VillRate < 20) {
ourVillanFig.getAnimations()[0].playbackRate += 0.2
VillRate += 0.5
}
}
}, 10)
#ourHero {
width: 20px;
height: 100px;
background-color: black;
position: fixed;
bottom: 0;
left: 0;
transition: 0.1s;
}
.animateHero {
animation: animateHero 0.7s linear;
}
#keyframes animateHero {
0% {
bottom: 0;
}
50% {
bottom: 350px;
}
100% {
bottom: 0;
}
}
#obstacleBar {
width: 20px;
height: 100px;
background-color: red;
position: fixed;
bottom: 0;
left: 50vw;
}
.animateVillan {
animation: animateVillan 5s linear infinite;
}
#keyframes animateVillan {
0% {
left: 110vw;
}
100% {
left: 0;
}
}
<div id="ourHero"></div>
<div id="obstacleBar" class="animateVillan"></div>
I tried this:
if (ourVillanFigXValue < 10) {
ourVillanFig.style.animationDuration = ourVillanAnimeDuration - 0.1 + "s";
}
It works ok but when time get reduced to 4s and after it, the red don't start from the end but start in between. Almost start from middle at 3s
let ourHeroFig = document.getElementById("ourHero");
let ourVillanFig = document.getElementById("obstacleBar");
let gameScoreDigits = document.getElementById("gameScoreDigits");
let valueXCoordinate = "";
let obstacleBarCrossed = true;
document.body.addEventListener('keydown', function(e) {
let ourHeroFigXValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('left'));
let ourHeroFigYValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('bottom'));
if (e.code === "ArrowRight") {
valueXCoordinate = ourHeroFigXValue + 100;
} else if (e.code === "KeyA" || e.code === "ArrowLeft") {
if (ourHeroFigXValue > ourHeroFig.offsetWidth + 90) {
valueXCoordinate = ourHeroFigXValue - 100;
} else {
valueXCoordinate = 0;
}
} else if (e.code === "Space") {
ourHeroFig.classList.add("animateHero");
setTimeout(function() {
ourHeroFig.classList.remove("animateHero");
}, 700)
}
changePosition();
})
function changePosition() {
ourHeroFig.style.left = valueXCoordinate + 'px'
}
let delayInAnimeSub = ""
setInterval(
function() {
let ourHeroFigXValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('left'));
let ourHeroFigYValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('bottom'));
let ourVillanFigXValue = parseInt(getComputedStyle(ourVillanFig).getPropertyValue('left'));
let ourVillanFigYValue = parseInt(getComputedStyle(ourVillanFig).getPropertyValue('bottom'));
let gameOverValueX = Math.abs(ourVillanFigXValue - ourHeroFigXValue);
let gameOverValueY = Math.abs(ourVillanFigYValue - ourHeroFigYValue);
if (ourVillanFigXValue < 10) {
ourVillanFig.style.animationDuration = ourVillanAnimeDuration - 0.1 + "s";
}
if (gameOverValueX < ourVillanFig.offsetWidth && gameOverValueY < ourVillanFig.offsetHeight) {
console.log("yes touched");
ourVillanFig.classList.remove("animateVillan");
obstacleBarCrossed = false;
} else if (obstacleBarCrossed && gameOverValueX < ourVillanFig.offsetWidth) {
ourVillanAnimeDuration = parseFloat(getComputedStyle(ourVillanFig).getPropertyValue('animation-duration'));
console.log(ourVillanFigXValue < 0, ourVillanAnimeDuration)
if (ourVillanAnimeDuration <= 2) {
ourVillanAnimeDuration = 2
}
}
// console.log(gameOverValueX,gameOverValueY)
}, 10);
#ourHero {
width: 20px;
height: 180px;
background-color: black;
position: fixed;
bottom: 0;
left: 0;
transition: 0.1s;
}
.animateHero {
animation: animateHero 0.7s linear;
}
#keyframes animateHero {
0% {
bottom: 0;
}
50% {
bottom: 350px;
}
100% {
bottom: 0;
}
}
#obstacleBar {
width: 20px;
height: 180px;
background-color: red;
position: fixed;
bottom: 0;
left: 50vw;
}
.animateVillan {
animation: animateVillan 5s linear infinite;
}
#keyframes animateVillan {
0% {
left: 110vw;
}
100% {
left: 0;
}
}
<div id="ourHero"></div>
<div id="obstacleBar" class="animateVillan"></div>

css transition issue with slider

i have provided all my codes here all seems good the problem is when i change from last slide to first or the opposite in both case all slides flips differently instead rotating like with other sildes
render() function is the place where all co-ordinates are calculating
i think the issue is with CSS transition since its the main thing which is animating slides, i dont see any easy fix for this if you can find any tell me please looking for easy solution or possibility without messing too much with co-ordinates . thanks in advance
class slider {
constructor(){
if(typeof arguments[0] != "undefined"){
this.init()
arguments[0].appendChild(this.html)
this.calculate()
this.render(1)
}
else{this.init()}
}
init(){
let obj = this
this.html = `
<div class="root border-2 p-5 border-black m-auto">
<div class="each border border-black absolute text-center">item name 1</div>
</div>
`
let el = document.createElement("div")
el.innerHTML = this.html
this.html = el.querySelector("*")
this.root = this.html
let eEl = this.root.querySelector(".each")
this.eachTemp = eEl.cloneNode(true)
eEl.remove()
this.fillBoxes(6)
this.html.addEventListener("click",function(e){
obj.onclick(obj,e)
})
}
onclick(obj,e){
let t = e.target
if(t.closest(".each")){
obj.render(t.index)
}
}
fillBoxes(num){
for(let i=0;i<num;i++){
let newEl = this.eachTemp.cloneNode(true)
newEl.index = i+1
newEl.innerText = "Item " + (i+1)
this.html.appendChild(newEl)
}
this.els = this.html.querySelectorAll(".each")
}
calculate(){
this.eCor = this.els[0].getClientRects()[0]
this.mag = this.eCor.width
this.per = Math.PI / (this.els.length / 2)
this.deg = this.rtod(this.per)
}
render(index){
this.els.forEach((each,i)=>{
let rad = (this.per * i) - this.per*(index-1),
x = Math.sin(rad)*this.mag,
y = Math.cos(rad)*this.mag
if(i+1 == index){each.classList.add("active")}
else{each.classList.remove("active")}
each.style.transform = `translate3d(${x}px,0%,${y}px)`
each.style.transform += `rotateY(${this.rtod(rad)}deg)`
})
}
rtod(radians){
return radians * (180/Math.PI)
}
}
const s = new slider(document.body)
.each{
height: 250px;
width: 250px;
background: skyblue;
opacity: 0.8;
transform-origin: 50% 50%;
transform-style: preserve-3d;
user-select: none;
filter: brightness(80%);
transition: all 1s cubic-bezier(0, 1.22, 0.53, 1.02);
/* box-shadow: 0px 0px 10px 1px black; */
}
.each:nth-child(odd){background:lightsalmon}
.root{
height: 280px;
width: 280px;
position: relative;
margin-top: 10%;
perspective: 1000px;
transform-style: preserve-3d;
transition: transform 0.5s;
/* animation: loop 4s infinite linear; */
}
.each:hover{
z-index: 1000 !important;
filter:brightness(110%);
}
.active{
opacity: 1 !important;
filter:brightness(100%) !important;
}
<link href="https://unpkg.com/tailwindcss#^2/dist/tailwind.min.css" rel="stylesheet">
When you switch between 1 and 6 slides, your calculated rotatedY value changes from 0deg to 300deg (or from 300deg to 0deg). But actually you need only ±60deg update. So you need a mechanism to increase/decrease your rotatedY endlessly in both ways, for example 240 -> 300 -> 360 -> 420 -> 480 ... (not 240 -> 300 -> 0 -> 60 -> ...)
I suggest you to have these properties in your class:
currentIndex = 1;
radOffset = 0;
Then, when you click on element, you need to calculate radOffset (it increases/decreases endlessly):
const totalElements = this.els.length;
const rightDiff = t.index > this.currentIndex ? t.index - this.currentIndex : totalElements + t.index - this.currentIndex;
const leftDiff = totalElements - rightDiff;
const closestDiff = rightDiff < leftDiff ? - rightDiff : leftDiff;
this.currentIndex = t.index;
this.radOffset += this.per * closestDiff;
Then use it in your render function:
let rad = (this.per * i) + this.radOffset,
class slider {
currentIndex = 1;
radOffset = 0;
rotateOffset = 0;
constructor(){
if(typeof arguments[0] != "undefined"){
this.init()
arguments[0].appendChild(this.html)
this.calculate()
this.render(1)
}
else{this.init()}
}
init(){
let obj = this
this.html = `
<div class="root border-2 p-5 border-black m-auto">
<div class="each border border-black absolute text-center">item name 1</div>
</div>
`
let el = document.createElement("div")
el.innerHTML = this.html
this.html = el.querySelector("*")
this.root = this.html
let eEl = this.root.querySelector(".each")
this.eachTemp = eEl.cloneNode(true)
eEl.remove()
this.fillBoxes(6)
this.html.addEventListener("click",function(e){
obj.onclick(obj,e)
})
}
onclick(obj,e){
let t = e.target
if(t.closest(".each")){
const totalElements = this.els.length;
const rightDiff = t.index > this.currentIndex ? t.index - this.currentIndex : totalElements + t.index - this.currentIndex;
const leftDiff = totalElements - rightDiff;
const closestDiff = rightDiff < leftDiff ? - rightDiff : leftDiff;
this.currentIndex = t.index;
this.radOffset += this.per * closestDiff;
obj.render(t.index);
}
}
fillBoxes(num){
for(let i=0;i<num;i++){
let newEl = this.eachTemp.cloneNode(true)
newEl.index = i+1
newEl.innerText = "Item " + (i+1)
this.html.appendChild(newEl)
}
this.els = this.html.querySelectorAll(".each")
}
calculate(){
this.eCor = this.els[0].getClientRects()[0]
this.mag = this.eCor.width
this.per = Math.PI / (this.els.length / 2)
this.deg = this.rtod(this.per)
}
render(index, rotateDiff){
this.els.forEach((each,i)=>{
let rad = (this.per * i) + this.radOffset,
x = Math.sin(rad)*this.mag,
y = Math.cos(rad)*this.mag
if(i+1 == index){each.classList.add("active")}
else{each.classList.remove("active")}
each.style.transform = `translate3d(${x}px,0%,${y}px)`
each.style.transform += `rotateY(${this.rtod(rad)}deg)`
})
}
rtod(radians){
return Math.round(radians * (180/Math.PI));
}
}
const s = new slider(document.body)
.each{
height: 250px;
width: 250px;
background: skyblue;
opacity: 0.8;
transform-origin: 50% 50%;
transform-style: preserve-3d;
user-select: none;
filter: brightness(80%);
transition: all 1s cubic-bezier(0, 1.22, 0.53, 1.02);
/* box-shadow: 0px 0px 10px 1px black; */
}
.each:nth-child(odd){background:lightsalmon}
.root{
height: 280px;
width: 280px;
position: relative;
margin-top: 10%;
perspective: 1000px;
transform-style: preserve-3d;
transition: transform 0.5s;
/* animation: loop 4s infinite linear; */
}
.each:hover{
z-index: 1000 !important;
filter:brightness(110%);
}
.active{
opacity: 1 !important;
filter:brightness(100%) !important;
}
<link href="https://unpkg.com/tailwindcss#^2/dist/tailwind.min.css" rel="stylesheet">

How to limit the number of total animated drop cubes and make them animate by their added order?

When we click this add button we will add a new cube which will drop immediately, but if we click it very quickly, there will be too many cubes dropping. I want to limit the number of the total animated dropping cube.
For example, even if we click the button very quickly and 10 cubes are added to the page, only two cubes are dropping, and others must wait until they finished.
For example, the 3rd and 4th cubes will start to drop when cube 1 and cube 2 finished.
I was thinking maybe we can have a global variable of the total animated cubes count, ++ and -- it when a new cube is starting and finishing animation, use setInterval to check if the variable is less than 2, but this can't make the animation based on the cubes created order. May I know how to solve this? Thank you so much!
var btn = document.getElementById('add');
var container = document.getElementById('cubes-container');
var cubeId = 0;
btn.addEventListener('click', addCube);
var currentAnimateCount = 0;
function dropCube(cube) {
var pos = 0;
let intervalId = setInterval(function() {
if (pos == 200) {
clearInterval(intervalId);
} else {
pos++;
cube.style.top = pos + "px";
}
}, 1);
}
function addCube() {
let cube = document.createElement('div');
let cubeContainer = document.createElement('div');
cube.className = 'cube';
cube.id = cubeId;
cube.innerHTML = cube.id;
cubeId++;
cubeContainer.className = 'cube-container';
cubeContainer.append(cube);
container.append(cubeContainer);
let pos = 0;
dropCube(cube)
}
#cubes-container {
display: grid;
grid-template-columns: repeat(10, 1fr);
}
.cube-container {
position: relative;
height: 30px;
}
.cube {
width: 30px;
height: 30px;
background-color: orange;
text-align: center;
display: grid;
align-items: center;
justify-items: center;
position: absolute;
}
<button id="add">add new</button>
<div id="cubes-container">
</div>
Consider the following example. It is using jQuery versus JavaScript. It could be similarly scripted.
$(function() {
function addCube(target) {
var i = $(".cube").length;
var container = $("<div>", {
class: "cube-container"
}).appendTo(target);
$("<div>", {
class: "cube",
id: "cube-" + i
}).html(i).appendTo(container);
return container;
}
function dropCube(cube) {
$(cube).addClass("falling").animate({
top: "200px"
}, 2000, function() {
$(cube).removeClass("falling");
if (cubes.length && $(".falling").length < 2) {
dropCube(cubes.shift());
}
});
}
var cubes = [];
$("#add").click(function() {
cubes.push(addCube("#cubes-container"));
if ($(".falling").length < 2) {
dropCube(cubes.shift());
}
});
});
/*
var btn = document.getElementById('add');
var container = document.getElementById('cubes-container');
var cubeId = 0;
btn.addEventListener('click', addCube);
var currentAnimateCount = 0;
function dropCube(cube) {
var pos = 0;
let intervalId = setInterval(function() {
if (pos == 200) {
clearInterval(intervalId);
} else {
pos++;
cube.style.top = pos + "px";
}
}, 1);
}
function addCube() {
let cube = document.createElement('div');
let cubeContainer = document.createElement('div');
cube.className = 'cube';
cube.id = cubeId;
cube.innerHTML = cube.id;
cubeId++;
cubeContainer.className = 'cube-container';
cubeContainer.append(cube);
container.append(cubeContainer);
let pos = 0;
dropCube(cube)
}
*/
#cubes-container {
display: grid;
grid-template-columns: repeat(10, 1fr);
}
.cube-container {
position: relative;
height: 30px;
}
.cube {
width: 30px;
height: 30px;
background-color: orange;
text-align: center;
display: grid;
align-items: center;
justify-items: center;
position: absolute;
top: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="add">add new</button>
<div id="cubes-container"></div>
Using an Array, you cna .shift() the first item of the array. Also adding a Class to the falling items allows you to keep a count of which ones are falling. I like to use Animate since it allows for a callback when the animation is done, so you can check for the next item.
Update for JavaScript
var btn = document.getElementById('add');
var container = document.getElementById('cubes-container');
var cubes = [];
btn.addEventListener('click', function(event) {
cubes.push(addCube());
if (document.getElementsByClassName("falling").length < 2) {
dropCube(cubes.shift());
}
});
function dropCube(cube) {
var pos = 0;
cube.classList.add("falling");
var intervalId = setInterval(function() {
if (pos == 200) {
clearInterval(intervalId);
cube.classList.remove("falling");
if (document.getElementsByClassName("falling").length < 2 && cubes.length) {
dropCube(cubes.shift());
}
} else {
pos++;
cube.style.top = pos + "px";
}
}, 1);
}
function addCube() {
let cube = document.createElement('div');
let cubeContainer = document.createElement('div');
cube.className = 'cube';
cube.id = document.getElementsByClassName("cube").length;
cube.innerHTML = cube.id;
cubeContainer.className = 'cube-container';
cubeContainer.append(cube);
container.append(cubeContainer);
return cubeContainer;
}
#cubes-container {
display: grid;
grid-template-columns: repeat(10, 1fr);
}
.cube-container {
position: relative;
height: 30px;
}
.cube {
width: 30px;
height: 30px;
background-color: orange;
text-align: center;
display: grid;
align-items: center;
justify-items: center;
position: absolute;
top: 0;
}
<button id="add">add new</button>
<div id="cubes-container"></div>

Categories