How to generate a random number each time a component re-renders? - css

I want to use Less variables to generate a random number for conditionally class that gets added and removed each second. Here is the sample code of reactjs file
function App() {
const [date , setDate] = useState('')
const [circleBoolean, setCircleBoolean] = useState(false);
const [random_number, setRandom_number] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
doThings()
}, 1000);
return () => clearInterval(interval);
}, []);
function doThings() {
setCircleBoolean((prev)=>{
return !prev});
setDate(moment().format('MMMM Do YYYY, h:mm:ss a')) ;
setRandom_number(Math.random() * 100);
console.log("cirlce boolean :" + circleBoolean)
}
return (
<div className="App">
<div className="bg">
<div style={{ top:`${random_number}%`, left:`${random_number}%` }} className={circleBoolean ? "circle" : ""} />
<div className="card">
<p className="card-info">{date}</p>
</div>
</div>
</div>
);
}
and here is the sample code from css file
#color: blue;
#random: (round(`Math.random()`)) ;
.circle {
position: absolute;
border: solid 4px #color;
border-radius: 50%;
height: #random;
width: 3em;
background-color: #color;
animation: circleSize 1s ease-in-out;
}
#keyframes circleSize {
0% {
transform: scale(0, 0);
}
50% {
transform: scale(1,1);
}
100%{
transform: scale(0,0);
}
}
The random number is generated only during the first render and is not changed during subsequent re-renders. How do I go about it ?

My advice is use a 'css-in-js' approach.
React only re-renders components that changed and your css is static right now. If you define your styles in a js file and pass your random number to this file every time your App's interval runs - you'll get your re-renders.

Related

Radial animated focus effect with mask-image in React TS

