animation with react and useRef hook? is there another way? - javascript

I want to make smth like clock with react.I hope to do it with help of ref, but it doesn't work sometimes. Is there another solution of this task, or what i do wrong?
import React, { useRef } from 'react';
import classes from "./Synchroniser.module.css";
const Synchroniser = () => {
const point:any = useRef(null);
var k = 0;
if (point.current !== null) {
setInterval(()=>{
if(k<360){
k++;
console.log(k);
}else{k=0}
point.current.style.transform = `rotate(${k}deg)`;
},100);
}else{
console.log(0)
}
return(
<div>
<h1 ref={point}>RLY?</h1>
</div>
);
};
export default Synchroniser

Go to bottom for the pure CSS solution
React Solution
This solves your problem but the k variable adds twice as much as it needs to, I don't know why. For the k variable to persist between states and not reset the clock every rerender you have to use it with useRef too.
const point = useRef(null);
const k = useRef(0);
useEffect(() => {
if (point.current !== null) {
setInterval(() => {
if (k.current < 360) {
k.current = k.current + 1;
} else {
k.current = 0;
}
point.current.style.transform = `rotate(${k.current}deg)`;
}, 1000);
} else {
console.log("0");
}
},[]);
CSS Solution CodeSandbox Link
JSX
<div className="clock">
<div className="spin"></div>
</div>
CSS
.clock {
height: 250px;
width: 250px;
border-radius: 9999px;
display: flex;
justify-content: center;
align-items: center;
background: #eeeeee;
}
.spin {
width: 90%;
height: 2px;
position: relative;
background: white;
-webkit-animation:spin 60s linear infinite;
-moz-animation:spin 60s linear infinite;
animation:spin 60s linear infinite;
}
.spin::after,
.spin::before {
height: 2px;
content: " ";
position: absolute;
top: 0;
width: 50%;
}
.spin::after {
right: 0;
background-color: black;
}
.spin::before {
left: 0;
background-color: #eeeeee;
}
#-moz-keyframes spin {
100% {
-moz-transform: rotate(360deg);
}
}
#-webkit-keyframes spin {
100% {
-webkit-transform: rotate(360deg);
}
}
#keyframes spin {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}

Related

Why the onAnimationEnd does not trigger when switching the focus to other tab of the browser?

