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>
};
Related
Since I am new to React, I am unable to understand why the below code is not showing the array bars correctly.
After inspecting, I realised that the code is not even generating the 'array-bar' div correctly.
Can somebody help in why this is not working ?
P.S.- This is an array visualiser which visualizes a random array.
import React, { Component } from "react";
import "./SortingViz.css";
export default class SortingViz extends Component {
constructor(props) {
super(props);
this.state = {
array: [],
};
}
componentDidMount() {
this.resetArray();
}
resetArray() {
let array = [];
for (let i = 0; i < 150; i++) {
array.push(randomInt(100,600));
}
this.setState(array);
console.log(array);
}
render() {
const { array } = this.state;
return (
<div className="array-container">
{array.map((value, idx) => {
<div className="array-bar"
key={idx}
style={{ height: `${value}px` }}></div>;
})}
<button onClick={() => this.resetArray}>Generate</button>
</div>
);
}
}
function randomInt(min, max)
{
return Math.floor(Math.random() * (max - min + 1)) + min;
}
Here is the CSS for ref.
.array-container{
margin: 0 auto;
}
.array-bar{
background-color: blue;
width: 5px;
}
You need to return the div from the map callback
render() {
const { array } = this.state;
return (
<div className="array-container">
{array.map((value, idx) => {
return <div className="array-bar"
key={idx}
style={{ height: `${value}px` }}></div>;
})}
<button onClick={() => this.resetArray}>Generate</button>
</div>
);
}
The reason is what #Mina says.
But given you only return an element and don't do anymore, you could Aldo do it by changing {} for () in your callback. That way indicates you return all surrender by (...)
render() {
const { array } = this.state;
return (
<div className="array-container">
{array.map((value, idx) => (
<div className="array-bar"
key={idx}
style={{ height: `${value}px` }}></div>;
))}
<button onClick={() => this.resetArray}>Generate</button>
</div>
);
}
I'm working on this code to make the image change on click, it works, but the transition between them is rough. Image and name both come from parent component <App.js/>, and I'd like to add an effect to them to make it more subtle. Images already have this kind of effect when they are visible on screen. This is the code for the <Message.jsx/> child component:
function Message(props){
return (
<div className="msg-div-index msg-div-height my-5">
<h3 className="text-start fs-5 m-0 px-2">Make us</h3>
<h2 className="msg-margin msg-lbl">{props.label}</h2>
<img src={props.img} className={"img-fluid msg-img-index msg-img-opacity msg-img-border inline-photo" + (props.visible ? " is-visible" : "")} alt={props.imgAlt}></img>
</div>
)
}
export default Message
And this is the code for the <App.js/> parent component in case you need it:
function useOnScreen(options){
const [ref, setRef] = useState(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setVisible(entry.isIntersecting);
}, options);
if (ref) {
observer.observe(ref);
}
return ( () => {
if (ref) {
observer.unobserve(ref);
}
})
}, [ref, options]);
console.log(visible);
return [setRef, visible];
}
function App() {
const [setRef, visible] = useOnScreen({ threshold: 0.2 });
const [index, setIndex] = useState(0);
const imgArray = [{img: dance, label:"Dance", imgAlt:"People dancing"},
{img: cry, label:"Cry", imgAlt:"Sunset with guitars"},
{img: mariachi, label:"Remember", imgAlt:"Mariachi singing"},
{img: concert, label:"Sing along", imgAlt:"Concert"}];
function handleClick(){
if (index === imgArray.length - 1){
setIndex(0)
} else {
setIndex(index + 1)
}
}
return (
<div className="App karaoke_bgr">
<Header />
<div ref={setRef} onClick={()=>handleClick()}>
<Message visible={visible} img={imgArray[index].img} label={imgArray[index].label} imgAlt={imgArray[index].imgAlt}/>
</div>
<Options date="Friday, September 24th"/>
<Footer />
</div>
);
}
export default App;
I want to add a transition effect each time the props change (when the user clicks it), so the next image slowly appears. Thank you!
Any suggestions about the code itself are highly appreciated. I'm new with React and trying to see what it can do.
You can use react-transition-group for this
styles.css
.fade-enter {
opacity: 0;
}
.fade-exit {
opacity: 1;
}
.fade-enter-active {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
}
.fade-enter-active,
.fade-exit-active {
transition: opacity 500ms;
}
import { useState, useEffect } from "react";
import { SwitchTransition, CSSTransition } from "react-transition-group";
import "./styles.css";
function App() {
const [setRef, visible] = useOnScreen({ threshold: 0.2 });
const [index, setIndex] = useState(0);
const imgArray = [
{ img: "dance", label: "Dance", imgAlt: "People dancing" },
{ img: "cry", label: "Cry", imgAlt: "Sunset with guitars" },
{ img: "mariachi", label: "Remember", imgAlt: "Mariachi singing" },
{ img: "concert", label: "Sing along", imgAlt: "Concert" }
];
function handleClick() {
if (index === imgArray.length - 1) {
setIndex(0);
} else {
setIndex(index + 1);
}
}
return (
<div className="App karaoke_bgr">
<div ref={setRef} onClick={() => handleClick()}>
<SwitchTransition>
<CSSTransition
key={index}
addEndListener={(node, done) =>
node.addEventListener("transitionend", done, false)
}
classNames="fade"
>
<Message
visible={visible}
img={imgArray[index].img}
label={imgArray[index].label}
imgAlt={imgArray[index].imgAlt}
/>
</CSSTransition>
</SwitchTransition>
</div>
</div>
);
}
export default App;
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
so what i try to achieve here is very similar to what is done here Transition flex-grow of items in a flexbox
But what i wonder how this could be done with React say i have this code
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
classNameToUse: ''
};
this.onElementClicked = this.onElementClicked.bind(this);
}
onElementClicked() {
this.setState({ classNameToUse : 'big-size'})
}
render() {
return (
<div>
<div className={this.state.classNameToUse} onClick={this.onElementClicked} >
something
</div>
<div className={this.state.classNameToUse onClick={this.onElementClicked} >
something else
</div>
</div>
);
}
}
This would of course add the className to them both but what i want to achieve is that one of them grows big with animation and the other collapse. And it sohuldnt matter if i have 2 or 10 elements
You can set active index on click:
// State
this.state = {
activeIndex: null
};
// event
onElementClicked(e) {
this.setState({ activeIndex: e.target.index })
}
// class to use
className={this.index === this.state.activeIndex ? 'big-size' : ''}
const { useState, useEffect } = React;
const App = () => {
const [divs,] = useState(['blue', 'green', 'black']);
const [selected, setSelected] = useState(null);
const onClick = (id) => {
setSelected(id);
}
return <div className="container">
{divs.map(pr => <div key={pr} style={{background: pr}} className={`box ${pr === selected ? 'selected' : ''}`} onClick={() => onClick(pr)}></div>)}
</div>
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
.container {
display: flex;
height: 200px;
}
.box {
flex: 1;
cursor: pointer;
transition: all .3s ease-in;
}
.selected {
flex: 2;
}
<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>
<script src="https://unpkg.com/#material-ui/core#latest/umd/material-ui.development.js"></script>
<script src="https://unpkg.com/material-ui-lab-umd#4.0.0-alpha.32/material-ui-lab.development.js"></script>
<div id="root"></div>
Code is here: https://codesandbox.io/s/gatsby-starter-default-ry8sm
You can try demo: https://ry8sm.sse.codesandbox.io/
Every picture is an Enlarger component which will zoom in when you click on it. And they are designed to show up sequentially by fading in. I use Ref to track every Enlarger and here is the code snippet for it.
import Img from "react-image-enlarger"
class Enlarger extends React.Component {
state = { zoomed: false, opacity: 0 }
toggleOpacity = o => {
this.setState({ opacity: o })
}
render() {
const { index, orderIndex, src, enlargedSrc, onLoad } = this.props
return (
<div style={{ margin: "0.25rem" }} onLoad={onLoad}>
<Img
style={{
opacity: this.state.opacity,
transition: "opacity 0.5s cubic-bezier(0.25,0.46,0.45,0.94)",
transitionDelay: `${orderIndex * 0.07}s`,
}}
zoomed={this.state.zoomed}
src={src}
enlargedSrc={enlargedSrc}
onClick={() => {
this.setState({ zoomed: true })
}}
onRequestClose={() => {
this.setState({ zoomed: false })
}}
/>
</div>
)
}
}
export default Enlarger
And I have a Masonry component which will achieve the Masonry layout
import React, { Component } from "react"
import imagesLoaded from "imagesloaded"
import PropTypes from "prop-types"
import TransitionGroup from "react-transition-group/TransitionGroup"
class MasonryGrid extends Component {
componentDidMount() {
window.onload = this.resizeAllGridItems()
window.addEventListener("resize", this.resizeAllGridItems)
let allItems = document.getElementsByClassName("masonry-grid--item")
for (let x = 0; x < allItems.length; x++) {
imagesLoaded(allItems[x], this.resizeInstance)
}
}
resizeAllGridItems = () => {
let allItems = document.getElementsByClassName("masonry-grid--item")
for (let x = 0; x < allItems.length; x++) {
this.resizeGridItem(allItems[x])
}
}
resizeGridItem = item => {
let grid = document.getElementsByClassName("masonry-grid")[0]
let rowHeight = parseInt(
window.getComputedStyle(grid).getPropertyValue("grid-auto-rows")
)
let rowGap = parseInt(
window.getComputedStyle(grid).getPropertyValue("grid-row-gap")
)
let rowSpan = Math.ceil(
(item.querySelector(".content").getBoundingClientRect().height + rowGap) /
(rowHeight + rowGap)
)
item.style.gridRowEnd = "span " + rowSpan
}
resizeInstance = instance => {
let item = instance.elements[0]
this.resizeGridItem(item)
}
render() {
const MasonryGrid = {
display: "grid",
gridGap: `${this.props.gridGap}`,
gridTemplateColumns: `repeat(auto-fill, minmax(${
this.props.itemWidth
}px, 1fr))`,
gridAutoRows: "10px",
}
return (
<TransitionGroup>
<div className="masonry-grid" style={MasonryGrid}>
{this.props.children.length >= 1 &&
this.props.children.map((item, index) => {
return (
<div className="masonry-grid--item" key={index}>
<div className="content">{item}</div>
</div>
)
})}
</div>
</TransitionGroup>
)
}
}
MasonryGrid.defaultProps = {
itemWidth: 250,
gridGap: "6px 10px",
}
MasonryGrid.propTypes = {
itemWidth: PropTypes.number,
gridGap: PropTypes.string,
}
export default MasonryGrid
The problem is, if you look at the demo, when you click on tab project1, you will see the pictures show up on top of each other and doesn't spread well as intended. But once you resize the browser a little bit, they becomes normal and form the Masonry layout I wanted. I suspect it has something to do with the fade-in effect I implemented but I don't know how to fix it.