Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Odometer Easy Laravel Package

gsferro/odometer-easy

A Laravel package that simplifies adding animated odometer counters to Blade views, with easy configuration for values, formatting, and front-end integration.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package with Composer:

composer require gsferro/odometer-easy

This package is a thin wrapper around HubSpot's Odometer jQuery plugin, so expect it to publish the plugin's CSS/JS assets and expose a Blade-friendly way to render animated number counters.

Where to look first

  1. config/odometer.php (after php artisan vendor:publish --tag=odometer-config) — check defaults for themes, format, and asset paths.
  2. The package's src/ directory — locate the ServiceProvider and any Blade component/directive classes. That's your real API surface.
  3. The published assets under public/vendor/odometer-easy/ — confirm the JS/CSS actually landed there before assuming a Blade directive will work.

First use case

Publish assets and a config:

php artisan vendor:publish --provider="Gsferro\OdometerEasy\OdometerEasyServiceProvider"

Then render a counter in a Blade view — depending on the version you may have a directive, a component, or a helper. The most likely shapes are:

{{-- directive style --}}
@odometer($totalUsers)

{{-- component style --}}
<x-odometer :value="$totalUsers" />

If neither works, read the provider's boot() method — small packages like this one evolve quickly and the README may lag behind the shipped API. The underlying Odometer call you're ultimately generating is:

new Odometer({ el: element, value: 1234, format: '(,ddd)', theme: 'default' });

Implementation Patterns

Dashboard / KPI counters

The classic use case: a card that shows total revenue, users, orders. Combine with a Blade component:

<div class="stat">
    <span class="stat-value">
        @odometer($stats['revenue'], ['format' => '(,ddd).dd', 'theme' => 'car'])
    </span>
    <span class="stat-label">Revenue</span>
</div>

Updating counters after a fetch

Odometer animates transitions when you mutate innerHTML on an existing element. The pattern that works with any Odometer wrapper is to keep a reference to the DOM node and set its value after an AJAX call:

fetch('/api/stats')
    .then(r => r.json())
    .then(data => {
        document.querySelector('#revenue').innerHTML = data.revenue;
        // Odometer detects the write and animates from old -> new value
    });

If the package exposes a JS helper (e.g. window.OdometerEasy.set(el, value)), prefer that over raw innerHTML so it can re-apply format/theme correctly.

Blade + Livewire

Because Livewire swaps DOM nodes, Odometer instances can be lost on re-render. Two options:

  • Use wire:ignore around the counter and push updates via $dispatch or a small Alpine component that calls new Odometer(...) on x-init.
  • Or listen for Livewire's message.processed hook and reinitialize Odometer on the affected element.

Formats and themes

Odometer's built-in formats are the ones worth memorizing:

Format Example output
(,ddd) 1,234,567
(,ddd).dd 1,234,567.89
(.ddd),dd 1.234.567,89
( ddd),dd 1 234 567,89

Themes: default, car, digital, plaza, slot-machine, train-station. For an "easy" package, most of these are likely pass-through config in the directive's options array.

Integration tips

  • If you only need Odometer on one or two pages, push the published JS/CSS via @push('scripts') / @push('styles') rather than loading them globally. The plugin is small but there's no reason to ship it on marketing pages.
  • Pair with a server-side formatted value in the HTML so the counter shows something meaningful before JS runs (progressive enhancement). If the package renders the initial value server-side, good — verify it does.
  • For accessibility, wrap the animated number with an aria-label containing the static value, since screen readers can't track the animation.

Gotchas and Tips

Asset publishing gotcha

Many small packages forget to include public/vendor/... in their publishes() call, or only publish on vendor:publish --force. If you get a 404 on odometer.min.js, check the provider's publishes() array and re-run with --force. You can also point a <script src> directly at the CDN as a workaround (https://cdnjs.cloudflare.com/ajax/libs/odometer/0.4.8/odometer.min.js).

jQuery dependency

Odometer depends on jQuery (or a jQuery-compatible shim). If your app runs Vite + Alpine and no jQuery, you'll need to either:

  • Add jQuery to your bundle, or
  • Use the standalone build from HubSpot which bundles jQuery internally (check which one the package ships).

This is the single most common failure mode — the counter silently stays at its initial value because $ is undefined.

Re-initialization is cheap, mutation resets are not

Calling odometer.innerHTML = x on an existing instance animates. Calling element.innerHTML = x where the element is a new node (e.g. after a Livewire re-render) doesn't. If numbers "jump" instead of animating, you're working against a fresh node — reinitialize.

Format strings are literal

'(,ddd)' is not a number_format equivalent. Passing a PHP number_format() string won't translate; you have to emit the Odometer format token. If the package has a config key for format, set it once globally rather than per-call — the plugin only supports one format per instance.

Value types

Odometer expects a number or numeric string. Passing a PHP decimal cast that serializes to a string with trailing zeros is fine, but passing a formatted string with commas will break the animation (it parses with parseFloat). Keep the raw value in the HTML/data attribute and let the format option do the display work.

Debugging checklist

  1. Browser console — is Odometer defined? Is jQuery defined?
  2. Network tab — did the JS/CSS actually load (200, not 404)?
  3. Inspect the element — did Odometer insert its <span class="odometer-digit"> children? If yes, the plugin ran; the value is the problem. If no, initialization never fired.
  4. The duration option defaults to 2000ms. If you set a value and see nothing for a moment, wait — it's animating.

Extension points

Because the package is thin, the practical extension route is: use it to render the markup, then take over initialization yourself with vanilla new Odometer(...). That lets you control duration, theme, and format without waiting for the wrapper to expose options. Keep the package for the Blade ergonomics and asset publishing; drop to raw Odometer the moment you need more than the wrapper exposes.

Version pinning

With a score this low and a single GitHub star, pin the exact version in composer.json and commit the published assets. A stray composer update can drop a breaking provider rename onto a working dashboard.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route