UI: Simplify Animation class

The Animation class had too much complexity and IMHO too hard to read:
- The running state is superfluous, it can be inferred by the
  start/end times. Superfluous state is unnecessary.
- Nothing was using the default 0 argument for start()
- The raf being an inner lambda was quite hard to follow.
- performance.now() is preferrable for animations vs Date.now()
  That is what is passed as argument to RAF.

Change-Id: Idfca10e751f6a11ee5bb57d24b2e48a807aada81
diff --git a/ui/src/frontend/animation.ts b/ui/src/frontend/animation.ts
index c91ad99..040d936 100644
--- a/ui/src/frontend/animation.ts
+++ b/ui/src/frontend/animation.ts
@@ -13,54 +13,41 @@
 // limitations under the License.
 
 export class Animation {
-  private running = false;
-  private runningStartedMs = 0;
-  private end = Infinity;
-  private requestedAnimationFrame = 0;
+  private startMs = 0;
+  private endMs = 0;
+  private lastFrameMs = 0;
+  private rafId = 0;
 
   constructor(private onAnimationStep: (timeSinceLastMs: number) => void) {}
 
-  start(durationMs?: number) {
-    if (durationMs !== undefined) {
-      this.end = Date.now() + durationMs;
+  start(durationMs: number) {
+    const nowMs = performance.now();
+
+    // If the animation is already happening, just update its end time.
+    if (nowMs <= this.endMs) {
+      this.endMs = nowMs + durationMs;
+      return;
     }
-    this.run();
+    this.lastFrameMs = 0;
+    this.startMs = nowMs;
+    this.endMs = nowMs + durationMs;
+    this.rafId = requestAnimationFrame(this.onAnimationFrame.bind(this));
   }
 
   stop() {
-    this.running = false;
-    cancelAnimationFrame(this.requestedAnimationFrame);
+    this.endMs = 0;
+    cancelAnimationFrame(this.rafId);
   }
 
-  getStartTimeMs(): number {
-    return this.runningStartedMs;
+  get startTimeMs(): number {
+    return this.startMs;
   }
 
-  private run() {
-    if (this.running) {
-      return;
+  private onAnimationFrame(nowMs: number) {
+    if (nowMs < this.endMs) {
+      this.rafId = requestAnimationFrame(this.onAnimationFrame.bind(this));
     }
-    let lastFrameTimeMs = 0;
-
-    const raf = (timestampMs: number) => {
-      if (!lastFrameTimeMs) {
-        lastFrameTimeMs = timestampMs;
-      }
-      this.onAnimationStep(timestampMs - lastFrameTimeMs);
-      lastFrameTimeMs = timestampMs;
-
-      if (this.running) {
-        if (Date.now() < this.end) {
-          this.requestedAnimationFrame = requestAnimationFrame(raf);
-        } else {
-          this.running = false;
-        }
-      }
-    };
-
-    this.running = true;
-    this.runningStartedMs = Date.now();
-
-    requestAnimationFrame(raf);
+    this.onAnimationStep(nowMs - (this.lastFrameMs || nowMs));
+    this.lastFrameMs = nowMs;
   }
-}
\ No newline at end of file
+}