Skip to main content

AnimationFrameListener

WLJS
Execution environment
Supports dynamics

signals once the beginning of a browser's frame (depends on OS settings, hardware). It can be reloaded again after any changes of dependent dynamic symbols similar to requestAnimationFrame.

The main idea is

  • to be in sync with a beginning of native browser's frame drawing
  • to animate something as fast as possible
  • don't fire next frame, before calculations are done

Therefore this provides a balanced load to the system.

AnimationFrameListener[trigger_Offload, "Event"->event_String, opts___]

where trigger can be any dynamic symbol wrapped in Offload, that will be changed each frame, it serves the purpose of a signal, that all calculations are done. event is a string representation of EventObject to which a handler function, that recalculates trigger is assigned.

Options

"Event" (mandatory)

Provides an event object in the form of a string

Example

Here is a basic example with many points and lines animated:

With[{pts = Unique[], event = CreateUUID[], n = Unique[]},
n = 1;

EventHandler[event, Function[Null,
pts = Table[{x, Sum[Cos[2 x i]/(2i+1), {i, 1, n}]/n}, {x,0,10Pi,0.1}];
n = If[n>20, 1, n + 0.1];
]];

EventFire[event, True];

Graphics[{
Blue, Line[pts//Offload], Red, Point[pts//Offload],
AnimationFrameListener[pts//Offload, "Event"->event]
}, Axes->True, PlotRange->{{0,10Pi}, {-0.15,0.3}}, TransitionType->None]
]
tip

Animation goes as fast as possible. It does not make sense to still use transition interpolation of 2D vector graphics, therefore we disabled it with TransitionType->None.

Here we used the same symbol pts used for drawing our points for triggering AnimationFrameListener. It does not cost anything, since it listens only for change of a symbol and does not read or decode an actual value.

Here is another example with 3D graphics:

balls = RandomReal[{-1,1}, {100,3}];
vels = RandomReal[{-1,1}, {100,3}];

EventHandler["frame", Function[Null,
vels = Table[
If[Norm[balls[[i]]] < 0.01, -1, 1] vels[[i]] - 0.08 balls[[i]]
, {i, Length[balls]}];

balls = Table[balls[[i]] + 0.08 vels[[i]], {i, Length[balls]}];
]];

Graphics3D[{
Table[With[{i = i},
{
RGBColor[RandomReal[{0,1}, 3]],
Sphere[balls[[i]] // Offload, 0.03]
}
], {i, Length[balls]}],
AnimationFrameListener[balls // Offload, "Event"->"frame"]
}]

There is also a famous example of 1000 particles "dancing" with each other

cell 1
n = 1000;
r := RandomInteger[{1, n}];
f := (#/(.01 + Sqrt[#.#])) & /@ (x[[#]] - x) &;
s := With[{r1 = r}, p[[r1]] = r; q[[r1]] = r];
x = RandomReal[{-1, 1}, {n, 2}];
{p, q} = RandomInteger[{1, n}, {2, n}];
cell 2
EventHandler["frame", Function[Null,
(* all calculations *)
x = 0.995 x + 0.02 f[p] - 0.01 f[q];
If[r < 100, s];
]];
cell 3
Graphics[{
PointSize[0.007], Point[x // Offload],
AnimationFrameListener[x // Offload, "Event"->"frame"]
}, PlotRange -> {{-2,2}, {-2,2}}, "TransitionType"->"Linear", "TransitionDuration"->1]

to cancel animation - remove handler

EventRemove["frame"]

and to restart - evaluate cell 2 and retrigger it using

EventFire["frame", True];