Custom Component: Counter

Yesterday we “vibe coded” a counter component with the @elementsbot here on the forum.

I spent a little extra time today tidying it up and making it more usable. In the project (included below) I also set up a couple of Templates to make it even easier to add to your own projects :tada:

Open this Project in Elements and watch the video below to see how it all works :grinning_face:

Oh, and here’s the final code that you can copy and paste into a custom component (if you want to build it yourself):

Template Code:

<div 
  class=""
  x-data="{ count: 0, hasAnimated: false }"
  x-intersect.once="if (!hasAnimated) { hasAnimated = true; $nextTick(() => { let start = 0; let end = +{{myNumber}}; let duration = 1500; let startTime = null; function animate(now) { if(!startTime) startTime = now; let progress = Math.min((now - startTime) / duration, 1); count = Math.floor(progress * (end - start) + start); if(progress < 1){ requestAnimationFrame(animate); } } requestAnimationFrame(animate); }); }"
>
  @if(edit)
    <div class="font-bold {{textColor}} {{headingTextStyles}} {{fontFamily}}">{{myNumber}}</div>
  @else
    <div class="font-bold {{textColor}} {{headingTextStyles}} {{fontFamily}}" x-text="count"></div>
  @endif
</div>

Properties Code:

{
  "groups": [
    {
      "title": "General",
      "icon": "gearshape",
      "properties": [
        {
          "title": "Number",
          "id": "myNumber",
          "number": {
            "default": 100
          }
        },
        {
          "title": "Font",
          "id": "fontFamily",
          "themeFont": {
            "default": {
              "base": { "name": "body" }
            }
          }
        },
        {
          "title": "Size",
          "id": "headingTextStyles",
          "themeTextStyle": {
            "default": {
              "base": {
                "name": "5xl"
              }
            }
          }
        },
        {
          "title": "Color",
          "id": "textColor",
          "format": "text-{{value}}",
          "themeColor": {
            "default": {
              "name": "text",
              "brightness": 300
            }
          }
        }
      ]
    }
  ]
}