diff --git a/examples/feature-animation.html b/examples/feature-animation.html
new file mode 100644
index 0000000000..6dd0795b36
--- /dev/null
+++ b/examples/feature-animation.html
@@ -0,0 +1,15 @@
+---
+template: example.html
+title: Feature animation example
+shortdesc: Demonstrates how to animate features.
+docs: >
+ This example shows how to use postcompose and vectorContext to
+ animate features. Here we choose to do a flash animation each time a feature
+ is added to the layer.
+tags: "animation, vector, feature, flash"
+---
+
diff --git a/examples/feature-animation.js b/examples/feature-animation.js
new file mode 100644
index 0000000000..00d97d98fc
--- /dev/null
+++ b/examples/feature-animation.js
@@ -0,0 +1,96 @@
+goog.require('ol.Feature');
+goog.require('ol.Map');
+goog.require('ol.Observable');
+goog.require('ol.View');
+goog.require('ol.control');
+goog.require('ol.easing');
+goog.require('ol.geom.Point');
+goog.require('ol.layer.Tile');
+goog.require('ol.layer.Vector');
+goog.require('ol.proj');
+goog.require('ol.source.OSM');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Circle');
+goog.require('ol.style.Stroke');
+
+
+var map = new ol.Map({
+ layers: [
+ new ol.layer.Tile({
+ source: new ol.source.OSM({
+ wrapX: false
+ })
+ })
+ ],
+ controls: ol.control.defaults({
+ attributionOptions: /** @type {olx.control.AttributionOptions} */ ({
+ collapsible: false
+ })
+ }),
+ renderer: common.getRendererFromQueryString(),
+ target: 'map',
+ view: new ol.View({
+ center: [0, 0],
+ zoom: 1
+ })
+});
+
+var source = new ol.source.Vector({
+ wrapX: false
+});
+var vector = new ol.layer.Vector({
+ source: source
+});
+map.addLayer(vector);
+
+function addRandomFeature() {
+ var x = Math.random() * 360 - 180;
+ var y = Math.random() * 180 - 90;
+ var geom = new ol.geom.Point(ol.proj.transform([x, y],
+ 'EPSG:4326', 'EPSG:3857'));
+ var feature = new ol.Feature(geom);
+ source.addFeature(feature);
+}
+
+var duration = 3000;
+function flash(feature) {
+ var start = new Date().getTime();
+ var listenerKey;
+
+ function animate(event) {
+ var vectorContext = event.vectorContext;
+ var frameState = event.frameState;
+ var flashGeom = feature.getGeometry().clone();
+ var elapsed = frameState.time - start;
+ var elapsedRatio = elapsed / duration;
+ // radius will be 5 at start and 30 at end.
+ var radius = ol.easing.easeOut(elapsedRatio) * 25 + 5;
+ var opacity = ol.easing.easeOut(1 - elapsedRatio);
+
+ var flashStyle = new ol.style.Circle({
+ radius: radius,
+ snapToPixel: false,
+ stroke: new ol.style.Stroke({
+ color: 'rgba(255, 0, 0, ' + opacity + ')',
+ width: 1,
+ opacity: opacity
+ })
+ });
+
+ vectorContext.setImageStyle(flashStyle);
+ vectorContext.drawPointGeometry(flashGeom, null);
+ if (elapsed > duration) {
+ ol.Observable.unByKey(listenerKey);
+ return;
+ }
+ // tell OL3 to continue postcompose animation
+ frameState.animate = true;
+ }
+ listenerKey = map.on('postcompose', animate);
+}
+
+source.on('addfeature', function(e) {
+ flash(e.feature);
+});
+
+window.setInterval(addRandomFeature, 1000);