How to animate image for 5 seconds in React using CSS? - javascript

I'm using React with Redux, and I have the following situation. In my component I have a div that holds and image, and the component is also receiving a property from my Redux state which is called showIcon. So, if showIcon is not null, I want the image to be displayed for 5 seconds, and once the 5 seconds passes, I want it to disappear and set the showIcon value to null by dispatching an action that I have like updateShowIcon(null);. How can I do this properly in React, and how can I use CSS to show and animate the icon as I want?
import React, { Component } from 'react';
class MyComp extends Component {
render() {
return (
<div style={styles.mainDiv}>
<div style={styles.childDiv}>
{
this.props.showIcon &&
<div style={styles.iconStlyes}>
<img src={process.env.PUBLIC_URL + '/icons/myicon.png'}/>
</div>
}
// partially removed for brevity, some other component
</div>
</div>
);
}
}
const styles = {
iconStlyes: {
position: 'absolute',
zIndex: 10,
},
mainDiv: {
overflow: 'auto',
margin: 'auto',
height: 'calc(100vh - 64px)',
padding: 0,
},
childDiv: {
height: 'calc(100vh - 64px)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
},
};
export default MyComp;

Whenever I detect a change in componentWillReceiveProps I would create a timer and dispatch the action. Remember to clear the timeout on componentWillUnmount.
The idea is based on you showing and hiding the icon with css and not with react conditional rendering, so once you need to show the icon you add the class show or remove it once you don't need to show it.
It would probably look like this:
componentWillReceiveProps(nextProps){
if (nextProps.showIcon && nextProps.showIcon !== this.props.showIcon){
this.timer = setTimeout(() => {nextProps.updateShowIcon(null)}, 5000);
}
}
componentWillUnmount(){
clearTimeout(this.timer);
}
render() {
const {showIcon} = this.props;
return (
<div style={styles.mainDiv}>
<div style={styles.childDiv}>
<div style={styles.iconStlyes} className={`${showIcon ? 'show':''} icon-container`}>
<img src={process.env.PUBLIC_URL + '/icons/myicon.png'}/>
</div>
</div>
</div>
);
}
and your css for a simple fade animation:
.icon-container{
opacity: 0;
transition: opacity: 500ms ease-in;
}
.icon-container.show{
opacity: 1;
}

If it is important for you to use the store state then you can manage the showIcon property via componentWillReceiveProps(nextProps) and do something like:
componentWillReceiveProps(nextProps){
if(!this.props.showIcon && nextProps.showIcon){
setTimeout(()=>dispatch(updateShowIcon(null)),5*1000);
}
//manage clear timeout if necessary
}
But for the animation part its better to use the showIcon property as a class and not for adding/removing it from the DOM, like:
<div style={styles.iconStlyes} className={this.props.showIcon?'show':'hide'}>
<img src={process.env.PUBLIC_URL + '/icons/myicon.png'}/>
</div>
and the styles should manage it:
iconStyles: {
position: 'absolute',
zIndex: 10;
transition: //effects of specified or all attributes
&.show{
visibility: visible;//display:block
}
&.hide{
visibility: hidden;//display:none
}
}

Related

Display different images onMouseOver in React

I'm looking to display a different image for every link I hover over. When I hover over each link, both images display on top of each other. I feel as if my issue stems from the conditional, which will show any image I place within it and not just one specific image.
I'm wondering if there's a better approach. Perhaps holding the images within the state?
My code:
class PhotoIndex extends Component {
state = {
hover: false
}
mouseOver = () => {
this.setState({ hover: true })
}
mouseOut = () => {
this.setState({ hover: false })
}
render() {
return (
<Wrapper>
<IndexWrapper>
<li>
<StyledLink
onMouseOver={this.mouseOver}
onMouseOut={this.mouseOut}
to="/checkered-flag/">Checkered Flag
{this.state.hover
?
<Fade >
<div style={{ position: 'relative' }}>
<img
style={{ position: 'absolute', top: '-200px', left: '100%' }}
src={car14}
alt="red car parked in a parkin lot"
/>
</div>
</Fade>
: null}
</StyledLink>
</li>
<li>
<StyledLink
onMouseOver={this.mouseOver}
onMouseOut={this.mouseOut}>
Birds Nest
{this.state.hover
?
<Fade >
<div style={{ position: 'relative' }}>
<img
style={{ position: 'absolute', top: '-200px', left: '100%' }}
src={car15}
alt="blue car parked in a grassy field"
/>
</div>
</Fade>
: null}
</StyledLink>
</li>
<li>
<StyledLink>The Grand Turret</StyledLink>
</li>
<li>
<StyledLink>Simulation Theory</StyledLink>
</li>
</IndexWrapper>
</Wrapper>
)
}
}
I will try to simplify the code to explain the solution.
If you wish to go with this solution the images should be numbered in order with a set structure. For example car0.jpg ,car1.jpg, car2.jpg .....
ImageGetter.js
import car1 from './cars/car1.jpg';
import car2 from './cars/car2.jpg';
export default {
car1,car2
}
In the above code I am importing the images and exporting them so they can be consumed by any component which uses the ImageGetter object.
PhotoIndex.js
import ImageGetter from './ImageGetter';
class PhotoIndex extends Component {
constructor() {
super();
this.state = {
carImgNum: '0',
hover: false
}
}
mouseOver = () => {
const max = 5; //Max number of images
const newcarImgNum = Math.floor(Math.random() * Math.floor(max));
this.setState({ hover: true, carImgNum: newcarImgNum });
}
render() {
const { carImgNum } = this.state;
return (
<div onMouseOver={this.mouseOver}>
<img src={ImageGetter[`car${carImgNum}`]} alt="" />
</div>
)
}
}
export default PhotoIndex;
You will have to create a default state for the number of the image which will be displayed. Over here the default image displayed will be car0.jpg.
In mouseOver function you will have to define how many images are available. (You can make the number of images dynamic with some other function too.).
It then creates a random number from 0 to the max number you specified and sets the value to the carImgNum state.
Over in the render method I am de structuring the state to get the carImgNum value.
I then pass the ImageGetter into src of the image tag and dynamically target the image i need to pass using templatestrings.

