Skip to content

Commit a39283e

Browse files
authored
Animate when target element is scrolled into view (#273)
* scroll spy! * allow for multiple scroll-enabled instances * log error when using scroll spy * tweaks
1 parent 361b999 commit a39283e

File tree

9 files changed

+132
-15
lines changed

9 files changed

+132
-15
lines changed

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ CountUp.js supports all browsers. MIT license.
2727

2828

2929
## Features
30+
- **Animate when element scrolls into view** - new in v2.1.0. Use option `enableScrollSpy`.
3031
- **Highly customizeable** with a large range of options, you can even substitute numerals.
3132
- **Smart easing**: CountUp intelligently defers easing until it gets close enough to the end value for easing to be visually noticeable. Configureable in the [options](#options).
3233
- **Separate bundles** for modern and legacy browsers, with and without the requestAnimationFrame polyfill. Choose `countUp.min.js` for modern browsers or `countUp.withPolyfill.min.js` for IE9 and older, and Opera mini.
@@ -59,6 +60,8 @@ interface CountUpOptions {
5960
prefix?: string; // text prepended to result
6061
suffix?: string; // text appended to result
6162
numerals?: string[]; // numeral glyph substitution
63+
enableScrollSpy?: boolean; // start animation when target is in view
64+
scrollSpyDelay?: number; // delay (ms) after target comes into view
6265
}
6366
```
6467

@@ -106,11 +109,28 @@ Update the end value and animate:
106109
```js
107110
countUp.update(989);
108111
```
112+
113+
### Animate when the element is scrolled into view
114+
115+
Use the scroll spy option to animate when the element is scrolled into view. When using scroll spy, just initialize CountUp but do not call start();
116+
117+
```js
118+
const countUp = new CountUp('targetId', 989, { enableScrollSpy: true });
119+
```
120+
121+
**Troubleshooting scroll spy**
122+
123+
CountUp checks the scroll position as soon as it's initialized. So if you initialize it before the DOM renders and your target element is in view before any scrolling, you'll need to re-check the scroll position after the page renders:
124+
125+
```js
126+
// after DOM has rendered
127+
countUp.handleScroll();
128+
```
109129
---
110130

111131
## Including CountUp
112132

113-
CountUp v2 is distributed as an ES6 module because it is the most standardized and most widely compatible module for browsers, though a UMD module is [also included](#umd-module).
133+
CountUp is distributed as an ES6 module because it is the most standardized and most widely compatible module for browsers, though a UMD module is [also included](#umd-module).
114134

115135
For the examples below, first install CountUp. This will give you the latest:
116136
```

dist/countUp.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ export interface CountUpOptions {
1313
prefix?: string;
1414
suffix?: string;
1515
numerals?: string[];
16+
enableScrollSpy?: boolean;
17+
scrollSpyDelay?: number;
1618
}
1719
export declare class CountUp {
18-
private target;
1920
private endVal;
2021
options?: CountUpOptions;
2122
version: string;
@@ -36,6 +37,7 @@ export declare class CountUp {
3637
paused: boolean;
3738
frameVal: number;
3839
constructor(target: string | HTMLElement | HTMLInputElement, endVal: number, options?: CountUpOptions);
40+
handleScroll(self: CountUp): void;
3941
private determineDirectionAndSmartEasing;
4042
start(callback?: (args?: any) => any): void;
4143
pauseResume(): void;

dist/countUp.js

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ var __assign = (this && this.__assign) || function () {
1313
var CountUp = /** @class */ (function () {
1414
function CountUp(target, endVal, options) {
1515
var _this = this;
16-
this.target = target;
1716
this.endVal = endVal;
1817
this.options = options;
19-
this.version = '2.0.8';
18+
this.version = '2.1.0';
2019
this.defaults = {
2120
startVal: 0,
2221
decimalPlaces: 0,
@@ -28,7 +27,9 @@ var CountUp = /** @class */ (function () {
2827
separator: ',',
2928
decimal: '.',
3029
prefix: '',
31-
suffix: ''
30+
suffix: '',
31+
enableScrollSpy: false,
32+
scrollSpyDelay: 200,
3233
};
3334
this.finalEndVal = null; // for smart easing
3435
this.useEasing = true;
@@ -135,7 +136,37 @@ var CountUp = /** @class */ (function () {
135136
else {
136137
this.error = '[CountUp] target is null or undefined';
137138
}
139+
// scroll spy
140+
if (window !== undefined && this.options.enableScrollSpy) {
141+
if (!this.error) {
142+
// set up global array of onscroll functions
143+
window['onScrollFns'] = window['onScrollFns'] || [];
144+
window['onScrollFns'].push(function () { return _this.handleScroll(_this); });
145+
window.onscroll = function () {
146+
window['onScrollFns'].forEach(function (fn) { return fn(); });
147+
};
148+
this.handleScroll(this);
149+
}
150+
else {
151+
console.error(this.error, target);
152+
}
153+
}
138154
}
155+
CountUp.prototype.handleScroll = function (self) {
156+
if (!self || !window)
157+
return;
158+
var bottomOfScroll = window.innerHeight + window.scrollY;
159+
var bottomOfEl = self.el.offsetTop + self.el.offsetHeight;
160+
if (bottomOfEl < bottomOfScroll && bottomOfEl > window.scrollY && self.paused) {
161+
// in view
162+
self.paused = false;
163+
setTimeout(function () { return self.start(); }, self.options.scrollSpyDelay);
164+
}
165+
else if (window.scrollY > bottomOfEl && !self.paused) {
166+
// scrolled past
167+
self.reset();
168+
}
169+
};
139170
// determines where easing starts and whether to count down or up
140171
CountUp.prototype.determineDirectionAndSmartEasing = function () {
141172
var end = (this.finalEndVal) ? this.finalEndVal : this.endVal;

dist/countUp.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/countUp.umd.js

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@
1919
var CountUp = /** @class */ (function () {
2020
function CountUp(target, endVal, options) {
2121
var _this = this;
22-
this.target = target;
2322
this.endVal = endVal;
2423
this.options = options;
25-
this.version = '2.0.8';
24+
this.version = '2.1.0';
2625
this.defaults = {
2726
startVal: 0,
2827
decimalPlaces: 0,
@@ -34,7 +33,9 @@
3433
separator: ',',
3534
decimal: '.',
3635
prefix: '',
37-
suffix: ''
36+
suffix: '',
37+
enableScrollSpy: false,
38+
scrollSpyDelay: 200,
3839
};
3940
this.finalEndVal = null; // for smart easing
4041
this.useEasing = true;
@@ -141,7 +142,37 @@
141142
else {
142143
this.error = '[CountUp] target is null or undefined';
143144
}
145+
// scroll spy
146+
if (window !== undefined && this.options.enableScrollSpy) {
147+
if (!this.error) {
148+
// set up global array of onscroll functions
149+
window['onScrollFns'] = window['onScrollFns'] || [];
150+
window['onScrollFns'].push(function () { return _this.handleScroll(_this); });
151+
window.onscroll = function () {
152+
window['onScrollFns'].forEach(function (fn) { return fn(); });
153+
};
154+
this.handleScroll(this);
155+
}
156+
else {
157+
console.error(this.error, target);
158+
}
159+
}
144160
}
161+
CountUp.prototype.handleScroll = function (self) {
162+
if (!self || !window)
163+
return;
164+
var bottomOfScroll = window.innerHeight + window.scrollY;
165+
var bottomOfEl = self.el.offsetTop + self.el.offsetHeight;
166+
if (bottomOfEl < bottomOfScroll && bottomOfEl > window.scrollY && self.paused) {
167+
// in view
168+
self.paused = false;
169+
setTimeout(function () { return self.start(); }, self.options.scrollSpyDelay);
170+
}
171+
else if (window.scrollY > bottomOfEl && !self.paused) {
172+
// scrolled past
173+
self.reset();
174+
}
175+
};
145176
// determines where easing starts and whether to count down or up
146177
CountUp.prototype.determineDirectionAndSmartEasing = function () {
147178
var end = (this.finalEndVal) ? this.finalEndVal : this.endVal;

0 commit comments

Comments
 (0)