I am trying to build a split-flap.
Here is my code:
let baseDiv, lowerDiv, middleDiv, upperDiv;
let intervalId;
document.addEventListener("DOMContentLoaded", () => {
baseDiv = document.getElementById("base");
lowerDiv = document.getElementById("lower");
middleDiv = document.getElementById("middle");
upperDiv = document.getElementById("upper");
});
let backward = () => {
middleDiv.innerHTML = baseDiv.innerHTML;
lowerDiv.classList.add("rotate0to90");
middleDiv.className = "upperHalfCard-after transform0to_90 zIndex4";
}
let forward = () => {
middleDiv.innerHTML = baseDiv.innerHTML;
upperDiv.classList.add("rotate0to_90");
middleDiv.className = "lowerHalfCard-after transform0to90 zIndex4";
}
let handler = obj => {
console.log(obj.id);
switch (obj.id) {
case "lower":
lowerDiv.classList.replace("zIndex4", "zIndex2");
middleDiv.classList.add("rotate_90to0");
break;
case "middle":
upperDiv.innerHTML = baseDiv.innerHTML;
lowerDiv.innerHTML = baseDiv.innerHTML;
middleDiv.className = "hide";
upperDiv.className = "upperHalfCard-after zIndex4";
lowerDiv.className = "lowerHalfCard-after zIndex2";
break;
case "upper":
middleDiv.classList.add("rotate90to0");
upperDiv.classList.replace("zIndex4", "zIndex2");
break;
default:
break;
}
}
let start = () => {
intervalId = setInterval(() => {
console.log("Kicked by interval");
forward();
}, 3000);
}
let stop = () => {
clearInterval(intervalId);
}
.fullCard,
.lowerHalfCard,
.upperHalfCard,
.fullCard-after,
.lowerHalfCard-after,
.upperHalfCard-after {
background-color: inherit;
border-radius: 10px;
height: 100%;
width: 100%;
position: absolute;
align-items: inherit;
display: inherit;
justify-content: inherit;
}
.fullCard-after::after,
.upperHalfCard-after::after {
content: "";
display: block;
position: absolute;
height: 4px;
background-color: inherit;
width: 100%;
top: calc(50% - 2px);
}
.lowerHalfCard-after::after {
content: "";
display: block;
position: absolute;
height: 4px;
background-color: inherit;
width: 100%;
top: calc(50% - 2px);
}
.lowerHalfCard,
.lowerHalfCard-after {
clip-path: polygon(0% 50%, 100% 50%, 100% 100%, 0% 100%);
}
.upperHalfCard,
.upperHalfCard-after {
clip-path: polygon(0% 0%, 100% 0%, 100% 50%, 0% 50%);
}
.splitFlap {
background-color: black;
box-sizing: border-box;
border-radius: 10px;
color: white;
font-weight: bold;
font-family: arial;
font-size: 5.5em;
width: 100px;
height: 150px;
position: relative;
align-items: center;
display: flex;
justify-content: center;
transform-style: preserve-3d;
}
.rotate0to90 {
animation-name: r0to90;
}
.rotate90to0 {
animation-name: r90to0;
}
.rotate0to_90 {
animation-name: r0to_90;
}
.rotate_90to0 {
animation-name: r_90to0;
}
.rotate0to90,
.rotate90to0,
.rotate0to_90,
.rotate_90to0 {
animation-duration: 0.3s;
animation-fill-mode: forwards;
}
#keyframes r0to90 {
from {
transform: rotateX(0deg);
}
to {
transform: rotateX(90deg);
}
}
#keyframes r90to0 {
from {
transform: rotateX(90deg);
}
to {
transform: rotateX(0deg);
}
}
#keyframes r0to_90 {
from {
transform: rotateX(0deg);
}
to {
transform: rotateX(-90deg);
}
}
#keyframes r_90to0 {
from {
transform: rotateX(-90deg);
}
to {
transform: rotateX(0deg);
}
}
.transform0to_90 {
transform: rotateX(-90deg);
}
.transform0to90 {
transform: rotateX(90deg);
}
.hide {
display: none
}
.zIndex2 {
z-index: 2;
}
.zIndex4 {
z-index: 4;
}
<div class="splitFlap">
<div id="base" class="fullCard-after zIndex2">
2
</div>
<div class="upperHalfCard-after zIndex4" id="upper" onAnimationEnd="handler(this)">
1
</div>
<div id="middle" class="hide" onAnimationEnd="handler(this)">
</div>
<div class="lowerHalfCard-after zIndex2" id="lower" onAnimationEnd="handler(this)">
1
</div>
</div>
<p>
<button onClick="start()">
Start
</button>
<button onClick="stop()">
Stop
</button>
</p>
The code works fine where the tab is on focus.
And you can see the onAnimationEnd event handler and interval handler work properly(i.e. 1 interval event trigger 2 onAnimationEnd event.).
Unfortunately, when switching the browser focus to another tab for about 1 min, the onAnimationEnd event handler seems to be not stable(i.e. sometimes only an interval event is triggered, and no onAnimationEnd event is triggered, sometimes the onAnimationEnd event handler can be resume).
What's going on? how can I fix it?

Track when CSS keyframe animation reaches 50%

I have a simple beating animation and I want to check when one iteration ends or reaches 50% keyframe or something like that, is this possible?
For now this is what I have tried but this doesn't track anything:
function prepareLabelBeatStart() {
const prepareLabelGroup = document.getElementById("prepare-label-group");
prepareLabelGroup.classList.add("beaton");
prepareLabelGroup.addEventListener("webkitAnimationEnd", beatonEnd);
function beatonEnd(e) {
console.log('FUCK');
if (e.animationName === 'beaton') {
console.log('one iteration has been end'); // this is not working
prepareLabelGroup.removeEventListener("webkitAnimationEnd", beatonEnd);
}
}
}
setTimeout(() => prepareLabelBeatStart(), 2500);
.beaton {
animation: beaton 1.5s ease-in-out infinite both;
}
#keyframes beaton {
0% { transform: scale(1) }
50% { transform: scale(0.5) }
100% { transform: scale(1) }
}
#prepare-label-group {
position: absolute;
background: black;
width: 50px;
}
<div id="prepare-label-group">ff</div>
May be this will help.
function prepareLabelBeatStart() {
const prepareLabelGroup = document.getElementById("prepare-label-group");
let percent = document.getElementById("percent");
prepareLabelGroup.classList.add("beaton");
percent.textContent = 0+"%"
let total = 6; //6 seconds
let step = 1;
let Track = setInterval(function(){
percent.textContent = Math.round(((step++) / total) * 100) + "%";
}, 1000);
let Track2 = setInterval(function(){
if (prepareLabelGroup.classList.contains("beaton")) {
prepareLabelGroup.classList.remove("beaton");
}
clearInterval(Track);
clearInterval(Track2);
}, 6000);
}
prepareLabelBeatStart();
setInterval(function(){
prepareLabelBeatStart();
}, 6500);
.beaton {
animation: beaton 1.5s ease-in-out infinite both;
}
#keyframes beaton {
0% { transform: scale(1) }
50% { transform: scale(0.5) }
100% { transform: scale(1) }
}
#prepare-label-group {
position: absolute;
background: black;
width: 50px;
text-align:center;
}
#percent{
color:white;
}
<div id="prepare-label-group"><div id="percent">0%</div></div>

