In my react app, there is a refresh icon. I am using material UI for the icon. I want that after clicking on it, it should spin for a second.
I have no idea how to do it.
I have tried this but it didn't work.
const useStyles = makeStyles((theme) => ({
refresh: {
marginTop: "20px",
cursor: "pointer",
margin: "auto",
animation: "spin 1s 1",
},
"#keyframes spin": {
"0%": {
transform: "translateY(-200%)",
},
"100%": {
transform: "translateY(0)",
},
},
}));
function Refresh() {
const [spin, setSpin] = React.useState(0);
const classes= useStyles();
const refreshCanvas = () => {
setSpin(1);
console.log("Refreshed");
};
return (
<AutorenewIcon
className={classes.refresh}
onClick={refreshCanvas}
onAnimationEnd={() => setSpin(0)}
spin={spin}
/>
)
}
Seprate the class of animaion let's assume it is refresh as of now and stored in classes.refresh.
You can do it by conditionally applying className to your element
function Refresh() {
const [spin, setSpin] = React.useState(0);
const classes= useStyles();
const refreshCanvas = () => {
setSpin(1);
console.log("Refreshed");
};
return (
<AutorenewIcon
className={spin === 1 ? classes.refresh : ''}
onClick={refreshCanvas}
onAnimationEnd={() => setSpin(0)}
spin={spin}
/>
)
}
set the spin true on click and then add a setTimeout for 1000ms, which will set that spin state again false. And then add a class conditionally based on the value of the spin state and add the animation to that class.
const useStyles = makeStyles((theme) => ({
refresh: {
marginTop: "20px",
cursor: "pointer",
margin: "auto",
"&.spin": {
animation: "$spin 1s 1",
// pointerEvents:'none'
}
},
"#keyframes spin": {
"0%": {
transform: "rotate(0deg)"
},
"100%": {
transform: "rotate(360deg)"
}
}
}));
function Refresh() {
const [spin, setSpin] = React.useState(false);
const classes = useStyles();
const refreshCanvas = () => {
setSpin(true);
setTimeout(() => {
setSpin(false);
}, 1000);
};
return (
<Autorenew
className={clsx({
[classes.refresh]: true,
spin: spin
})}
onClick={refreshCanvas}
spin={360}
/>
);
}
Optional Update: Also you can add pointerEvents:"none" in spin class to disable the click for that period of time until the animation is going which is 1000ms here.
Here is the working demo:
Got the answer... Thanx for both the answers I got here... I got the idea from those answers.
const useStyles = makeStyles((theme) => ({
refresh: {
margin: "auto",
},
spin: {
margin: "auto",
animation: "$spin 1s 1",
},
"#keyframes spin": {
"0%": {
transform: "rotate(0deg)",
},
"100%": {
transform: "rotate(360deg)",
},
}
}))
function Refresh() {
const [spin, setSpin] = React.useState(0);
const classes= useStyles();
const refreshCanvas = () => {
setSpin(true);
setTimeout(() => {
setSpin(false);
}, 1000);
console.log("Refreshed");
};
return (
<AutorenewIcon
className={spin ? classes.spin : classes.refresh}
onClick={refreshCanvas}
spin={spin}
/>
)
}
Related
Here is what I have for now:
import {
Alert,
Animated,
Easing,
Linking,
StyleSheet,
Text,
View,
} from "react-native";
import React, { useEffect, useState } from "react";
import * as Location from "expo-location";
import * as geolib from "geolib";
import { COLORS } from "../../assets/Colors/Colors";
export default function DateFinder() {
const [hasForegroundPermissions, setHasForegroundPermissions] =
useState(null);
const [userLocation, setUserLocation] = useState(null);
const [userHeading, setUserHeading] = useState(null);
const [angle, setAngle] = useState(0);
useEffect(() => {
const AccessLocation = async () => {
function appSettings() {
console.warn("Open settigs pressed");
if (Platform.OS === "ios") {
Linking.openURL("app-settings:");
} else RNAndroidOpenSettings.appDetailsSettings();
}
const appSettingsALert = () => {
Alert.alert(
"Allow Wassupp to Use your Location",
"Open your app settings to allow Wassupp to access your current position. Without it, you won't be able to use the love compass",
[
{
text: "Cancel",
onPress: () => console.warn("Cancel pressed"),
},
{ text: "Open settings", onPress: appSettings },
]
);
};
const foregroundPermissions =
await Location.requestForegroundPermissionsAsync();
if (
foregroundPermissions.canAskAgain == false ||
foregroundPermissions.status == "denied"
) {
appSettingsALert();
}
setHasForegroundPermissions(foregroundPermissions.status === "granted");
if (foregroundPermissions.status == "granted") {
const location = await Location.watchPositionAsync(
{
accuracy: Location.Accuracy.BestForNavigation,
activityType: Location.ActivityType.Fitness,
distanceInterval: 0,
},
(location) => {
setUserLocation(location);
}
);
const heading = await Location.watchHeadingAsync((heading) => {
setUserHeading(heading.trueHeading);
});
}
};
AccessLocation().catch(console.error);
}, []);
useEffect(() => {
if (userLocation != null) {
setAngle(getBearing() - userHeading);
rotateImage(angle);
}
}, [userLocation]);
const textPosition = JSON.stringify(userLocation);
const getBearing = () => {
const bearing = geolib.getGreatCircleBearing(
{
latitude: userLocation.coords.latitude,
longitude: userLocation.coords.longitude,
},
{
latitude: 45.47200370608976,
longitude: -73.86246549592089,
}
);
return bearing;
};
const rotation = new Animated.Value(0);
console.warn(angle);
const rotateImage = (angle) => {
Animated.timing(rotation, {
toValue: angle,
duration: 1000,
easing: Easing.bounce,
useNativeDriver: true,
}).start();
};
//console.warn(rotation);
return (
<View style={styles.background}>
<Text>{textPosition}</Text>
<Animated.Image
source={require("../../assets/Compass/Arrow_up.png")}
style={[styles.image, { transform: [{ rotate: `${angle}deg` }] }]}
/>
</View>
);
}
const styles = StyleSheet.create({
background: {
backgroundColor: COLORS.background_Pale,
flex: 1,
// justifyContent: "flex-start",
//alignItems: "center",
},
image: {
flex: 1,
// height: null,
// width: null,
//alignItems: "center",
},
scrollView: {
backgroundColor: COLORS.background_Pale,
},
});
I think that the math I'm doing must be wrong because the arrow is pointing random directions spinning like crazy and not going to the coordinate I gave it. Also, I can't seem to use the rotateImage function in a way that rotation would be animated and i'd be able to use it to animate the image/compass. If anyone could help me out i'd really appreciate it I've been stuck on this for literally weeks.
Edit
Currently, this is more or less how I realize this functinality, but this uses Javascript, it's not a CSS-only solution, which is what I'd like to have.
import * as React from "react";
import styled from "#emotion/styled";
import { css, keyframes } from "#emotion/react";
const Container = styled.div`
display: flex;
height: 100%;
transition: 0.3s ease-in-out;
`;
const translate = ({ transitionFrom, beginningOfRowPosition }) => keyframes`
from {
transform: translate(${transitionFrom.x}px, ${transitionFrom.y}px);
}
to {
transform: translate(${beginningOfRowPosition.x}px, ${beginningOfRowPosition.y}px);
}
`;
const Button = styled.button<{
beginningOfRowPosition?: { x: number; y: number };
transitionFrom?: { x: number; y: number };
}>`
${({ beginningOfRowPosition, transitionFrom }) =>
beginningOfRowPosition && transitionFrom
? css`
transform: translate(
${beginningOfRowPosition.x}px,
${beginningOfRowPosition.y}px
);
animation: ${translate({
transitionFrom,
beginningOfRowPosition
})}
3s ease-in-out;
`
: ""}
`;
export default function App() {
const buttonsRef = React.useRef<HTMLButtonElement[]>([]);
const paramsSet = React.useRef(false);
const [selectedButton, setSelectedButton] = React.useState<number | null>(
null
);
const [buttonWidth, setButtonWidth] = React.useState(0);
const [beginningOfRowPosition, setBeginningOfRowPosition] = React.useState({
x: 0,
y: 0
});
const [transitionFrom, setTransitionFrom] = React.useState({
x: 0,
y: 0
});
const handleSelectButton = React.useCallback(
(index) => () => {
setSelectedButton(index);
console.log(buttonsRef.current);
const button = buttonsRef.current[index];
setTransitionFrom({
x: button.getBoundingClientRect().x,
y: button.getBoundingClientRect().y
});
},
[]
);
const handleRef = React.useCallback(
(index) => (ref: HTMLButtonElement) => {
if (!ref) return;
buttonsRef.current.push(ref);
if (paramsSet.current) return;
if (index === 0) {
const buttonWidth =
Math.ceil(parseInt(getComputedStyle(ref).width.split("px")[0])) + 1;
paramsSet.current = true;
setButtonWidth(buttonWidth);
const { x, y } = ref.getBoundingClientRect();
setBeginningOfRowPosition({ x, y });
}
},
[]
);
const buttons = Array.from({ length: 5 });
return (
<Container style={{}}>
{selectedButton ? (
<Button
beginningOfRowPosition={beginningOfRowPosition}
transitionFrom={transitionFrom}
style={{ width: buttonWidth }}
>
button {selectedButton}
</Button>
) : (
buttons.map((button, index) => (
<Button ref={handleRef(index)} onClick={handleSelectButton(index)}>
button {index}
</Button>
))
)}
</Container>
);
}
Original question
I'm trying to figure out how to animate the position of a button element, making it translate to the beginning of the row once it is selected and other button element nodes are removed from the DOM.
Here's an example:
import * as React from "react";
export default function App() {
const [selectedButton, setSelectedButton] = React.useState<number | null>(
null
);
const [buttonWidth, setButtonWidth] = React.useState(0);
const handleSelectButton = React.useCallback(
(index) => () => {
setSelectedButton(index);
},
[]
);
const handleRef = React.useCallback(
(index) => (ref: HTMLButtonElement) => {
if (!ref) return;
if (index === 0) {
const buttonWidth = getComputedStyle(ref).width;
setButtonWidth(parseInt(buttonWidth.split("px")[0]));
}
},
[]
);
const buttons = Array.from({ length: 5 });
const buttonStyle = {
flexGrow: 1,
transition: "0.3s ease-in-out"
};
return (
<div
style={{
display: "flex",
height: "100%",
transition: "0.3s ease-in-out"
}}
>
{selectedButton ? (
<button style={{ ...buttonStyle, maxWidth: buttonWidth }}>
button {selectedButton}
</button>
) : (
buttons.map((button, index) => (
<button
ref={handleRef(index)}
className="button"
style={{ ...buttonStyle, width: "max-content" }}
onClick={handleSelectButton(index)}
>
button {index}
</button>
))
)}
</div>
);
}
I've tried playing around with most flex properties, because I'm aware nearly all of them are animatable, however once I select a button, it just jumps/renders to the beginning of the row, instead of transition from its current position to the beginning of the row.
The only other thing that I could think of is to click a specific button, get its position with getBoundingClientRect() and then transition the transform property to the beginning of the row.
What can I do here?
I want my component to have an animation that if my mouse enter,it will be fully displayed in 0.3s,and if my mouse leave,it will disappear in 0.1s.But useSpring can just define one duration just like the code below,which cause that the component will be displayed and disappear all in 0.3s.How can I define different duration for from->to and to->from?Thanks for anyone who can help.
const animationStyle = useSpring({
bottom: show ? 0 : -71,
from: {
bottom: -71
},
config: { duration: 300 }
})
Like this
import import React, { useState } from 'react';
import { animated, useSpring } from '#react-spring/web';
const SuspendedComponent: React.FC = ({ children }) => {
const [isMouseEnter, setMouseEnter] = useState<boolean>(false);
const _style = useSpring({
bottom: isMouseEnter ? 0 : -71,
from: {
bottom: -71,
},
config: { duration: 300 },
});
function onMouseHandler(isEnter: boolean) {
setMouseEnter(isEnter);
}
return (
<div
style={{ display: 'block', width: '100px', height: '100px' }}
onMouseEnter={() => onMouseHandler(true)}
onMouseLeave={() => onMouseHandler(false)}
>
{isMouseEnter && <animated.div style={_style}>{children}</animated.div>}
</div>
);
};
export default SuspendedComponent;
You can control animated.div element display by onMouseEnter and onMouseLeave event.
Every element of the array should be displayed for some time and the time for which each element is displayed should be determined by a value in each element.
let array=[{display:"a",time:10},{display:"b",time:15},{display:"c",time:22}]
class App extends React.Component{
state={stateDisplay:"",
stateTime:""
}
componentWillMount(){
var i=0;
let handle=setInterval(()=>{
var element= array[i]
this.setState({
stateDisplay:element.display,
stateTime:element.time,
})
i=i+1;
if(i===array.length){
clearInterval(handle)
}
},10000)
}
render(){
return(
<div> {this.state.stateDisplay} </div>
)}}
i have done something like this but using setinterval the delay can only be set for a constant time,here 10s.
I want the first element to display for 10s and then the next element for 15s, third for 22s which is the time value for each element of the array.
I know i cant do that using setinterval is there a way to do this using Settimeout?
This was almost like a little challenge, heres what i managed to come up with, its in typescript, if you need js, just remove interfaces and type annotations
/* eslint-disable #typescript-eslint/no-explicit-any */
/* eslint-disable prettier/prettier */
/* eslint-disable no-shadow */
/* eslint-disable no-console */
import React, { FC, useState, useEffect, useCallback } from 'react';
import { View, Button, Text } from 'react-native';
interface Data {
duration: number;
bgColor: string;
}
const dataArr: Data[] = [
{ duration: 3, bgColor: 'tomato' },
{ duration: 6, bgColor: 'skyblue' },
{ duration: 9, bgColor: 'gray' },
];
const Parent = () => {
const [currentIdx, setCurrentIdx] = useState<number>(0);
const [elementData, setElementData] = useState<Data>(dataArr[currentIdx]);
useEffect(() => {
console.log('idx', currentIdx);
if (currentIdx > dataArr.length) return;
setElementData({ ...dataArr[currentIdx] });
}, [currentIdx]);
const pushNext = () => {
setCurrentIdx(currentIdx + 1);
};
const handleRestart = () => {
setCurrentIdx(0);
setElementData({ ...dataArr[0] });
};
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Timer
data={elementData}
onCountDownComplete={pushNext}
restart={handleRestart}
/>
</View>
);
};
interface Props {
data: Data;
onCountDownComplete: () => void;
restart: () => void;
}
const Timer: FC<Props> = ({ data, onCountDownComplete, restart }) => {
const [seconds, setSeconds] = useState<number>(data.duration);
// update on data change
useEffect(() => {
setSeconds(data.duration);
}, [data]);
const callback = useCallback(() => {
onCountDownComplete();
}, [onCountDownComplete]);
useEffect(() => {
let interval: any = null;
if (seconds > -1) {
interval = setInterval(() => {
if (seconds - 1 === -1) {
callback();
} else {
setSeconds(seconds - 1);
}
}, 1000);
} else {
return;
}
return () => {
clearInterval(interval);
};
}, [seconds, callback]);
return (
<View
style={{ backgroundColor: data.bgColor, padding: 16, borderRadius: 10 }}
>
<Text style={{ marginBottom: 24 }}>{seconds}</Text>
<Button title="restart" onPress={restart} />
</View>
);
};
I have the following code, which should render a simple fade in animation for the Container component after 3 seconds. However, the component is flashing fully visible before fading in. My question is: why is this happening, and how can I stop it from happening?
import React, { useState, useEffect } from "react";
import { render } from "react-dom";
import posed, { PoseGroup } from "react-pose";
import styled from "styled-components";
const sequence = b =>
b.every(
(a, i) => !(a.call ? a() : setTimeout(() => sequence(b.slice(++i)), a))
);
const usePose = (initial, poses = {}) => {
const [pose, setPose] = useState(initial);
return { pose, setPose, poses };
};
const useAnimation = () => {
const { pose, setPose } = usePose(`hidden`, [`hidden`, `normal`]);
useEffect(() => {
sequence([3000, () => setPose(`normal`)]);
}, []);
return {
pose
};
};
const Container = styled(
posed.div({
hidden: {
opacity: 0
},
normal: { opacity: 1 }
})
)({
color: "red"
});
const App = () => {
const { pose } = useAnimation();
return (
<PoseGroup animateOnMount>
<Container key={0} pose={pose}>
<h1>hello world</h1>
</Container>
</PoseGroup>
);
};
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Issue solved by:
const Container = styled(
posed.div({
hidden: {
opacity: 0
},
normal: { opacity: 1 }
})
)({
color: "red"
opacity: 0, // Add this to stop flash.
});