I'm creating a progress/volume bar component with Angular/Tailwind where the percentage increments are colored circles. All gray circles means 0%, all blue means 100%, 10 out of 20 means 50%, etc.
I want to animate this so that the color portion slides to where the user clicks on the bar.
I experimented with clip-path
to try to make a series of "windows" where the circles are transparent sections revealing the color behind them, but I can't quite get it right.
Here's my code for the static version:
export class ProgressBarComponent implements OnInit {
@Input() min = 0;
@Input() max = 100;
@Input() value = 0;
@Input() step = 5;
@Input() dotSize: 'xs' | 'sm' | 'md' | 'lg' | 'xl' = 'lg';
@Input() dotSpacing = 0; // Gap between dots in pixels
@Output() valueChange = new EventEmitter<number>();
public dots = Array.from(
{ length: Math.floor(this.max / this.step) },
(_, i) => i * this.step + this.step,
);
public dotPixels = 0;
ngOnInit(): void {
switch (this.dotSize) {
case 'xs':
this.dotPixels = 6;
break;
case 'sm':
this.dotPixels = 8;
break;
case 'md':
this.dotPixels = 10;
break;
case 'lg':
this.dotPixels = 12;
break;
case 'xl':
this.dotPixels = 16;
break;
}
}
public onDotClick(value: number) {
this.valueChange.emit(value);
}
}
<div class="progress-bar-wrapper flex items-center">
<ng-container *ngFor="let dotVal of dots; let i = index">
<div
class="dot py-1 hover:cursor-pointer"
[style.padding-left]="dotSpacing / 2 + 'px'"
[style.padding-right]="dotSpacing / 2 + 'px'"
(click)="onDotClick(dotVal)"
>
<svg
class="{{
value >= dotVal || (i === 0 && value > min)
? 'fill-accent'
: 'fill-zinc-600'
}}"
[attr.width]="dotPixels + 'px'"
[attr.height]="dotPixels + 'px'"
>
<circle
[attr.cx]="dotPixels / 2"
[attr.cy]="dotPixels / 2"
[attr.r]="dotPixels / 2"
stroke-width="1"
/>
</svg>
</div>
</ng-container>
</div>
You can render two sets of dots: one as the background and one to indicate progress. The list of dots indicating progress can have its width
controlled to change the number of displayed dots. Additionally, since the width
can be transitioned, achieving an animation effect is also relatively easy.
<script src="https://cdn.tailwindcss.com/3.4.5"></script>
<div class="flex h-dvh items-center justify-center bg-zinc-900">
<article class="group/volume mx-auto">
<header class="flex justify-between text-lg font-semibold text-amber-100">
<h1>Volume</h1>
<div class="">35%</div>
</header>
<section class="relative mx-2 my-4">
<div class="flex gap-2 text-zinc-600">
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
<div class="h-4 w-4 rounded-full bg-current"></div>
</div>
<div class="absolute inset-0 flex gap-2 overflow-hidden text-blue-300 transition-[width] duration-1000 ease-out w-[35%] group-hover/volume:w-[70%]">
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
<div class="h-4 w-4 shrink-0 rounded-full bg-current"></div>
</div>
</section>
</article>
</div>
(👆Run the snippet and hover to see the effect)
You can also view this example on Tailwind Play
In my example, I use mouse hover to trigger the animation to avoid writing JavaScript. In your actual case, you can set the width
of the element based on the actual percentage value. For this type of dynamic change, it is recommended to set the style
of the element directly instead of using the Tailwind utility classes.