How do I stop css rotate back to face before expected animation?

I'm trying to simulate the animation of flips of a coin with JS & CSS.
I guess the keys are transform-style, backface-visibility, rotateY, animation-fill-mode and transform in CSS as well as Math.random in JS.
If the coin is the heads, everything is OK.
If the coin is tail, clicking the button will flip it to head and then start the expected flipping animation.
How do I make it start flipping animation directly from the tail?
const coin = document.querySelector('#coin');
const button = document.querySelector('#flip');
const status = document.querySelector('#status');
const heads = document.querySelector('#headsCount');
const tails = document.querySelector('#tailsCount');
let headsCount = 0;
let tailsCount = 0;
function deferFn(callback, ms) {
setTimeout(callback, ms);
}
function processResult(result) {
if (result === 'heads') {
headsCount++;
heads.innerText = headsCount;
} else {
tailsCount++;
tails.innerText = tailsCount;
}
status.innerText = result.toUpperCase();
}
function flipCoin() {
coin.setAttribute('class', '');
const random = Math.random();
const result = random < 0.5 ? 'heads' : 'tails';
deferFn(function() {
coin.setAttribute('class', 'animate-' + result);
deferFn(processResult.bind(null, result), 2900);
}, 100);
}
button.addEventListener('click', flipCoin);
h2 {
margin: .25rem;
}
div.container {
margin: auto;
display: flex;
flex-direction: column;
align-items: center;
}
button {
padding: 1rem;
background-color: skyblue;
}
#coin {
position: relative;
width: 15rem;
height: 15rem;
margin: 2rem 0rem;
transform-style: preserve-3d;
}
#coin div {
width: 100%;
height: 100%;
border: 2px solid black;
border-radius: 50%;
backface-visibility: hidden;
background-size: contain;
position: absolute;
}
.heads {
background-image: url("https://en.numista.com/catalogue/photos/inde/2311-original.jpg");
}
.animate-heads {
animation: flipHeads 3s;
animation-fill-mode: forwards;
}
#keyframes flipHeads {
from {
transform: rotateY(0deg);
}
to {
transform: rotateY(1800deg);
}
}
.tails {
background-image: url("https://en.numista.com/catalogue/photos/inde/3165-original.jpg");
transform: rotateY(-180deg);
}
.animate-tails {
animation: flipTails 3s;
animation-fill-mode: forwards;
}
#keyframes flipTails {
from {
transform: rotateY(0deg);
}
to {
transform: rotateY(1620deg);
}
}
<div class='container'>
<h2>Confused about your life decision? Just flip this coin!</h2>
<h2>Btw, don't forget to assign something to both sides.</h2>
<p>And don't take your life decision based on this stupid coin flip. I was kidding.</p>
<div id="coin" class=''>
<div id="heads" class="heads"></div>
<div id="tails" class="tails"></div>
</div>
<button id="flip">Flip this thing</button>
<p>Heads: <span id="headsCount">0</span> Tails: <span id="tailsCount">0</span></p>
<p><span id="status"></span></p>
</div>
You can use the css property:
animation-fill-mode: forwards;

ReactJS - Prevent initial animation of modal with react hooks

