diff --git a/src/mol-plugin/behavior.ts b/src/mol-plugin/behavior.ts
index 976e78982558424c4fa084fac2596efc672389f1..86339477be343557bbb6f51a1d3229cd14462db7 100644
--- a/src/mol-plugin/behavior.ts
+++ b/src/mol-plugin/behavior.ts
@@ -14,6 +14,7 @@ import * as StaticMisc from './behavior/static/misc'
 import * as DynamicRepresentation from './behavior/dynamic/representation'
 import * as DynamicCamera from './behavior/dynamic/camera'
 import * as DynamicCustomProps from './behavior/dynamic/custom-props'
+import * as DynamicAnimation from './behavior/dynamic/animation'
 
 export const BuiltInPluginBehaviors = {
     State: StaticState,
@@ -25,5 +26,6 @@ export const BuiltInPluginBehaviors = {
 export const PluginBehaviors = {
     Representation: DynamicRepresentation,
     Camera: DynamicCamera,
-    CustomProps: DynamicCustomProps
+    CustomProps: DynamicCustomProps,
+    Animation: DynamicAnimation
 }
\ No newline at end of file
diff --git a/src/mol-plugin/behavior/dynamic/animation.ts b/src/mol-plugin/behavior/dynamic/animation.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b3696db3fa84d878a261e282cb5202d454c4bd3a
--- /dev/null
+++ b/src/mol-plugin/behavior/dynamic/animation.ts
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { PluginContext } from 'mol-plugin/context';
+import { PluginBehavior } from '../behavior';
+import { ParamDefinition as PD } from 'mol-util/param-definition'
+import { degToRad } from 'mol-math/misc';
+import { Mat4, Vec3 } from 'mol-math/linear-algebra';
+import { PluginStateObject as SO, PluginStateObject } from '../../state/objects';
+import { StateSelection } from 'mol-state/state/selection';
+
+// TODO this is just for testing purposes
+export const Animation = PluginBehavior.create<{ play: boolean }>({
+    name: 'animation',
+    display: { name: 'Animation', group: 'Animation' },
+    ctor: class extends PluginBehavior.Handler<{ play: boolean }> {
+        private tmpMat = Mat4.identity()
+        private rotMat = Mat4.identity()
+        private transMat = Mat4.identity()
+        private animMat = Mat4.identity()
+        private transVec = Vec3.zero()
+        private rotVec = Vec3.create(0, 1, 0)
+        private animHandle = -1
+
+        constructor(protected ctx: PluginContext, protected params: { play: boolean }) {
+            super(ctx, params)
+            this.update(params)
+        }
+
+        animate(play: boolean) {
+            if (play) {
+                const state = this.ctx.state.dataState
+                const reprs = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Representation3D));
+                const anim = (t: number) => {
+                    const rad = degToRad((t / 10) % 360)
+                    Mat4.rotate(this.rotMat, this.tmpMat, rad, this.rotVec)
+                    for (const r of reprs) {
+                        if (!SO.isRepresentation3D(r.obj)) return
+                        const parent = StateSelection.findAncestorOfType(state.tree, state.cells, r.transform.ref, [PluginStateObject.Molecule.Structure])
+                        if (!parent || !parent.obj) continue
+                        const structure = parent.obj as PluginStateObject.Molecule.Structure
+
+                        Vec3.negate(this.transVec, Vec3.copy(this.transVec, structure.data.boundary.sphere.center))
+                        Mat4.fromTranslation(this.transMat, this.transVec)
+                        Mat4.mul(this.animMat, this.rotMat, this.transMat)
+
+                        Vec3.copy(this.transVec, structure.data.boundary.sphere.center)
+                        Mat4.fromTranslation(this.transMat, this.transVec)
+                        Mat4.mul(this.animMat, this.transMat, this.animMat)
+
+                        r.obj.data.setState({ transform: this.animMat })
+                        this.ctx.canvas3d.add(r.obj.data)
+                        this.ctx.canvas3d.requestDraw(true)
+                    }
+                    this.animHandle = requestAnimationFrame(anim)
+                }
+                this.animHandle = requestAnimationFrame(anim)
+            } else {
+                cancelAnimationFrame(this.animHandle)
+            }
+        }
+
+        register(): void { }
+
+        update(p: { play: boolean }) {
+            let updated = this.params.play !== p.play
+            this.params.play = p.play
+            if (updated) {
+                this.animate(this.params.play)
+            }
+            return updated;
+        }
+
+        unregister() {
+            cancelAnimationFrame(this.animHandle)
+        }
+    },
+    params: () => ({
+        play: PD.Boolean(false)
+    })
+});
\ No newline at end of file
diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts
index 838935a85296c4ca41ec10382ef96be395f98da7..cf3507b2ab977f373ee533a3e1841312ea427d37 100644
--- a/src/mol-plugin/index.ts
+++ b/src/mol-plugin/index.ts
@@ -2,6 +2,7 @@
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { PluginContext } from './context';
@@ -36,6 +37,7 @@ const DefaultSpec: PluginSpec = {
         PluginSpec.Behavior(PluginBehaviors.Representation.SelectLoci),
         PluginSpec.Behavior(PluginBehaviors.Representation.DefaultLociLabelProvider),
         PluginSpec.Behavior(PluginBehaviors.Camera.FocusLociOnSelect, { minRadius: 20, extraRadius: 4 }),
+        PluginSpec.Behavior(PluginBehaviors.Animation.Animation, { play: false }),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.PDBeStructureQualityReport, { autoAttach: true }),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.RCSBAssemblySymmetry, { autoAttach: true }),
     ]