I am recreating this Radial animated focus effect with mask-image: Codepen I know I can just copy&paste the CSS into a .css file but I want to achieve the same result with a styled component. For that, I declared the CSS in my styled component and apply it. But I am not sure why nothing happens at all and what should I use instead of getElementById as manual DOM manipulation is bad practice?
App.tsx
import React from "react";
import styled from "styled-components";
const Property = styled.div`
#property --focal-size {
syntax: "<length-percentage>";
initial-value: 100%;
inherits: false;
}
`;
const FocusZoom = styled.div`
--mouse-x: center;
--mouse-y: center;
--backdrop-color: hsl(200 50% 0% / 50%); /* can't be opaque */
--backdrop-blur-strength: 10px;
position: fixed;
touch-action: none;
inset: 0;
background-color: var(--backdrop-color);
backdrop-filter: blur(var(--backdrop-blur-strength));
mask-image: radial-gradient(
circle at var(--mouse-x) var(--mouse-y),
transparent var(--focal-size),
black 0%
);
transition: --focal-size .3s ease;
/* debug/grok the gradient mask image here */
/* background-image: radial-gradient(
circle,
transparent 100px,
black 0%
); */
}
`;
function App(bool: boolean) {
const zoom: Element = document.querySelector("focus-zoom");
const toggleSpotlight = (bool) =>
zoom.style.setProperty("--focal-size", bool ? "15vmax" : "100%");
window.addEventListener("pointermove", (e) => {
zoom.style.setProperty("--mouse-x", e.clientX + "px");
zoom.style.setProperty("--mouse-y", e.clientY + "px");
});
window.addEventListener("keydown", (e) => toggleSpotlight(e.altKey));
window.addEventListener("keyup", (e) => toggleSpotlight(e.altKey));
window.addEventListener("touchstart", (e) => toggleSpotlight(true));
window.addEventListener("touchend", (e) => toggleSpotlight(false));
return (
<>
<h1>
Press <kbd>Opt/Alt</kbd> or touch for a spotlight effect
</h1>
<FocusZoom></FocusZoom>
</>
);
}
export default App;
Check out solution with styled components
Code sandbox
import React, { useEffect } from "react";
import styled, { createGlobalStyle } from "styled-components";
export const GlobalStyle = createGlobalStyle`
body {
display: flex;
align-items: center;
justify-content: center;
}
/* custom properties */
:root {
--focal-size: {
syntax: "<length-percentage>";
initial-value: 100%;
inherits: false;
}
--mouse-x: center;
--mouse-y: center;
--backdrop-color: hsl(200 50% 0% / 50%);
--backdrop-blur-strength: 10px;
}
`;
const Wrapper = styled.div`
height: 400px;
width: 400px;
background: conic-gradient(
from -0.5turn at bottom right,
deeppink,
cyan,
rebeccapurple
);
`;
const FocusZoom = styled.div`
position: fixed;
touch-action: none;
inset: 0;
background-color: var(--backdrop-color);
backdrop-filter: blur(var(--backdrop-blur-strength));
mask-image: radial-gradient(
circle at var(--mouse-x) var(--mouse-y),
transparent var(--focal-size),
black 0%
);
transition: --focal-size 0.3s ease;
`;
function App(bool) {
useEffect(() => {
const zoom = document.getElementById("zoomId");
const toggleSpotlight = (bool) =>
zoom.style.setProperty("--focal-size", bool ? "15vmax" : "100%");
window.addEventListener("pointermove", (e) => {
zoom.style.setProperty("--mouse-x", e.clientX + "px");
zoom.style.setProperty("--mouse-y", e.clientY + "px");
});
window.addEventListener("keydown", (e) => toggleSpotlight(e.altKey));
window.addEventListener("keyup", (e) => toggleSpotlight(e.altKey));
window.addEventListener("touchstart", (e) => toggleSpotlight(true));
window.addEventListener("touchend", (e) => toggleSpotlight(false));
toggleSpotlight();
}, []);
return (
<Wrapper>
<h1>
Press <kbd>Opt/Alt</kbd> or touch for a spotlight effect
</h1>
<FocusZoom id="zoomId"></FocusZoom>
</Wrapper>
);
}
export default App;
Also, ensure you have global styles & component imported in app file.
import Test, { GlobalStyle } from "./test";
export default function App() {
return (
<div className="App">
<GlobalStyle />
<Test />
</div>
);
}
As mentioned by others, we can simply refer to a DOM element in the React component template by using a useRef hook:
function App() {
// Get an imperative reference to a DOM element
const zoomRef = useRef<HTMLDivElement>(null);
const toggleSpotlight = (bool: boolean) =>
// To get the DOM element, use the .current property of the ref
zoomRef.current?.style.setProperty(
"--focal-size",
bool ? "15vmax" : "100%"
);
// Etc. including event listeners
return (
<>
<h1>
Press <kbd>Opt/Alt</kbd> or touch for a spotlight effect
</h1>
<FocusZoom ref={zoomRef} /> {/* Pass the reference to the special ref prop */}
</>
);
}
Demo: https://codesandbox.io/s/exciting-flower-349b48?file=/src/App.tsx
A more intensive solution could leverage styled-components props adaptation to replace the calls to zoom.style.setProperty(), as described in Jumping Text in React with styled component
In particular, this can help replace the use of CSS variables.
Except for --focal-size unfortunately, which is configured with a transition.
const FocusZoom = styled.div<{
focalSize: string; // Specify the extra styling props for adaptation
pointerPos: { x: string; y: string };
}>`
--focal-size: ${(props) => props.focalSize};
position: fixed;
touch-action: none;
inset: 0;
background-color: hsl(200 50% 0% / 50%);
backdrop-filter: blur(10px);
mask-image: radial-gradient(
circle at ${(props) => props.pointerPos.x + " " + props.pointerPos.y},
transparent var(--focal-size),
black 0%
);
transition: --focal-size 0.3s ease;
`;
function App() {
// Store all dynamic values into state
const [focalSize, setFocalSize] = useState("100%");
const [pointerPosition, setPointerPosition] = useState({
x: "center",
y: "center"
});
const toggleSpotlight = (bool: boolean) =>
// Change the state instead of messing directly with the DOM element
setFocalSize(bool ? "15vmax" : "100%");
// Etc. including event listeners
return (
<>
<h1>
Press <kbd>Opt/Alt</kbd> or touch for a spotlight effect
</h1>
{/* Pass the states to the styled component */}
<FocusZoom focalSize={focalSize} pointerPos={pointerPosition} />
</>
);
}
Demo: https://codesandbox.io/s/frosty-swirles-jdbcte?file=/src/App.tsx
This solution might be overkill for such case where the values change all the time (especially the mouse position), but it decouples the logic from the style implementation (the component does not know whether CSS variables are used or not).
Side note: for the event listeners, make sure to attach them only once (typically with a useEffect(cb, []) with an empty dependency array), and to remove them when the component is unmounted (typically by returning a clean up function from the useEffect callback).
You could also use useEvent from react-use for example, which hendles all that directly:
React sensor hook that subscribes a handler to events.
import { useEvent } from "react-use";
function App() {
// Attaches to window and takes care of removing on unmount
useEvent("pointermove", (e: PointerEvent) =>
setPointerPosition({ x: e.clientX + "px", y: e.clientY + "px" })
);
// Etc.
}
Instead of getElementById you should use the useRef hook

