Search code examples
angulargsap

Angular TimelineLite time function doesn't work with OnInit


I try to implement a progress bar and a timer using TimelineLite:

HTML:

<div id="progress"></div>
{{timeline.time()}}

CSS:

#progress {
  width: 100%;
  height: 30px;
  background-color: red;
}

enter image description here

Here's what I want:
When the page loads, the progress bar starts animating from 100% to 0% width (10 sec. duration). Also, I want to show the result of the time function.
Here's what I did:

public timeline = new TimelineLite();

ngOnInit() {
  this.start();
}

start() {
  const progress = document.getElementById('progress');
  this.timeline.fromTo(progress, 10, {width: '100%'}, {width: 0, ease: Linear.easeNone});
}

So when the page loads, the progress bar works but the timer doesn't. I don't know why. If I try to set a timeout for 3 seconds at ngOnInit, it works:

ngOnInit() {
  setTimeout(() => this.start(), 3000);
}

Also if I create a button than invokes start function at click, it works:

<button (click)="start()">Start</button>

So the problem is that the timer doesn't work if I try to invoke fromTo function from ngOnInit.


Solution

  • Hi you should use @ViewChild to target your elements and use AfterViewInit lifeCycleHook when you want to manipulate | access to your DOMElement (current component or child component).

    export class AppComponent implements AfterViewInit {
    
      @ViewChild('progress') progressElement: ElementRef;
    
      constructor(private changeRef: ChangeDetectorRef) { } 
    
      public timeline = new TimelineLite();
      ngAfterViewInit() {
        this.start();
      }
    
      start() {
        this.timeline.eventCallback('onUpdate', () => {
          this.time = this.timeline.time();
          this.changeRef.detectChanges();
        }, [] );
    
        // Here i use the ViewChild Binding instead of pure javascript fetching.
        this.timeline.fromTo(this.progressElement.nativeElement, 10, {width: '100%'}, {width: 0, ease: Linear.easeNone});
      }
    }
    

    To facilitate your testing i have does Github Pull request. Anyone which read your original question can easily see the difference.


    Update 1 : on your view, you call a function which return the current timer from TimelineLite. Because is just function call, it will not be called again and again on each internal TimelineLite update.

    To do it, you have to use TimelineLite event binding.

    Now, by default your view will not be "updated" each time you update your attribute from following code :

    this.timeline.eventCallback('onUpdate', () => {
      // angular is not inform by default than something happen here.
     }, [] );
    

    is why, you should use ChangeDetectorRef to force angular to detect the freshest updated component attribute.

    pull requested updated