Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add onProgress to track the scroll progress between enter / leave #304

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ below) has changed.
/>
```

If you need more information over the scrolling progress you can use pass a function to the `onProgress` prop.
```js
<Waypoint
onProgress={this._handleProgress}
/>
```
It will fire on every position change inside the object. The information is passed
with the `progress` attribute that ranges from 0 - 1. Using this can result in a big performance hit,
as a lot of scrolling events are firing, only use this with caution.

Waypoints can take a child, allowing you to track when a section of content
enters or leaves the viewport. For details, see [Children](#children), below.

Expand Down Expand Up @@ -105,6 +115,12 @@ enters or leaves the viewport. For details, see [Children](#children), below.
*/
onLeave: PropTypes.func,

/**
* Function called when waypoint is in the viewport and the scroll position changed
*/
onProgress: PropTypes.func,


/**
* Function called when waypoint position changes
*/
Expand Down Expand Up @@ -165,7 +181,7 @@ enters or leaves the viewport. For details, see [Children](#children), below.
},
```

All callbacks (`onEnter`/`onLeave`/`onPositionChange`) receive an object as the
All callbacks (`onEnter`/`onLeave`/`onProgress`/`onPositionChange`) receive an object as the
only argument. That object has the following properties:

- `currentPosition` - the position that the waypoint has at the moment. One
Expand Down
13 changes: 13 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ declare namespace Waypoint {
viewportBottom: number;
}

interface CallbackProgressArgs extends CallbackArgs {
/*
* Indicates the progress between the enter and leave event with a number between 0 and 1.
*/
progress: number;
}

interface WaypointProps {
/**
* Function called when waypoint enters viewport
Expand All @@ -56,6 +63,12 @@ declare namespace Waypoint {
*/
onLeave?: (args: CallbackArgs) => void;

/**
* Function called when waypoint is inside viewport and a scroll event happens.
* @param {CallbackProgressArgs} args
*/
onProgress?: (args: CallbackProgressArgs) => void;

/**
* Function called when waypoint position changes
* @param {CallbackArgs} args
Expand Down
23 changes: 23 additions & 0 deletions src/getCurrentProgress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
function clamp(val, min, max) {
return Math.min(Math.max(val, min), max);
}

/**
* @param {object} bounds An object with bounds data for the waypoint and
* scrollable parent
* @return {integer} The current scroll progress of the Waypoint inside of its
* scrollable parent
*/
export default function getCurrentProgress({
viewportBottom,
viewportTop,
waypointBottom,
waypointTop,
}) {
const viewportHeight = viewportBottom - viewportTop || 1;
const waypointHeight = waypointBottom - waypointTop || 1;
const distance = viewportHeight + waypointHeight;
const traveled = waypointBottom - viewportTop;
const progress = 1 - clamp(traveled / distance, 0, 1);
return progress;
}
22 changes: 17 additions & 5 deletions src/waypoint.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import debugLog from './debugLog';
import ensureRefIsUsedByChild from './ensureRefIsUsedByChild';
import isDOMElement from './isDOMElement';
import getCurrentPosition from './getCurrentPosition';
import getCurrentProgress from './getCurrentProgress';
import onNextTick from './onNextTick';
import resolveScrollableAncestorProp from './resolveScrollableAncestorProp';

Expand All @@ -25,6 +26,7 @@ const defaultProps = {
horizontal: false,
onEnter() { },
onLeave() { },
onProgress() { },
onPositionChange() { },
fireOnRapidScroll: true,
};
Expand Down Expand Up @@ -187,6 +189,7 @@ export class Waypoint extends React.PureComponent {
onEnter,
onLeave,
fireOnRapidScroll,
onProgress,
} = this.props;

if (process.env.NODE_ENV !== 'production' && debug) {
Expand All @@ -197,11 +200,6 @@ export class Waypoint extends React.PureComponent {
// Save previous position as early as possible to prevent cycles
this._previousPosition = currentPosition;

if (previousPosition === currentPosition) {
// No change since last trigger
return;
}

const callbackArg = {
currentPosition,
previousPosition,
Expand All @@ -211,12 +209,25 @@ export class Waypoint extends React.PureComponent {
viewportTop: bounds.viewportTop,
viewportBottom: bounds.viewportBottom,
};

if (currentPosition === INSIDE) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can this be combined with the same if-statement a few lines down on 225?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried it out and it needs to be done before the previousPosition === currentPosition check, as these are then "inside" and "inside" and thus the onProgress wouldn't be called.

const progress = getCurrentProgress(bounds);
onProgress.call(this, Object.assign({}, callbackArg, { progress }));
}

if (previousPosition === currentPosition) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to make this check happen first?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately not, as inside === inside is always true and breaks the onProgress logic to fire an event on every scroll.

// No change since last trigger
return;
}

onPositionChange.call(this, callbackArg);

if (currentPosition === INSIDE) {
onEnter.call(this, callbackArg);
} else if (previousPosition === INSIDE) {
onLeave.call(this, callbackArg);
const progress = +(currentPosition === ABOVE);
onProgress.call(this, Object.assign({}, callbackArg, { progress }));
}

const isRapidScrollDown = previousPosition === BELOW
Expand Down Expand Up @@ -327,6 +338,7 @@ if (process.env.NODE_ENV !== 'production') {
onEnter: PropTypes.func,
onLeave: PropTypes.func,
onPositionChange: PropTypes.func,
onProgress: PropTypes.func,
fireOnRapidScroll: PropTypes.bool,
// eslint-disable-next-line react/forbid-prop-types
scrollableAncestor: PropTypes.any,
Expand Down