How to make dropdown animation?

I am implementing drop-down list using styled-component in react. In the process, I have two questions.
First, when dropDownVisible changes from true to false, why doesn't the animation effect apply and it disappears immediately? How can I improve the animation effect? Like when this list goes down, I want to make it gradually when it goes up.
Second, when StyledDropdown is dropped down, I want it to drop down behind the StyledHead, so I set the z-index property like that. I want the StyledHead to be always on top, so I'm curious why the StyledHead is hidden as the StyledDropdown drops down, even though I gave the z-index property bigger.
The source code is roughly structured like this:
// AApage.jsx
import { useEffect, useState, useRef } from 'react';
import { MdArrowDropDown, MdArrowDropUp } from 'react-icons/md';
import styled, { keyframes } from 'styled-components';
const dropAnimation = keyframes`
0% {
transform : translateY(-300px);
display : none;
}
100% {
transform : translateY(0);
}
`;
const StyledHead = styled.div`
width: 100px;
height: 100px;
background-color: red;
z-index: 11;
`;
const StyledDropdown = styled.div`
width: 100px;
height: 300px;
background-color: #d9d9d9;
border-radius: 0px 0px 10px 10px;
z-index: 3;
animation: ${dropAnimation} 1s alternate;
`;
const AApage = () => {
const [dropDownVisible, setDropDownVisible] = useState<boolean>(false);
const toggleDropDownVisible = () => {
setDropDownVisible((prev) => !prev);
};
return (
<>
<StyledHead>
<div>Dropdown</div>
<span>{`${dropDownVisible}`}</span>
{dropDownVisible ? (
<MdArrowDropUp
onClick={() => {
toggleDropDownVisible();
}}
></MdArrowDropUp>
) : (
<MdArrowDropDown
onClick={() => {
toggleDropDownVisible();
}}
></MdArrowDropDown>
)}
</StyledHead>
{dropDownVisible ? (
<StyledDropdown>
<div>temp data</div>
<div>temp data</div>
<div>temp data</div>
</StyledDropdown>
) : (
<></>
)}
</>
);
};
export default AApage;

Change state in function component react