I built simple Modal component which will slide from bottom when opened. Animations are working fine when Modal trigger button clicked and backdrop clicked. But i am seeing slide-down animation at initial render of page. How can i prevent initial animation ?? I am specifically looking how to solve with react hooks.
Modal.js
import React, { useRef, useEffect } from 'react';
import { createPortal } from 'react-dom';
import './Modal.css';
const Modal = ({ isOpen, onClose, children }) => {
const modalEl = useRef(null);
const handleCoverClick = (e) => {
if (e.target.hasAttribute('modal')) {
onClose();
}
}
useEffect(() => {
const handleAnimationEnd = (event) => {
if (!isOpen) {
event.target.classList.remove('show');
event.target.classList.add('hide');
} else {
event.target.classList.remove('hide');
event.target.classList.add('show');
}
};
modalEl.current.addEventListener('animationend', handleAnimationEnd);
return () => modalEl.current.removeEventListener('animationend', handleAnimationEnd);
}, [isOpen]);
return createPortal(
<>
<div className={`ModalCover ${isOpen ? 'show' : 'hide'}`} onClick={handleCoverClick} modal="true"></div>
<div className={`ModalContainer ${isOpen ? 'slide-up' : 'slide-down'}`} ref={modalEl}>
{children}
</div>
</>,
document.body);
};
export default Modal;
Modal.css
.show {
display: block;
}
.hide {
display: none;
}
.slide-up {
transform: translateY(0%);
animation: slide-up 0.5s forwards;
}
.slide-down {
transform: translateY(100%);
animation: slide-down 0.5s forwards;
}
#keyframes slide-up {
0% { transform: translateY(100%); }
100% { transform: translateY(0%); }
}
#keyframes slide-down {
0% { transform: translateY(0%); }
100% { transform: translateY(100%); }
}
.ModalCover {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
background-color: rgba(0, 0, 0, 0.15);
}
.ModalContainer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 400px;
margin-top: calc(100vh - 400px);
z-index: 20;
}
demo (codesandbox) : https://codesandbox.io/s/l7x5p4k82m
Thanks!
A simpler way is to do this with classNames since direct DOM access is discouraged with DOM. modalEl.current ref is assigned after initial render, it can be used as a flag that a component was mounted:
<div className={`
ModalContainer
${isOpen ? 'slide-up' : 'slide-down'}
${!modalEl.current && 'hide'}
`} ref={modalEl}>
Applying hide class on component mount in useEffect may result in briefly shown modal animation.

CSS Transition Triggered by JavaScript

I want to have the following JavaScript function to transition function between from have none display to block when generate_loading_screen() is called to to when it finishes transition between display block to none. How do I do this?
function generate_loading_screen() {
window.setInterval(function(){
if (progress_percent < 75) {
document.getElementById("loading_screen").style.display = "block";
document.getElementById("body_of").style.filter = "grayscale(1)";
}
else {
document.getElementById("loading_screen").style.display = "none";
document.getElementById("body_of").style.filter = "none";
stop_generating_loading();
}
}, 50);
};
function stop_generating_loading() {
clearInterval(generate_loading_screen);
};
.loading {
position: fixed;
border: 16px solid #dbdbdb;
border-radius: 50%;
border-top: 16px solid #53f442;
margin-left: 44%;
margin-top: 10%;
width: 120px;
height: 120px;
animation: spin 2s linear infinite;
}
#keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
<div class="loading" id="loading_screen" style="display: none;"></div>
Just extra info: progress_percent is a variable that determines how much of the rest of the web-app has loaded. The grayscale filter does not affect the whole page, just the ID body_of
Thanks in advance
Probably better to use a opacity transition by adding a class when your percent reaches 100.
Codepen for working example or see below.
HTML:
<div class="loading" id="loading_screen"></div>
CSS:
.loading {
position: fixed;
border: 16px solid #dbdbdb;
border-radius: 50%;
border-top: 16px solid #53f442;
margin-left: 44%;
margin-top: 10%;
width: 120px;
height: 120px;
animation: spin 2s linear infinite;
opacity: 100%;
transition: opacity 1s ease-in-out;
}
.done_loading {
opacity: 0;
}
#keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
Javascript:
var progress_percent = 25;
var interval;
function generate_loading_screen() {
interval = window.setInterval(function(){
progress_percent += 1; //totest
if (progress_percent > 75) {
document.getElementById("loading_screen").className = "loading done_loading";
//stop_generating_loading();
}
//TESTING
if(progress_percent > 100){
console.log("Reached 100%");
document.getElementById("loading_screen").className = 'loading';
progress_percent = 0;
}
//
}, 50);
};
function stop_generating_loading() {
clearInterval(interval);
};
document.addEventListener('DOMContentLoaded', function(){
generate_loading_screen();
});
Remove all the testing code to get this to work once, you might need to add additional code for your body div. Let me know if you need me to add more to this example!
window.setInterval returns an intervalId which you need to cancel in order to stop the interval
let timer;
function generate_loading_screen() {
timer = window.setInterval(function(){
if (progress_percent < 75) {
document.getElementById("loading_screen").style.display = "block";
document.getElementById("body_of").style.filter = "grayscale(1)";
}
else {
document.getElementById("loading_screen").style.display = "none";
document.getElementById("body_of").style.filter = "none";
stop_generating_loading();
}
}, 50);
};
function stop_generating_loading() {
clearInterval(timer);
};

Categories