如何为Angular/Tailwind点状进度条添加滑动着色动画
Angular + Tailwind 圆点进度条滑动动画实现方案
需求回顾
基于Angular和Tailwind开发圆点式进度/音量条:
- 圆点总数对应百分比(如20个点=0-100%,每步5%),全灰为0%,全蓝为100%
- 点击进度条时,着色部分需平滑滑动到点击位置
- 曾尝试
clip-path实现,未达成预期效果
实现方案:双层圆点叠加+滑动遮罩
放弃clip-path的复杂路径计算,改用底层彩色圆点+上层灰色滑动遮罩的方案,通过CSS过渡实现平滑动画:
步骤1:组件TS代码修改
新增动画相关计算逻辑,监听value变化并计算滑动偏移量:
import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; export class ProgressBarComponent implements OnInit, OnChanges { @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; public dotTotalWidth = 0; // 单个圆点总宽度(尺寸+间距) public animatedOffset = 0; // 滑动遮罩的偏移量 ngOnInit(): void { this.calculateDotSize(); this.calculateOffset(); } ngOnChanges(changes: SimpleChanges): void { if (changes['value']) { this.calculateOffset(); } if (changes['dotSize'] || changes['dotSpacing']) { this.calculateDotSize(); this.calculateOffset(); } } private calculateDotSize(): 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; } this.dotTotalWidth = this.dotPixels + this.dotSpacing; } private calculateOffset(): void { // 计算已激活的圆点数量 const activeDotCount = Math.floor(this.value / this.step); // 计算遮罩需要滑动的偏移量:总宽度 - 已激活圆点的总宽度 this.animatedOffset = (this.dots.length - activeDotCount) * this.dotTotalWidth; } public onDotClick(value: number) { this.valueChange.emit(value); } }
步骤2:模板HTML修改
重构结构为双层圆点+滑动遮罩,添加CSS过渡动画:
<div class="progress-bar-wrapper relative"> <!-- 底层:全彩色圆点 --> <div class="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="fill-accent" [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> <!-- 上层:灰色滑动遮罩(带overflow:hidden) --> <div class="absolute top-0 left-0 flex items-center transition-transform duration-300 ease-in-out" [style.transform]="'translateX(' + animatedOffset + 'px)'" [style.width]="'calc(100% - ' + animatedOffset + 'px)'" style="overflow: hidden;" > <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="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> </div>
关键实现说明
- 双层结构逻辑:
- 底层固定显示全彩色圆点,作为进度条的着色部分
- 上层灰色圆点容器通过
transform: translateX()滑动,遮住底层未激活的彩色圆点,形成"已激活区域"的视觉效果
- 偏移量计算:
- 根据当前value算出已激活的圆点数量,进而计算遮罩需要滑动的距离,确保滑动后刚好露出对应数量的彩色圆点
- 平滑动画:
- 给上层容器添加Tailwind的
transition-transform duration-300 ease-in-out类,实现0.3秒的平滑过渡效果
- 给上层容器添加Tailwind的
效果展示

内容的提问来源于stack exchange,提问作者Michael Stowe
相关产品推荐
相关产品推荐