I am new to React and being held back by a seemingly simple task.
I've got a Header component nested within which is a HamburgerButton component. Clicking the latter should make a sidenav appear but for now I would like the icon to change from the 'hamburger' to the big 'X'.
Here is my parent component:
import { MyMoviesLogo } from 'components/Icons';
import HamburgerButton from 'components/HamburgerButton/HamburgerButton';
import styles from './Header.module.css';
const Header = (): JSX.Element => {
const [isActive, setIsActive] = useState(false);
return (
<header className={styles.header}>
<MyMoviesLogo className={styles.headerIcon} />
<HamburgerButton
isActive={false}
/>
</header>
);
};
export default Header;
And here is the HamburgerButton
import styles from './HamburgerButton.module.css';
type HamburgerButtonProps = {
isActive: boolean;
onClick?: () => void;
};
const addMultipleClassNames = (classNames: string[]): string => classNames.join(' ');
const HamburgerButton = ({ isActive, onClick }: HamburgerButtonProps): JSX.Element => {
return (
<div className={isActive ? addMultipleClassNames([styles.hamburger, styles.active]) : styles.hamburger} onClick={onClick}>
<div className={styles.bar}></div>
<div className={styles.bar}></div>
<div className={styles.bar}></div>
</div>
);
}
export default HamburgerButton;
Here's my HamburgerButton.module.css file:
.hamburger {
cursor: pointer;
display: block;
width: 25px;
}
.bar {
background-color: var(--hamburger-button-global);
display: block;
height: 3px;
margin: 5px auto;
transition: all 0.3s ease-in-out;
width: 25px;
}
.hamburger.active .bar:nth-child(2) {
opacity: 0;
}
.hamburger.active .bar:nth-child(1) {
transform: translateY(8px) rotate(45deg);
}
.hamburger.active .bar:nth-child(3) {
transform: translateY(-8px) rotate(-45deg);
}
Manually changing the isActive prop to false verifies that the styling is applied as required.
My question is, how could I make it so when I click the icon its state gets toggled? I am familiar with React hooks like useState but can't quite put something together.
Any help would be greatly appreciated.
Thank you.
P.S.: It's probably obvious but I am using TypeScript.
You should use your onClick prop from your <HamburgerButton /> to change the parent state.
<HamburgerButton isActive={isActive} onClick={() => { setIsActive(oldState => !oldState) } />

CSS/Vue.js synchronize animation reset with ref value change

I want to create an animation that fades between 3 states. To store these states (that contain an image and some text), I've created an array of objects (uploadSteps). I increment the index of this array at a specific interval to create an animation.
My problem comes with CSS, I would like to create a fade-out/fade-in effect between each step, so that the transition is more smooth. It works most of the time but sometimes, after a refresh and for the first transition, I think the CSS animation is restarted before the state is changed. I wasn't able to reproduce it constantly so far.
What it does => Image fade-in -> Image 1 -> Image 1 fade-Out -> Image 1 -> Image 2 fade-in
What I want => Image fade-in -> Image 1 -> Image 1 fade-Out -> Image 2 fade-in
If you pay attention you can see that there's kind of a blink effect at the transition between image 1 and 2. What could I do to solve this ? Help appreciated !
PS: I've included some videos to illustrate my issue
https://streamable.com/4aigjq - Animation KO
https://streamable.com/umu3nj - Animation OK
<template>
<div class="screenshot-upload-processing-container">
<div class="image-container">
<img
ref="imageRef"
:src="
currentStep ? $requireImage(currentStep.imagePath).toString() : ''
"
:alt="currentStep.text"
class="image"
/>
</div>
<div class="centered-row">
<span ref="textRef" class="text">{{
currentStep ? currentStep.text : ''
}}</span>
</div>
</div>
</template>
<script lang="ts">
import {
computed,
defineComponent,
onBeforeUnmount,
ref,
} from '#nuxtjs/composition-api';
export default defineComponent({
name: 'ScreenshotUploadProcessing',
setup() {
const stepNumber = ref(0);
const timer = ref<NodeJS.Timeout | null>(null);
const imageRef = ref<HTMLImageElement | null>(null);
const textRef = ref<HTMLSpanElement | null>(null);
const uploadSteps = computed(() => {
return [
{
imagePath: 'image1.svg',
text: 'Text 1',
},
{
imagePath: 'image2.svg',
text: 'Text 2',
},
{
imagePath: 'image3.svg',
text: 'Text 3',
},
];
});
const resetAnimations = () => {
const image = imageRef.value;
const text = textRef.value;
if (image) {
image.style.animation = 'none';
void image.offsetHeight;
image.style.animation = '';
}
if (text) {
text.style.animation = 'none';
void text.offsetHeight;
text.style.animation = '';
}
};
const updateStepNumber = () => {
timer.value = setInterval(() => {
if (stepNumber.value === uploadSteps.value.length - 1) {
stepNumber.value = 0;
} else {
stepNumber.value++;
}
resetAnimations();
}, 3000);
};
onBeforeUnmount(() => {
clearInterval(timer.value as NodeJS.Timeout);
});
const currentStep = computed(() => uploadSteps.value[stepNumber.value]);
updateStepNumber();
return {
stepNumber,
currentStep,
imageRef,
textRef,
uploadSteps,
};
},
});
</script>
<style lang="scss" scoped>
#keyframes fade {
0%,
100% {
opacity: 0;
}
40%,
60% {
opacity: 1;
}
}
.screenshot-upload-processing-container {
display: flex;
flex-direction: column;
height: 100%;
}
.image {
animation: fade 3s linear;
}
.image-container {
display: flex;
align-items: center;
justify-content: center;
height: 27rem;
margin-top: 10.3rem;
}
.centered-row {
display: flex;
align-items: center;
justify-content: center;
margin-top: $spacing-xl;
}
.text {
#extend %text-gradient-1;
animation: fade 3s linear;
font-weight: $font-weight-medium;
}
</style>

CSS animation-direction not working as I would have expected

