diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts
index 156219dead421927c46c3de787dc0a2740423850..5169f3f294581527fd08c255efce39de6923e58b 100644
--- a/src/mol-canvas3d/canvas3d.ts
+++ b/src/mol-canvas3d/canvas3d.ts
@@ -192,7 +192,7 @@ namespace Canvas3D {
             if (isIdentifying || isUpdating) return false
 
             let didRender = false
-            controls.update()
+            controls.update(currentTime);
             // TODO: is this a good fix? Also, setClipping does not work if the user has manually set a clipping plane.
             if (!camera.transition.inTransition) setClipping();
             const cameraChanged = camera.updateMatrices();
@@ -230,6 +230,7 @@ namespace Canvas3D {
         }
 
         let forceNextDraw = false;
+        let currentTime = 0;
 
         function draw(force?: boolean) {
             if (render('draw', !!force || forceNextDraw)) {
@@ -246,8 +247,8 @@ namespace Canvas3D {
         }
 
         function animate() {
-            const t = now();
-            camera.transition.tick(t);
+            currentTime = now();
+            camera.transition.tick(currentTime);
             draw(false)
             window.requestAnimationFrame(animate)
         }
diff --git a/src/mol-canvas3d/controls/trackball.ts b/src/mol-canvas3d/controls/trackball.ts
index 63234d324de095b95e09cd11ad07bd13354d4d95..b2e01c66d3206c082d0cbdd20b0da58f10eaf874 100644
--- a/src/mol-canvas3d/controls/trackball.ts
+++ b/src/mol-canvas3d/controls/trackball.ts
@@ -39,12 +39,12 @@ interface TrackballControls {
     readonly props: Readonly<TrackballControlsProps>
     setProps: (props: Partial<TrackballControlsProps>) => void
 
-    update: () => void
+    update: (t: number) => void
     reset: () => void
     dispose: () => void
 }
 namespace TrackballControls {
-    export function create (input: InputObserver, object: Object3D & { target: Vec3 }, props: Partial<TrackballControlsProps> = {}): TrackballControls {
+    export function create(input: InputObserver, object: Object3D & { target: Vec3 }, props: Partial<TrackballControlsProps> = {}): TrackballControls {
         const p = { ...PD.getDefaultValues(TrackballControlsParams), ...props }
 
         const viewport: Viewport = { x: 0, y: 0, width: 0, height: 0 }
@@ -131,7 +131,7 @@ namespace TrackballControls {
                 Vec3.normalize(rotAxis, Vec3.cross(rotAxis, rotMoveDir, _eye))
 
                 angle *= p.rotateSpeed;
-                Quat.setAxisAngle(rotQuat, rotAxis, angle )
+                Quat.setAxisAngle(rotQuat, rotAxis, angle)
 
                 Vec3.transformQuat(_eye, _eye, rotQuat)
                 Vec3.transformQuat(object.up, object.up, rotQuat)
@@ -150,7 +150,7 @@ namespace TrackballControls {
             Vec2.copy(_movePrev, _moveCurr)
         }
 
-        function zoomCamera () {
+        function zoomCamera() {
             const factor = 1.0 + (_zoomEnd[1] - _zoomStart[1]) * p.zoomSpeed
             if (factor !== 1.0 && factor > 0.0) {
                 Vec3.scale(_eye, _eye, factor)
@@ -207,9 +207,11 @@ namespace TrackballControls {
             }
         }
 
+        let lastUpdated = -1;
         /** Update the object's position, direction and up vectors */
-        function update() {
-            if (p.spin) spin();
+        function update(t: number) {
+            if (lastUpdated === t) return;
+            if (p.spin) spin(t - lastUpdated);
 
             Vec3.sub(_eye, object.position, target)
 
@@ -226,6 +228,8 @@ namespace TrackballControls {
             if (Vec3.squaredDistance(lastPosition, object.position) > EPSILON.Value) {
                 Vec3.copy(lastPosition, object.position)
             }
+
+            lastUpdated = t;
         }
 
         /** Reset object's vectors and the target vector to their initial values */
@@ -299,15 +303,14 @@ namespace TrackballControls {
         }
 
         const _spinSpeed = Vec2.create(0.005, 0);
-        function spin() {
-            _spinSpeed[0] = (p.spinSpeed || 0) / 1000;
+        function spin(deltaT: number) {
+            const frameSpeed = (p.spinSpeed || 0) / 1000;
+            _spinSpeed[0] = 60 * Math.min(Math.abs(deltaT), 1000 / 8) / 1000 * frameSpeed;
             if (!_isInteracting) Vec2.add(_moveCurr, _movePrev, _spinSpeed);
         }
 
         // force an update at start
-        update();
-
-        if (props.spin) { spin(); }
+        update(0);
 
         return {
             viewport,