another case when the state of a component changes but seems that the component does not

I have a react component that has a state variable:
showEditor
when showEditor is false it is supposed to show only a div with some number inside it (initially showEditor is false). If this state variable is true my react component is supposed to show a textbox and a button with "save" label inside another div -making dissapear the first div with the number-. This textbox will be used to change the number. For the first div (the one that only shows a number) I defined:
<div onClick={this.showEditorProc}>
{ this.state.showEditor ?
<div ....>
{ just the numeric value }
</div>
:
<div ....>
textBox
<button onClick={save and make show Editor false}>
Save
</button>
</div>
</div>
the function this.showEditorProc will modify the state of the showEditor variable to true and the save button and textbox components will appear (inside another div too). I created a function that will executed if the save button is clicked. This function modifies the showEditor variable to false however, I can not see the div with just the numeric value. Instead I still see the textbox with the save button. Is there something else I could be missing? Here it is the code of my component:
import React from 'react';
import ReactDOM from 'react-dom';
import NumberFormat from 'react-number-format';
export class NumericBox extends React.Component {
constructor() {
super();
this.state = {
enteredValue: '',
showNumEditor: false,
index: ''
};
this.showNumericEditor = this.showNumericEditor.bind(this);
this.handle_enteredValue_change = this.handle_enteredValue_change.bind(this);
this.saveCellInfo = this.saveCellInfo.bind(this);
this.loadBasicInformation = this.loadBasicInformation.bind(this);
}
saveCellInfo(e){
alert(this.state.index);
/* cellAuxParams = new Map([['updateCellValue', this.state.updateCellValue]]); */
console.log('numericBox.js>saveCellInfo>cellAuxParams= ------------------------------------------------ ' + 28);
console.log(this.props.cellAuxParams);
var f = this.props.cellAuxParams.get('updateCellValue');
this.setState({showNumEditor: false}, () => f(this.state.Index, '77'));
}
handle_enteredValue_change(values) {
const {formattedValue, value} = values;
// formattedValue = $2,223
// value ie, 2223
this.setState({enteredValue: value});
}
showNumericEditor()
{
this.setState({showNumEditor: true})
}
loadBasicInformation()
{
this.setState({enteredValue: this.props.enteredValue,
index: this.props.index
});
}
componentDidMount(){
this.loadBasicInformation();
}
componentWillReceiveProps(nextProps){
alert(nextProps.enteredValue);
this.setState({enteredValue: nextProps.enteredValue}, () => this.loadBasicInformation());
}
render() {
const table4controls = {
display: 'table',
width: this.props.style.width,
backgroundColor: 'white',
border: '0px solid #666666',
borderSpacing: '0px',
paddingBottom: '0em',
paddingTop: '0em'
};
const table4controls_RowStyle = {
display: 'table-row',
width: 'auto',
clear: 'both',
borderBottom: '5px'
};
const table4controls_ColsStyleA = {
float: 'left',
display: 'table-column',
width: '60px',
backgroundColor: 'white'
};
const table4controls_ColsStyleB = {
float: 'left',
display: 'table-column',
width: '20px',
backgroundColor: 'white'
};
const table4controls_ColsStyleC = {
float: 'left',
display: 'table-column',
width: '20px',
backgroundColor: 'white'
};
const btnStyle={
};
return (
<div onClick={this.showNumericEditor}>
{ this.state.showNumEditor ?
<div style ={table4controls}>
<div style={table4controls_RowStyle}>
<div style={table4controls_ColsStyleA}>
<NumberFormat style={{width: '60px'}}
value={this.state.enteredValue}
thousandSeparator={true}
prefix={this.props.prefix}
onValueChange={this.handle_enteredValue_change}
/>
</div>
<div style={table4controls_ColsStyleB}>
<button style={btnStyle} onClick={() => this.saveCellInfo(this.state.index)}>
▲
</button>
<button style={btnStyle} onClick={() => this.saveCellInfo(this.state.index)}>
▼
</button>
</div>
<div style={table4controls_ColsStyleC}>
<button style={btnStyle} onClick={(e) => {this.saveCellInfo(e, this.state.index)}}>
Save
</button>
</div>
</div>
</div>
:
<div syle={table4controls_ColsStyleA}>
{this.state.enteredValue}
</div>
}
</div>
);
}
}
You have an onClick={this.showNumericEditor} handler on the surrounding div, so when you press the save button, the click event bubbles up and invokes a this.setState({showNumEditor: true}).
To fix it, you can either restructure the rendering or call e.stopPropagation(); at the start of saveCellInfo. Also note that some of your this.saveCellInfo calls are not passing the event.

Images not displaying in Slider

I am working on a GatsbyJS project, using Contentful, and have created a template 'BlogPost'. Inside this template, I have imported a 'ResearchSlider' component. However, the images are not displaying at all.
I have passed down the image url data, as props, down to the researchSlider, which is then put inside the component state, and used in a function that passes each piece of image data into the 'Slide' child component, using a for loop. In 'Slide', the piece of image data is used as the value for backgroundImage in the styles of a div.
I would like to understand why they are not displaying and how I can fix this.
Here is the relevant code.
ResearchSlider component:
export default class ResearchSlider extends React.Component {
constructor(props) {
super(props);
this.state = {
images: [this.props.firstSlide,
this.props.secondSlide,
this.props.thirdSlide
],
translateValue: 0,
index: 0
}
this.renderSlides = this.renderSlides.bind(this);
this.handleClickPrevious = this.handleClickPrevious.bind(this);
this.handleClickNext = this.handleClickNext.bind(this);
this.slideWidth = this.slideWidth.bind(this);
}
renderSlides() {
const images = this.state.images;
let slides = []
for(let i = 0; i < images.length; i++)
slides.push(<Slide key={i} image={images[i]} />)
return slides
}
render() {
const { translateValue } = this.state
return(
<div className='slider'>
<div className="slider-wrapper"
style={{
transform: `translateX(${translateValue}px)`,
transition: 'transform ease-out 0.3s'
}}>
{ this.renderSlides() }
</div>
<ClickPrevious slideRight={this.handleClickPrevious}/>
<ClickNext slideLeft={this.handleClickNext}/>
</div>
);
}
'Slide' component:
import React from 'react';
const Slide = ({image}) => {
const styles = {
backgroundImage: `url("${image}")`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: '50% 60%'
}
return <div className="slide" style={styles}></div>
}
export default Slide
Here is a screenshot of the inspection:
contentful assets URLs don't have the protocol attached to them they will in the following format //images.contentful.com/...... when setting this url to the src of an <img> tag the browser will assign the protocol automatically based on the protocol you app is using. But using the url in css you need to append explicitly the protocol to the url.
You Slide component should look like this.
const Slide = ({image}) => {
const styles = {
backgroundImage: `url("https:${image}")`,
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: '50% 60%'
}
return <div className="slide" style={styles}></div>
}
export default Slide

React-motion rendering on every animation

I'm getting really bad performance using React-Motion (https://github.com/chenglou/react-motion). I'm animating the height of a dropdown from a table row from 0 to 260.
constructor() {
this.state = {
opened: false
}
this.handleRowClick = this.handleRowClick.bind(this)
}
handleRowClick() {
this.setState({
opened: !this.state.opened
})
}
render() {
<Motion style={{height: spring(this.state.opened ? 260 : 0, {stiffness: 140, damping: 30})}}>
{(height) =>
<div onClick={this.handleRowClick}>
<div style={height}>
...stuff goes here
</div>
</div>
}
</Motion>
}
The animation is working as expected, but upon logging the height every time it renders all of this in the span of ~5 seconds (which is WAY too long):
Maybe I misread something in the docs, but is there a way to avoid lag on the animation?
You'll need to apply the transition styles to a div and render a component inside it which implements shouldComponentUpdate (eg. with React.PureComponent) to prevent it from rerendering when not needed.
render () {
<Motion
style={{
height: spring(
this.state.opened ? 260 : 0,
{ stiffness: 140, damping: 30 }
)
}}
>
{(height) =>
<div style={height}>
<YourComponent />
</div>
}
</Motion>
}
And MyComponent might be something like class MyComponent extends React.PureComponent or using a HOC like pure from recompose. This way MyComponent will only update when it's props changes.

React - change state right after previous state change was rendered

I wanna make cool box grow animation (expand) when user clicks on it and I want to do it following way:
user clicks on expand button -> get div dimensions and top/left positions via ref, store it in state and assign div's style to these values
changed expanded state variable and change div's position to fixed, also change left, top values and width, height css values
My problem is in initial div expand click. It seems that both state's changes are rendered in one cycle so I don't see smooth animation on first expand click. I've tried to do it via setState callback, also tried to update expanded in componentDidUpdate method once div dimensions are in state, nothing worked except delaying expanded set via setTimeout.
Code example via setState callbacks
if (chartsExpanded.get(chart) === "collapsed-end" || !chartsExpanded.get(chart)) {
this.setState({
chartsProportions: chartsProportions.set(
chart,
Map({
left: chartProportions.left,
top: chartProportions.top,
width: chartProportions.width,
height: chartProportions.height
})
)
}, () => {
this.setState({
chartsExpanded: chartsExpanded.set(chart, "expanded")
})
})
}
...
<div
className={`box customers-per-sources-count ${
customersPerSourcesCount.loading ? "loading" : ""
} ${
chartsExpanded.get("customersPerSourcesCount")
? chartsExpanded.get("customersPerSourcesCount")
: "collapsed-end"
}`}
ref={el => {
this.chartRefs["customersPerSourcesCount"] = el
}}
style={{
left: chartsProportions.getIn(["customersPerSourcesCount", "left"], "auto"),
top: chartsProportions.getIn(["customersPerSourcesCount", "top"], "auto"),
width: chartsProportions.getIn(["customersPerSourcesCount", "width"], "100%"),
height: chartsProportions.getIn(["customersPerSourcesCount", "height"], "100%")
}}
>
How can I achieve that style from chartsProportions will be rendered before class based on expanded value is changed? I don't want to use setTimeout nor want to update all charts proportions onScroll event etc.
You just need to pass setState a function instead of an object, to ensure the state changes are applied in order:
this.setState(previousState => ({
previousChange: "value"
}))
this.setState(previousState => ({
afterPreviousChange: previousState.previousChange
}))
https://reactjs.org/docs/react-component.html#setstate
Another option might be to pass a callback to setState that runs after the state changes have been applied, like:
this.setState({ someChange: "value" }, () => this.setState({
otherChange: "value"
}))
CSS transitions could help with this too.
Using React state to animate properties is not the right way to do it. State updates will always get batched and you generally don't want to re-render your entire component 60 times per second
Store 'expanded' boolean in your state, and change element's class accordingly. Use css to add animations between two states
handleClick = () => {
this.setState({ expanded: !this.state.expanded })
}
render() {
return (
<div
className={`box ${this.state.expanded ? 'expanded' : ''}`}
onCLick={this.handleClick}
/>
)
}
in your css
.box {
width: 100px;
height: 100px;
transition: width 2s;
}
.expanded {
width: 300px;
}
added based on comments:
What you want to do is:
set position: fixed to your element. This would snap it to the top of the screen instantly, so you need to pick the right top and left values so that position fixed starts off where it was when position was static (default). For that you can use element.getBoundingClientRect()
calculate desired top and left attributes that would make your element appear in the middle of a screen, and apply them
very important: between step 1 and 2 browser has to render the page to apply position and initial top and left values, in order to have something to start animation from. It won't be able to do that if we apply both of these styles synchronously one after another, as page will not render until JS stack frame is clear. Wrap stage 2 logic in setTimeout which will make sure that browser renders at least once with styles applied at stage 1
rough working example:
class Example extends React.Component {
constructor(props) {
super(props)
this.state = {
expanded: false,
style: {}
}
}
handleClick = (e) => {
if (!this.state.expanded) {
const r = e.target.getBoundingClientRect()
const style = {
top: r.y,
left: r.x,
}
this.setState({
expanded: !this.state.expanded,
style
})
setTimeout(() => {
this.setState({
style: {
top: (window.innerHeight / 2) - 50,
left: (window.innerWidth / 2) - 50,
}
})
})
} else {
this.setState({
expanded: false,
style: {}
})
}
}
render() {
return (
<div className={'container'}>
<div className={'empty'} />
<div className={'empty'} />
<div className={'empty'} />
<div
onClick={this.handleClick}
className={`box ${this.state.expanded ? 'expanded' : ''}`}
style={this.state.style}
/>
</div>
)
}
}
and styles.css
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
.container {
height: 200vh;
}
.empty {
height: 100px;
width: 100px;
border: 1px solid gray;
}
.box {
height: 100px;
width: 100px;
border: 3px solid red;
transition: all 0.5s;
}
.expanded {
position: fixed;
}

Categories

Resources