N.B. With the (more complex) setup I'm actually working with I can't use CSS Transitions. I recognise that CSS Transitions would be a
perfectly good solution in the example below.
I'm having a little trouble with
animation-direction: reverse
which I've never used before but doesn't seem to be running the way I might have expected it to.
The easiest solution to my problem would be to write two CSS #keyframes animations and use one or the other.
But for the sake of economy and elegance I would like to use a single animation and play it forwards or backwards.
This example below shows the effect I'm trying to achieve.
When the page loads, pressing either button will fire the intended animation.
However, after one button is pushed, the animation no longer runs and only the end-frame of the forwards or reverse animation is displayed.
What am I doing wrong here?
Working Example:
const square = document.querySelector('.square');
const buttonOutbound = document.querySelector('button.outboundButton');
const buttonReturn = document.querySelector('button.returnButton');
buttonOutbound.addEventListener('click', () => {
square.className = 'square';
square.classList.add('outbound');
}, false);
buttonReturn.addEventListener('click', () => {
square.className = 'square';
square.classList.add('return');
}, false);
.square {
width: 100px;
height: 100px;
margin: 12px;
background-color: red;
transform: translateX(0) scale(1);
}
.square.outbound {
animation: animateSquare 1s linear normal forwards;
}
.square.return {
animation: animateSquare 1s linear reverse forwards;
}
#keyframes animateSquare {
100% {
background-color: blue;
transform: translateX(200px) scale(0.5);
}
}
<div class="square"></div>
<button type="button" class="outboundButton">Outbound animation</button>
<button type="button" class="returnButton">Return animation</button>
The browser considers that animation as complete therefore it does not restart it in order to restart the animation you need to first remove the class and then re-add it however for the browser to recognize this change you need add a slight delay. Adding a setTimeout does the trick even if the timeout is 0 because js is single threaded.
const square = document.querySelector('.square');
const buttonOutbound = document.querySelector('button.outboundButton');
const buttonReturn = document.querySelector('button.returnButton');
buttonOutbound.addEventListener('click', () => {
square.classList.remove('return');
square.classList.remove('outbound');
setTimeout(() => {
square.classList.add('outbound');
}, 0)
}, false);
buttonReturn.addEventListener('click', () => {
square.classList.remove('return');
square.classList.remove('outbound');
setTimeout(() => {
square.classList.add('return');
}, 0)
}, false);
.square {
width: 100px;
height: 100px;
margin: 12px;
background-color: red;
transform: translateX(0) scale(1);
}
.square.outbound {
animation: animateSquare 1s linear normal forwards;
}
.square.return {
animation: animateSquare 1s linear reverse forwards;
}
#keyframes animateSquare {
100% {
background-color: blue;
transform: translateX(200px) scale(0.5);
}
}
<div class="square"></div>
<button type="button" class="outboundButton">Outbound animation</button>
<button type="button" class="returnButton">Return animation</button>
I had a think about this away from my laptop screen and realised that... what was missing from my original set up was one additional static class, describing the presentational state of .square after the .outbound animation has run:
.square.outbounded {
background-color: blue;
transform: translateX(200px) scale(0.5);
}
(N.B. There's no need for a corresponding static class, describing the state of .square after the .return animation has run, since the presentational state that class would describe is already described in the initial styles of .square)
Working Example (with .outbounded class added)
const square = document.querySelector('.square');
const buttonOutbound = document.querySelector('button.outboundButton');
const buttonReturn = document.querySelector('button.returnButton');
buttonOutbound.addEventListener('click', () => {
square.className = 'square outbound';
setTimeout(() => {
square.classList.add('outbounded');
square.classList.remove('outbound');
}, 1000);
}, false);
buttonReturn.addEventListener('click', () => {
square.className = 'square return';
setTimeout(() => {
square.classList.remove('return');
}, 1000);
}, false);
.square {
width: 100px;
height: 100px;
margin: 12px;
background-color: red;
transform: translateX(0) scale(1);
}
.square.outbounded {
background-color: blue;
transform: translateX(200px) scale(0.5);
}
.square.outbound {
animation: animateSquare 1s linear normal forwards;
}
.square.return {
animation: animateSquare 1s linear reverse forwards;
}
#keyframes animateSquare {
100% {
background-color: blue;
transform: translateX(200px) scale(0.5);
}
}
<div class="square"></div>
<button type="button" class="outboundButton">Outbound animation</button>
<button type="button" class="returnButton">Return animation</button>

Resources