feat: implement Countup component with initialization, update, and disposal methods

This commit is contained in:
codecalm
2026-01-10 23:12:50 +01:00
parent ebcfa18060
commit 4ef9dbde51
3 changed files with 186 additions and 18 deletions

View File

@@ -1,10 +1,56 @@
const countupElements: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>('[data-countup]')
import { CountUp } from 'countup.js'
if (countupElements.length) {
countupElements.forEach(function (element: HTMLElement) {
/**
* --------------------------------------------------------------------------
* Tabler countup.js
* --------------------------------------------------------------------------
*/
/**
* Constants
*/
const NAME = 'countup'
const DATA_KEY = `tblr.${NAME}`
const SELECTOR_DATA_COUNTUP = '[data-countup]'
/**
* Class definition
*/
class Countup {
private element: HTMLElement
private countUpInstance: CountUp | null = null
private initialized: boolean = false
private options: Record<string, any> = {}
constructor(element: HTMLElement) {
this.element = element
}
// Getters
static get NAME() {
return NAME
}
static get DATA_KEY() {
return DATA_KEY
}
// Public
/**
* Initialize countup on the element
*/
init(): void {
if (this.initialized) {
return
}
// Parse options from data attribute
let options: Record<string, any> = {}
try {
const dataOptions = element.getAttribute('data-countup') ? JSON.parse(element.getAttribute('data-countup')!) : {}
const dataOptions = this.element.getAttribute('data-countup') ? JSON.parse(this.element.getAttribute('data-countup')!) : {}
options = Object.assign(
{
enableScrollSpy: true,
@@ -15,13 +61,79 @@ if (countupElements.length) {
// ignore invalid JSON
}
const value = parseInt(element.innerHTML, 10)
this.options = options
if (window.countUp && window.countUp.CountUp) {
const countUp = new window.countUp.CountUp(element, value, options)
if (!countUp.error) {
countUp.start()
const value = parseInt(this.element.innerHTML, 10)
if (!isNaN(value)) {
this.countUpInstance = new CountUp(this.element, value, options)
if (!this.countUpInstance.error) {
this.countUpInstance.start()
this.initialized = true
}
}
})
}
/**
* Update countup (restart animation)
*/
update(): void {
if (!this.initialized || !this.countUpInstance) {
return
}
const value = parseInt(this.element.innerHTML, 10)
if (!isNaN(value)) {
this.countUpInstance = new CountUp(this.element, value, this.options)
if (!this.countUpInstance.error) {
this.countUpInstance.start()
}
}
}
/**
* Destroy countup instance
*/
dispose(): void {
if (!this.initialized) {
return
}
// CountUp doesn't have a destroy method, so we just reset state
this.countUpInstance = null
this.initialized = false
}
// Static
/**
* Get instance from element
*/
static getInstance(element: HTMLElement): Countup | null {
return elementMap.get(element) || null
}
/**
* Get or create instance
*/
static getOrCreateInstance(element: HTMLElement): Countup {
return this.getInstance(element) || new this(element)
}
}
/**
* Instance storage
*/
const elementMap = new WeakMap<HTMLElement, Countup>()
/**
* Data API implementation
*/
const countupTriggerList: HTMLElement[] = [].slice.call(document.querySelectorAll<HTMLElement>(SELECTOR_DATA_COUNTUP))
countupTriggerList.map(function (countupTriggerEl: HTMLElement) {
const instance = Countup.getOrCreateInstance(countupTriggerEl)
elementMap.set(countupTriggerEl, instance)
instance.init()
return instance
})
export default Countup

View File

@@ -1,10 +1,7 @@
import './src/autosize'
import './src/countup'
import './src/input-mask'
import './src/dropdown'
import './src/tooltip'
import './src/popover'
import './src/switch-icon'
import './src/tab'
import './src/toast'
import './src/sortable'
@@ -18,6 +15,7 @@ export { Alert, Button, Carousel, Collapse, Dropdown, Modal, Offcanvas, Popover,
// Export custom Tabler components
export { default as Autosize } from './src/autosize'
export { default as SwitchIcon } from './src/switch-icon'
export { default as Countup } from './src/countup'
// Re-export everything as namespace for backward compatibility
export * as bootstrap from 'bootstrap'

View File

@@ -1,19 +1,16 @@
---
title: Countup
summary: A countup element is used to display numerical data in an interesting way and make the interface more interactive.
docs-libs: countup
description: Display numbers dynamically with countups.
---
The countup component is used to display numbers dynamically. It is a great way to make the interface more interactive and engaging. The countup component is a simple and easy way to animate numbers in your application.
To be able to use the countup in your application you will need to install the countup.js dependency with `npm install countup.js`.
The countup component is built into Tabler and works similar to Bootstrap components. It animates numbers dynamically, making the interface more interactive and engaging.
For more advanced features of countups, see the demo on the [countUp.js website](https://inorganik.github.io/countUp.js/).
## Basic usage
To create a countup, add `data-countup` to any HTML text tag and specify the number which is to be reached. The animation will be triggered as soon as the number enters the viewport.
The easiest way to use countup is through the Data API. Add the `data-countup` attribute to any HTML text element and specify the number which is to be reached. The animation will be triggered as soon as the number enters the viewport.
```html
<h1 data-countup>30000</h1>
@@ -120,3 +117,64 @@ Set the countup suffix using `suffix` and specifying the suffix you want to add,
<h1 data-countup='{"suffix":"‰"}'>300</h1>
{%- endcapture %}
{% include "docs/example.html" html=html vertical separated %}
## Usage
### Via data attributes
Add `data-countup` to any HTML text element to automatically initialize countup. You can pass options as JSON:
```html
<h1 data-countup>30000</h1>
<h1 data-countup='{"duration":4,"prefix":"$"}'>30000</h1>
```
### Via JavaScript
Initialize countup programmatically using the `Countup` class:
```javascript
import { Countup } from '@tabler/core'
// Get or create instance
const element = document.querySelector('[data-countup]')
const countup = Countup.getOrCreateInstance(element)
countup.init()
```
### Methods
| Method | Description |
| --- | --- |
| `init()` | Initialize countup on the element. |
| `update()` | Update countup when the target value changes programmatically. |
| `dispose()` | Destroy countup instance. |
| `getInstance(element)` | *Static* method which allows you to get the countup instance associated with a DOM element. |
| `getOrCreateInstance(element)` | *Static* method which allows you to get the countup instance associated with a DOM element, or create a new one in case it wasn't initialized. |
#### Example: Update after value change
```javascript
import { Countup } from '@tabler/core'
const element = document.querySelector('[data-countup]')
const countup = Countup.getOrCreateInstance(element)
countup.init()
// Later, when the target value changes programmatically
element.innerHTML = '50000'
countup.update()
```
#### Example: Get existing instance
```javascript
import { Countup } from '@tabler/core'
const element = document.querySelector('[data-countup]')
const countup = Countup.getInstance(element)
if (countup) {
countup.update()
}
```