I have a list of p tags, and I want to cycle through this list, by fading in one p tag, then fading out, then again fading in after replacing it.
Here is this codepen in jQuery: https://codepen.io/motion333/pen/EBBGVM
I am trying to do this in React by this:
useEffect(() => {
(function() {
var quotes = document.getElementsByClassName('tagline-text');
var quoteIndex = -1;
function showNextQuote() {
++quoteIndex;
document.querySelectorAll(".tagline-text")[quoteIndex % quotes.length].fadeIn(1000).delay(1000).fadeOut(1000, showNextQuote);
}
showNextQuote();
})();
}, []);
And this is the conainer:
<div className="tagline h-100 d-flex flex-column align-items-center justify-content-center">
<p className="tagline-text">Your Business</p>
<p className="tagline-text">Your Brand</p>
<p className="tagline-text">Your Content</p>
<p className="tagline-text">Your Portfolio</p>
<p className="tagline-text">You.</p>
</div>
But it gives me this error:
Uncaught TypeError: document.querySelectorAll(...)[(quoteIndex % quotes.length)].fadeIn is not a function
this should do it.
const { useState, useEffect } = React;
const texts = ["Your Business", "Your Brand", "Your Content", "Your Portfolio", "You."];
const time_between_text = 2; // text show for 2s before fade out.
const transition_duration = 0.5;
const App = () => {
const [show, setShow] = useState(0);
useEffect(() => {
const timerId = setInterval(() => {
setShow(p => {
if(p === texts.length - 1) p = -transition_duration;
else p = p + transition_duration;
return p;
});
}, time_between_text * 1000)
return () => clearInterval(timerId);
}, [])
return <div className="pContainer">
{texts.map((t, i) => <p key={i} style={{ opacity: `${show === i ? 1 : 0}`, transitionDuration: `${time_between_text + transition_duration}s` }}>{t}</p>)}
</div>
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App />
);
.pContainer {
position: relative;
}
.pContainer p {
font-size: 36px;
font-weight: bold;
position: absolute;
top: 0;
left: 0;
opacity: 0;
transition-property: opacity;
transition-timing-function: ease-in-out;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Related
Can someone explain how to make smooth transition like opacity 0 - opacity 1 with CSStransiton or react-spring animation, when data comes from server and i'm doing map div instantly appears without transition.
i want to make transition on form submit, when im returning data from map(), could someone show me how to add this transition with CSStransition or react-spring.
import React, {useState} from "react";
import axios from "axios";
import moment from "moment";
import { KEY } from "../../const";
import { cloudy, hurricane, rain, snow, sunny } from "./weatherType";
import "./winderCondition.scss";
import "./weather.scss";
import {CSSTransition} from "react-transition-group";
export const Weather = () => {
const [currentWeatherData, setCurrentWeatherData] = useState([]);
const [foreCast, setForeCast] = useState([]);
const [query, setQuery] = useState("");
const getCurrentWeather = async (query: string) => {
const response = await axios.get(`https://api.weatherbit.io/v2.0/current?&city=${query ? query : ""}&key=${KEY}`)
setCurrentWeatherData(response.data.data)
};
const getForecast = async (query: string) => {
const response = await axios.get(`https://api.weatherbit.io/v2.0/forecast/daily?&city=${query ? query : ""}&key=${KEY}&days=5`)
setForeCast(response.data.data);
foreCast.shift();
};
const handleCityChange = (e: any) => {
setQuery(e.target.value);
};
const handleOnSubmit = async (e: any) => {
e.preventDefault();
await getCurrentWeather(query);
await getForecast(query);
};
const getCondition = (weatherCode: number) => {
if (weatherCode >= 200 && weatherCode <= 233) {
return hurricane;
}
if (weatherCode >= 300 && weatherCode <= 522) {
return rain;
}
if (weatherCode >= 600 && weatherCode <= 610) {
return snow;
}
if (weatherCode === 800) {
return sunny;
}
if (weatherCode >= 801 && weatherCode <= 900) {
return cloudy;
}
};
return (
<div className="weather">
<form onSubmit={handleOnSubmit}>
<div className="input_wrapper">
<input className="city-input"
type="text"
onChange={(e) => handleCityChange(e)}
value={query}
name="city"
/>
<label className={query.length !== 0 ? "move-up" : "city-label"} htmlFor="city">Your City</label>
</div>
<button type="submit">Search</button>
</form>
<div className="weather-wrapper">
{currentWeatherData &&
currentWeatherData.map((weather: any) => {
return (
<CSSTransition classNames="my-node" key={weather.city_name} in={true} timeout={300}>
<div className="currentWeather">
<div className="gradient">
<div className="country">
Location: {`${weather.city_name}, ${weather.country_code}`}
</div>
<div className="temperature">
{Math.floor(weather.temp)} °C
</div>
{getCondition(weather.weather.code)}
<div>{weather.weather.description}</div>
</div>
</div>
</CSSTransition>
);
})}
<div className="forecast-wrapper">
{foreCast &&
foreCast.map((weather: any) => {
return (
<div className="forecast" key={weather.ts}>
<div className="forecast-date">
{moment(weather.ts * 1000).format("dddd")}
</div>
<div>{Math.round(weather.temp)} °C</div>
<img
className="forecast-icon"
src={`https://www.weatherbit.io/static/img/icons/${weather.weather.icon}.png`}
alt="weather-condition"
/>
</div>
);
})}
</div>
</div>
</div>
);
};
CSS
.my-node-enter {
opacity: 0;
}
.my-node-enter-active {
opacity: 1;
transition: opacity 500ms;
}
.my-node-exit {
opacity: 1;
}
.my-node-exit-active {
opacity: 0;
transition: opacity 500ms;
}
just needed to add another prop with value true appear={true} and classNames for it.
<CSSTransition classNames="fade" key={weather.city_name} in={true} timeout={500} appear={true]>
<div className="currentWeather">
<div className="gradient">
<div className="country">
Location: {`${weather.city_name}, ${weather.country_code}`}
</div>
<div className="temperature">
{Math.floor(weather.temp)} °C
</div>
{getCondition(weather.weather.code)}
<div>{weather.weather.description}</div>
</div>
</div>
</CSSTransition>
.fade-appear {
opacity: 0.01;
}
.fade-appear.fade-appear-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
thanks to user "wherewereat" from reddit
Here a small demo. There are a few block; hovering on each block appears a tooltip(orange rect). It doesn't work correctly.
Tooltip should be displayed from left or right side. To get sizes of tooltip need to display it. Coords to display tooltip can be calculated only after tooltip is displayed
Codesandbox https://codesandbox.io/s/react-ref-65jj6?file=/src/index.js:88-231
const { useState, useEffect, useCallback } = React;
function App() {
return (
<div>
<HoveredBlock index={1} />
<HoveredBlock index={2} blockStyle={{ marginLeft: "5%" }} />
<HoveredBlock index={3} blockStyle={{ marginLeft: "50%" }} />
</div>
);
}
function calcCoords(blockRect, hoverRect) {
const docWidth = document.documentElement.clientWidth;
const isLeft = blockRect.right + hoverRect.width > docWidth;
const coords = {};
if (!isLeft) {
coords.x = blockRect.right;
coords.y = blockRect.top;
coords.type = "right";
} else {
coords.x = blockRect.left - 5 - hoverRect.width;
coords.y = blockRect.top;
coords.type = "left";
}
return coords;
}
function HoveredBlock({ index, blockStyle }) {
const [blockRect, setBlockRect] = useState();
const [hoverRect, setHoverRect] = useState();
const [showHover, setShowHover] = useState(false);
const [coords, setCoords] = useState();
const blockRef = useCallback((node) => {
if (node) {
setBlockRect(node.getBoundingClientRect());
}
}, []);
const hoverRef = useCallback(
(node) => {
if (showHover && node) {
setHoverRect(node.getBoundingClientRect());
}
},
[showHover]
);
useEffect(() => {
if (showHover && hoverRect) {
const coords = calcCoords(blockRect, hoverRect);
setCoords(coords);
}
}, [hoverRect]);
const isHidden = !showHover || !coords ? 'hidden' : '';
return (
<div>
<div
ref={blockRef}
className="block"
style={blockStyle}
onMouseEnter={() => setShowHover(true)}
onMouseLeave={() => setShowHover(false)}
>
{index}
</div>
<div
ref={hoverRef}
className={'hover-block' + isHidden}
style={{
left: coords && coords.x,
top: coords && coords.y
}}
/>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
.block {
width: 100px;
height: 100px;
background-color: aquamarine;
margin-left: 82%;
}
.hover-block {
position: fixed;
width: 100px;
height: 100px;
background-color: coral;
}
.hidden {
display: none;
}
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
I solved it. I changed the way how to hide element: visibility:hidded instead of display:none
export default function HoveredBlock({ blockStyle }) {
const [blockRect, setBlockRect] = useState();
const [hoverRect, setHoverRect] = useState();
const [showHover, setShowHover] = useState(false);
const [coords, setCoords] = useState();
const blockRef = useCallback((node) => {
if (node) {
setBlockRect(node.getBoundingClientRect());
}
}, []);
const hoverRef = useCallback((node) => {
if (node) {
setHoverRect(node.getBoundingClientRect());
}
}, []);
useEffect(() => {
if (showHover) {
console.log({ blockRect, hoverRect });
const coords = calcCoords(blockRect, hoverRect);
setCoords(coords);
}
}, [showHover, blockRect, hoverRect]);
return (
<>
<div
ref={blockRef}
className="block"
style={blockStyle}
onMouseEnter={() => setShowHover(true)}
onMouseLeave={() => setShowHover(false)}
/>
<div
ref={hoverRef}
className={cx("hover-block", {
hidden: !showHover || !coords
})}
style={{
left: coords && coords.x,
top: coords && coords.y
}}
></div>
</>
);
}
.block {
width: 100px;
height: 100px;
background-color: aquamarine;
margin-left: 20%;
}
.hover-block {
position: fixed;
width: 100px;
height: 100px;
background-color: coral;
}
.hidden {
visibility: hidden;
}
const {useState, useEffect, useRef} = React;
const App = () => {
const [pressed, setPressed] = useState(false);
const [shoot, setShoot] = useState(false);
const [seconds, setSeconds] = useState(0);
useInterval(() => {
// Your custom logic here
pressed && seconds < 3 && setSeconds((prev)=> Number((prev+0.1).toFixed(1)));
}, 100);
useInterval(()=>{
!pressed && seconds > 0 && setSeconds((prev)=>{
if( Number((prev-0.5).toFixed(1)) < 0){
return 0;
}
return Number((prev-0.5).toFixed(1))
});
}, 20)
return (
<div>
<button
onMouseDown={()=>{
console.log('mouseDown')
setShoot(false);
setPressed(true);
}}
onMouseUp={()=>{
console.log('mouseUp')
setShoot(true);
setPressed(false);
}}
style={{
transform: `rotate(-${seconds*15}deg)`
}}
>Press</button>
<span className={`dot ${shoot ? '--shooted' : ''}`} />
<p>{seconds}</p>
</div>
)
};
ReactDOM.render(<App />, document.getElementById('root'));
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
.dot{
position: absolute;
width: 16px;
height: 16px;
border-radius:100%;
background: red;
}
.dot.--shooted{
animation: test 1s;
}
#keyframes test{
0%{
transform: translateX(0px);
}
100%{
transform: translateX(200px); // it should be dynamic px.
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.26.0/moment.min.js"></script>
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root" />
I'd like to move the red dot as much as the seconds I pressed the button.
but I am using animation so I can't control the px in CSS.
If I pressed the button for 3seconds, the red dot should be moved to 300px.
If I pressed the button for 1seconds, the red dot should be moved to 100px.
This is an example. But you need to add a logic to move it back.
const {
useState,
useEffect,
useRef
} = React;
const App = () => {
const [pressed, setPressed] = useState(false);
const [shoot, setShoot] = useState(false);
const [seconds, setSeconds] = useState(0);
const dotRef = useRef();
useInterval(() => {
// Your custom logic here
pressed && seconds < 3 && setSeconds((prev) => Number((prev + 0.1).toFixed(1)));
}, 100);
useInterval(() => {
!pressed && seconds > 0 && setSeconds((prev) => {
if (Number((prev - 0.5).toFixed(1)) < 0) {
return 0;
}
return Number((prev - 0.5).toFixed(1))
});
}, 20)
const handleMouseUp = () => {
dotRef.current.style.transform = `translateX(${seconds * 100}px)`;
}
return ( <
div >
<
button onMouseDown = {
() => {
console.log('mouseDown')
setShoot(false);
setPressed(true);
}
}
onMouseUp = {
() => {
console.log('mouseUp')
setShoot(true);
setPressed(false);
handleMouseUp();
}
}
style = {
{
transform: `rotate(-${seconds*15}deg)`
}
} >
Press < /button> <
span className = {
`dot ${shoot ? '--shooted' : ''}`
}
ref = {
dotRef
}
/> <
p > {
seconds
} < /p> < /
div >
)
};
ReactDOM.render( < App / > , document.getElementById('root'));
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
.dot {
position: absolute;
width: 16px;
height: 16px;
border-radius: 100%;
background: red;
transition: transform 1s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.26.0/moment.min.js"></script>
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root" />
I have literally tried for a few hours to replicate a clickable ticker, much like they have at the very top of this site: https://www.thebay.com/
I'm confused about what triggers useEffect and long story short, I can't come up with a solution that keeps the ticker moving AND also gives the option of clicking forward/backwards via arrows. Clicking the arrow should not permanently pause the ticker.
function Ticker() {
const [tickerDisplay, setTickerDisplay] = useState('Free In-store Pickup')
const [tickerIndex, setTickerIndex] = useState(0)
const [arrowClicked, setArrowClicked] = useState(false)
const notices = [
'Easy Returns within 30 Days of Purchase',
'Free Shipping on $99+ Orders',
'Free In-store Pickup',
]
const handleClick = (side) => {
setArrowClicked(true)
switch (side) {
case 'left':
setTickerIndex(
tickerIndex === 0 ? notices.length - 1 : tickerIndex - 1
)
break
case 'right':
setTickerIndex(
tickerIndex === notices.length - 1 ? 0 : tickerIndex + 1
)
break
default:
console.log('something went wrong')
break
}
}
useEffect(() => {
if (arrowClicked) {
setTickerDisplay(notices[tickerIndex])
setTickerIndex(
tickerIndex === notices.length - 1 ? 0 : tickerIndex + 1
)
setArrowClicked(false)
return
}
setTimeout(() => {
setTickerDisplay(notices[tickerIndex])
setTickerIndex(
tickerIndex === notices.length - 1 ? 0 : tickerIndex + 1
)
console.log('This will run every 6 seconds!')
}, 6000)
}, [tickerIndex, notices, tickerDisplay, arrowClicked])
return (
<IconContext.Provider value={{ className: 'ticker-icons-provider' }}>
<div className='ticker'>
<FaChevronLeft onClick={() => handleClick('left')} />
<div className='ticker_msg-wrapper'>{tickerDisplay}</div>
<FaChevronRight onClick={() => handleClick('right')} />
</div>
</IconContext.Provider>
)
}
export default Ticker
What is the best way to code this component?
This is not a work of art and probably some things could've been done better.
Hope that suits you.
const { useRef, useState, useEffect } = React;
const getItems = () => Promise.resolve(['All of our questions are now open', 'Answers extended: 72 hours after questions open', 'Post a question or get an answer', 'Free badges on 20k points'])
const Ticker = ({onPrevious, onNext, items, currentIndex}) => {
const ref = useRef(null);
const [size, setSize] = useState({
width: 0,
widthPx: '0px',
height: 0,
heightPx: '0px'
})
useEffect(() => {
if(ref && ref.current) {
const {width, height} = ref.current.getBoundingClientRect();
setSize({
width,
widthPx: `${width}px`,
height,
height: `${height}px`
})
}
}, [ref]);
const calculateStyleForItem = (index) => {
return {
width: size.width,
transform: `translateX(${0}px)`
}
}
const calculateStyleForContainer = () => {
return {
width: `${size.width * (items.length + 1)}px`,
transform: `translateX(${-currentIndex * size.width + 2 * size.width}px)`
}
}
return <div ref={ref} className="ticker">
<div style={{width: size.widthPx, height: size.heightPx}} className="ticker__foreground">
<div onClick={onPrevious} className="arrow">{'<'}</div>
<div onClick={onNext} className="arrow">{'>'}</div>
</div>
<div>
<div style={calculateStyleForContainer()} className="ticker__values">
{items.map((value, index) => <div key={index} style={calculateStyleForItem(index)}className="ticker__value">{value}</div>)}
</div>
</div>
</div>
}
const App = () => {
const [items, setItems] = useState([]);
const [currentIndex, setCurrentIndex] = useState(0);
const [clicked, setClicked] = useState(false);
useEffect(() => {
let isUnmounted = false;
getItems()
.then(items => {
if(isUnmounted) {
return
}
setItems(items);
})
return () => {
isUnmounted = true;
}
}, [])
useEffect(() => {
if(!items.length) {
return () => {
}
}
let handle = null;
const loop = () => {
if(!clicked) {
onNext(null);
}
setClicked(false);
handle = setTimeout(loop, 2000);
}
handle = setTimeout(loop, 2000);
return () => {
clearTimeout(handle);
}
}, [items, clicked])
const onPrevious = () => {
setClicked(true);
setCurrentIndex(index => (index - 1) > -1 ? index - 1 : items.length - 1)
}
const onNext = (programmatically) => {
if(programmatically) {
setClicked(programmatically);
}
setCurrentIndex(index => (index + 1) % items.length)
}
return <div>
{items.length ? <Ticker onPrevious={onPrevious} onNext={onNext} currentIndex={currentIndex} items={items}/> : 'Loading'}
</div>
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
html, body {
box-sizing: border-box;
margin: 0;
}
.ticker {
display: flex;
justify-content: center;
align-items: center;
background: black;
font-size: 1rem;
color: white;
font-weight: bold;
padding: 1rem;
overflow: hidden;
}
.ticker__foreground {
position: absolute;
z-index: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
.ticker__values {
transition: all .3s ease-in;
}
.ticker__value {
text-align: center;
display: inline-block;
vertical-align: middle;
float: none;
}
.arrow {
font-size: 1.5rem;
cursor: pointer;
padding: 1rem;
}
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
I have a component that switches some content and the animation of the content depending on the side it is switching it from:
import React, { Component } from "react";
class Skills extends Component {
constructor(props) {
super(props);
this.state = {
shownSkill: 0,
fallIn: true,
slideUp: false
};
}
getPreviousSkill = () => {
const { shownSkill } = this.state;
const newSkill = shownSkill < 1 ? 3 : shownSkill - 1;
this.updateShownSkill(newSkill, false);
};
getNextSkill = () => {
const { shownSkill } = this.state;
const newSkill = shownSkill > 2 ? 0 : shownSkill + 1;
this.updateShownSkill(newSkill, true);
};
updateShownSkill = (skillIndex, fallIn) => {
this.setState({
shownSkill: skillIndex,
fallIn: fallIn,
slideUp: !fallIn
});
};
getSkillData = () => {
const { skills } = this.props;
const { shownSkill } = this.state;
return skills[shownSkill];
};
render() {
const { name, skill, description } = this.getSkillData();
const { shownSkill, slideUp } = this.state;
const { skills } = this.props;
return (
<div className="route-container skills">
<div className="skills-content-container">
{slideUp ? (
<div className="skills-right-content slide-up">
<div className="subtitle">{name}</div>
{description.map((p, i) => (
<div className="text" key={i}>
{p}
</div>
))}
</div>
) : (
<div className="skills-right-content
fall-in">
<div className="subtitle">{name}</div>
{description.map((p, i) => (
<div className="text" key={i}>
{p}
</div>
))}
</div>
)}
</div>
</div>
);
}
}
export default Skills;
Then I am animating the .fall-in class with css:
#keyframes fall-in {
0% {
margin-top: -600px;
}
100% {
margin-top: 0;
}
}
.fall-in {
animation-name: fall-in;
animation-duration: 0.5s;
animation-timing-function: linear;
animation-iteration-count: 1;
}
I would like this animation to trigger once every time the content of the .subtitle and .text divs changes, regardless of whether or not the animation changed.
This example will only trigger the animation the first time the css class is added.
Hey maybe you want to give a try on my OSS.
https://github.com/bluebill1049/react-simple-animate
I think it does what you want above, maybe worth to give it a try?
import Animate from 'react-simple-img';
import React from 'react';
export default ({ready}) => {
return <Animate startAnimation={ready} startStyle={{
marginTop: '-600px',
}} endStyle={{
marginTop: '0',
}}>
<YourComponent />
</Animate>
};