diff --git a/CHANGELOG.md b/CHANGELOG.md
index b61a6f7ff105d68573bf6056260504599a2e0269..6d8c5963038ba92b58e284561f7e466e4c491372 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,10 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
-- allow link cylinder/line `dashCount` set to '0'
+## [v3.31.4] - 2023-02-24
+
+- Allow link cylinder/line `dashCount` set to '0'
+- Stop animation loop when disposing `PluginContext` (thanks @gfrn for identifying the issue)
 
 ## [v3.31.3] - 2023-02-22
 
diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts
index 9c93941a004a4bf859ed6abdfe7e0356fb9651a5..a993df511c3791ddb09278f15d6c1a08f9568912 100644
--- a/src/mol-canvas3d/canvas3d.ts
+++ b/src/mol-canvas3d/canvas3d.ts
@@ -908,6 +908,7 @@ namespace Canvas3D {
             },
             dispose: () => {
                 contextRestoredSub.unsubscribe();
+                cancelAnimationFrame(animationFrameHandle);
 
                 markBuffer = [];
 
diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts
index 92fd7bfaa2724b7ecdbeeb0c88274b3d3727e6b9..03346bbc348cd5f0371464b0f77cda9412a1271c 100644
--- a/src/mol-plugin/context.ts
+++ b/src/mol-plugin/context.ts
@@ -362,6 +362,7 @@ export class PluginContext {
         }
         this.subs = [];
 
+        this.animationLoop.stop();
         this.commands.dispose();
         this.canvas3d?.dispose();
         this.canvas3dContext?.dispose(options);