Nils
Nils

Reputation: 469

Is there a 'smoother' way to code a progressbar?

im trying to add a progressbar but sometimes it bugs out and fills too slow or too fast, most of the time it works fine though.

I also think that there must be a better way to programm this since mine does way to many calculations for a single progressbar i think.

Here is my Code:

ts:

startProgressbar(timeToWait: number) {
this.progress = 0;
const interval = setInterval(() => {
  let addvalue = (1 / timeToWait * 100) / 64;
  this.progress += addvalue;
  if (this.progress >= 100) {
    this.progress = 100;
    window.clearInterval(interval);
  }
}, 15.625); }

The function gets called again as soon as its over since there will be the next slide of the slideshow directly after.

The progress value resembles the width of the progressbar, its always a percentage.

Where does the number 15.625 come from? Its simply a 64th of a second so the progressbar updates 64 times a second giving the illusion of moving smoothly.

My html is pretty basic:

<div class="progressbar" [ngStyle]="{'width': progress+'%'}"></div>

I hope you have a better approach than i did. Thanks in advance

[EDIT]: What i mean by it bugging out is that the progressbar sometimes starts filling late and therefor doesnt reach the end (100%) before the next slide appears and then the next ones timing is offset too. Sometimes it starts early and then reaches the end (100%) too quickly so it stays at 100% for a while until the next slide appears.

Upvotes: 3

Views: 2026

Answers (2)

Eliseo
Eliseo

Reputation: 57939

why not use Rxjs timer?

interval(timeToWait):Observable<number>
  {
    let progress = 0;
    return timer(0, timeToWait)
      .pipe(
        takeWhile(() => progress <= 100),
        map(()=>progress<100?progress:100),
        tap(() => (progress += 10))
      )
  }

Use

   this.interval(200).subscribe((res) => {
        console.log(res);
      });

Update add stackblitz: stackblitz

Update 2 thanks to @Ritaj, timer return a stream (0,1,2,3,4,...), so we can use simple

 return timer(0, timeToWait)
  .pipe(
    map((progress)=>progress*5<100?progress*5:100),
    takeWhile((progress) => progress <= 100)
  )

Update 3 thanks to this answer of SO, we can included the last value whe we are using takeWhile, so, if is important the timeToWait we can replace the function interval by

 interval(timeToWait) {
    const initial = new Date().getTime();
    return timer(0, 200).pipe(
      map(()=> new Date().getTime()),
      takeWhile((res) => res<=initial+timeToWait,true),
      map(now=>{
        const porc = (100 * (now - initial)) / timeToWait;
        return porc<100?Math.round(porc):100
      })
    );

Upvotes: 5

igg
igg

Reputation: 2250

I recommend following the Javascript best practice for waiting. Here is a stackblitz I've prepared with example code for a progress bar that uses the recommended practices.

export class AppComponent  {
  name = 'Angular';
  public progress = 0;
  public timeToWait: number = 1;

  private default_ms = 8.33; // 120 times a second.
  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  public async startProgressBar(): Promise<void> {
    this.progress = 0;
    while(this.progress <= 100) {
      this.progress += (1 / this.timeToWait);
      await this.sleep(this.default_ms);
    }
  }
}

I can't say for sure why your solution "bugs out". Calling window.clearInterval inside the interval function seems like bad practice, but I can't be certain. Let me know if this solution works for you.

Upvotes: 1

Related Questions