Skip to content
Merged
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project are documented in this file.

This project follows Semantic Versioning.

## [0.1.6] - 2026-02-16

### Added

- Added viewport-based start with `startOnView` using `IntersectionObserver`.
- Added observer options: `once`, `root`, `rootMargin`, and `threshold`.

## [0.1.4] - 2026-02-16

### Fixed
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ const counters = counterUp(".metric", {
counters.update([100, 250, 999]);
```

### ESM (iniciar ao entrar na tela)

```js
import { counterUp } from "@nullsablex/counter-up";

counterUp(".metric", {
end: 1500,
startOnView: true,
once: true,
threshold: 0.2,
});
```

### Navegador (UMD)

```html
Expand Down Expand Up @@ -94,6 +107,11 @@ counters.update([100, 250, 999]);
- `easing` (`"linear"` | `"easeInOutQuad"` | `"easeOutCubic"` | function)
- `formatter` (function)
- `autostart` (boolean, padrão `true`)
- `startOnView` (boolean, padrão `false`): inicia quando o elemento entra na viewport
- `once` (boolean, padrão `true`): com `startOnView`, anima apenas uma vez
- `root` (Element|null, padrão `null`): root do `IntersectionObserver`
- `rootMargin` (string, padrão `"0px"`): margem do `IntersectionObserver`
- `threshold` (number|number[], padrão `0.1`): threshold do `IntersectionObserver`
- `onUpdate` (function)
- `onComplete` (function)

Expand Down
69 changes: 67 additions & 2 deletions dist/counterup.esm.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* @nullsablex/counter-up v0.1.5 | Author: NullSablex | git+https://github.com/NullSablex/counter-up.git | MIT License */
/* @nullsablex/counter-up v0.1.7 | Author: NullSablex | git+https://github.com/NullSablex/counter-up.git | MIT License */
const easings = {
linear: (t) => t,
easeInOutQuad: (t) => (t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2),
Expand All @@ -17,6 +17,11 @@ const defaultOptions = {
easing: "easeOutCubic",
formatter: null,
autostart: true,
startOnView: false,
once: true,
root: null,
rootMargin: "0px",
threshold: 0.1,
onUpdate: null,
onComplete: null,
};
Expand Down Expand Up @@ -107,6 +112,8 @@ function createCounterInstance(element, userOptions = {}, index = 0) {
isRunning: false,
isPaused: false,
destroyed: false,
hasPlayed: false,
observer: null,
};

function render(value, notify = true) {
Expand All @@ -124,6 +131,13 @@ function createCounterInstance(element, userOptions = {}, index = 0) {
}
}

function disconnectObserver() {
if (state.observer) {
state.observer.disconnect();
state.observer = null;
}
}

function animate(timestamp) {
if (!state.isRunning || state.destroyed) return;

Expand Down Expand Up @@ -163,6 +177,7 @@ function createCounterInstance(element, userOptions = {}, index = 0) {
state.startTime = null;
state.isPaused = false;
state.isRunning = true;
state.hasPlayed = true;
state.rafId = requestAnimationFrame(animate);
return api;
}
Expand All @@ -177,6 +192,10 @@ function createCounterInstance(element, userOptions = {}, index = 0) {
}

function start() {
if (state.destroyed) return api;
if (options.startOnView && options.once) {
disconnectObserver();
}
if (state.isPaused) {
return resume();
}
Expand Down Expand Up @@ -223,6 +242,7 @@ function createCounterInstance(element, userOptions = {}, index = 0) {

function update(nextEnd, nextOptions = {}) {
if (state.destroyed) return api;
disconnectObserver();
options = normalizeOptions({
...options,
...nextOptions,
Expand All @@ -237,9 +257,52 @@ function createCounterInstance(element, userOptions = {}, index = 0) {

function destroy() {
stop();
disconnectObserver();
state.destroyed = true;
}

function setupObserver() {
if (
!options.startOnView ||
!options.autostart ||
typeof IntersectionObserver === "undefined"
) {
return;
}

disconnectObserver();
state.observer = new IntersectionObserver(
(entries) => {
const entry = entries[0];
if (!entry) return;

if (entry.isIntersecting) {
if (options.once && state.hasPlayed) {
return;
}
render(options.start, false);
play(options.start, options.end);
if (options.once) {
disconnectObserver();
}
return;
}

if (!options.once) {
stop();
render(options.start, false);
}
},
{
root: options.root,
rootMargin: options.rootMargin,
threshold: options.threshold,
}
);

state.observer.observe(element);
}

const api = {
start,
stop,
Expand All @@ -261,7 +324,9 @@ function createCounterInstance(element, userOptions = {}, index = 0) {
};

render(options.start, false);
if (options.autostart) {
if (options.startOnView) {
setupObserver();
} else if (options.autostart) {
start();
}

Expand Down
6 changes: 3 additions & 3 deletions dist/counterup.esm.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading