问题
I have a react single page app, with multiple components. For the 5th component(visible only when scrolled down) I have a counter . Now I am using react-countup library to achieve the counter function. However , the counter starts soon as the page is loaded . Is it possible for countup to begin once we scroll down to the component. Animation happens only once(which is good)after the page is loaded, but I would like the counter not to begin soon after the page is loaded, but when user scrolls down to the component the first time. My code looks like this:
render() {
return (
<div className={style.componentName}>
<h2>Heading</h2>
<div className={style.col}>
<div>My counter</div>
<CountUp className={style.countup} decimals={1} start={0} end={25} suffix=" %" duration={3} />
</div>
</div>)}
Updated code:
import CountUp, { startAnimation } from 'react-countup';
import VisibilitySensor from 'react-visibility-sensor';
class className extends Component {
state = {
scrollStatus: true
};
onVisibilityChange = isVisible => {
if (isVisible) {
if (this.state.scrollStatus) {
startAnimation(this.myCountUp);
this.setState({ scrollStatus: false });
}
}
}
render() {
return (
<div className={style.componentName}>
<h2>Heading</h2>
<VisibilitySensor onChange={this.onVisibilityChange} offset = {{ top:
10}} delayedCall>
<CountUp className={style.countup} decimals={1} start={0} end={25}
suffix=" %" duration={3} ref={countUp => { this.myCountUp= countUp;}}/>
</VisibilitySensor>
</div>)}
}
回答1:
Per React CountUp's README, you can use the startAnimation
hook to manually kick off the animation. Combine this with something like react-visibility-sensor, and you can wait to kick off the animation until it is visible in the user's browser.
import React, {Component} from 'react';
import CountUp, {startAnimation} from 'react-countup';
import './App.css';
import VisibilitySensor from 'react-visibility-sensor';
const style = {
componentName: {},
col: {},
countup: {},
};
class App extends Component {
constructor(props) {
super(props);
this.onVisibilityChange = this.onVisibilityChange.bind(this); // Bind for appropriate 'this' context
}
onVisibilityChange(isVisible) {
if (isVisible) {
startAnimation(this.myCountUp);
}
}
render() {
return (
<div className={style.componentName}>
<h2>Heading</h2>
<div className={style.col}>
<div>My counter</div>
<VisibilitySensor
onChange={this.onVisibilityChange}
delayedCall // Prevents react apps triggering elements as visible before styles are loaded
>
<CountUp className={style.countup} decimals={1} start={0} end={25} suffix=" %" duration={3}
ref={countUp => { this.myCountUp = countUp; }} // From react-countup README
/>
</VisibilitySensor>
</div>
</div>
);
}
}
export default App;
As is, it will startAnimation
every time you scroll to the countup. If you want to only do that once, just add a piece of state that gets set after the first render (and then prevent it from doing startAnimation
again based on that altered state).
Less elegant (not recommended) ways to accomplish the same effect might include:
- Use the built-in animation triggers (i.e. changing the props
duration
,end
,start
) by setting them equal to some state that changes when the user scrolls down - Leveraging the
onStart
prop, called before the animation starts, to delay starting the animation until the user scrolls down
EDIT: Update to address your second question
Unfortunately, it looks like the react-countup
library doesn't expose a way to prevent startAnimation
on startup.
But we can hack together a fairly elegant fix by manipulating the end
prop using state instead:
import React, {Component} from 'react';
import CountUp, {startAnimation} from 'react-countup';
import './App.css';
import VisibilitySensor from 'react-visibility-sensor';
const style = {
componentName: {},
col: {},
countup: {},
};
class App extends Component {
state = {
didViewCountUp: false
};
onVisibilityChange = isVisible => {
if (isVisible) {
this.setState({didViewCountUp: true});
}
}
render() {
return (
<div className={style.componentName}>
<h2 style={{fontSize: '40em'}}>Heading</h2>
<VisibilitySensor onChange={this.onVisibilityChange} offset={{
top:
10
}} delayedCall>
<CountUp className={style.countup} decimals={1} start={0} end={this.state.didViewCountUp ? 25 : 0}
suffix=" %" duration={3} />
</VisibilitySensor>
</div>)
}
}
export default App;
回答2:
The API may have changed since last year. I manage to make this work with this code now :
import React from "react";
import CountUp from "react-countup";
import VisibilitySensor from 'react-visibility-sensor';
const MyComponent = () => (
<>
<CountUp end={100} redraw={true}>
{({ countUpRef, start }) => (
<VisibilitySensor onChange={start} delayedCall>
<span ref={countUpRef} />
</VisibilitySensor>
)}
</CountUp>
</>
);
export default App;
I use this component inside a tab, so the redraw={true}
prop is only here to redraw the animation on tabChange.
回答3:
Here's my implementation. It just runs once and also doesn't re-render every time the component enters viewport to check visibility.
Dependencies:
1. react-countup v.4.3.2
2. react-visibility-sensor v.5.1.1
import React, { useState } from "react";
import CountUp from "react-countup";
import VisibilitySensor from "react-visibility-sensor";
const Ticker = ({ className, ...rest }) => {
const [viewPortEntered, setViewPortEntered] = useState(false);
return (
<CountUp {...rest} start={viewPortEntered ? null : 0}>
{({ countUpRef }) => {
return (
<VisibilitySensor
active={!viewPortEntered}
onChange={isVisible => {
if (isVisible) {
setViewPortEntered(true);
}
}}
delayedCall
>
<h4 className={className} ref={countUpRef} />
</VisibilitySensor>
);
}}
</CountUp>
);
};
export default Ticker;
Here's how to use it:
<Ticker className="count" end={21} suffix="M+" />
回答4:
The docs for that library have a way to manually start the counter. I would use that approach to start the counter once a user has scrolled to the required distance.
import React, { Component } from 'react';
import CountUp, { startAnimation } from 'react-countup';
const MyComponent = () => (
<div>
<CountUp className="CountUp" start={0} end={100} duration={3} ref={(countUp) => {
this.myCountUp = countUp;
}} />
<button className="Button" onClick={(event) => {
startAnimation(this.myCountUp);
}}>Count me up!</button>
</div>
);
export default App;
Link to Github. Read the README at the very bottom.
回答5:
you can have look at my functional component to achieve this
import React from "react";
import { Box } from "@material-ui/core";
import CountUp from "react-countup";
import VisibilitySensor from "react-visibility-sensor";
export default function Counter() {
const [focus, setFocus] = React.useState(false);
return (
<Box component="div">
<CountUp start={focus ? 0 : null} end={100} duration={5} redraw={true}>
{({ countUpRef }) => (
<div>
<span ref={countUpRef} />
<VisibilitySensor
onChange={isVisible => {
if (isVisible) {
setFocus(true);
}
}}
>
<a>+</a>
</VisibilitySensor>
</div>
)}
</CountUp>
</Box>
);
}
来源:https://stackoverflow.com/questions/51044090/react-countup-animation-starts-immediately-after-the-page-loading-should-start