Shows how to change the canvas compositing / blending mode in post- and precompose eventhandlers.
This example shows how to change the canvas compositing / blending mode in post- and precompose event handlers. The Canvas 2D API provides the property globalCompositeOperation
with which one can influence which composition operation will be used when drawing on the canvas. The various options are well described on the MDN documentation page.
In this example three circles on the corners of an equilateral triangle are drawn with red, green or blue styles respectively. By setting the globalCompositeOperation
you can change how these colors turn out when they are combined on the map.
You can select an operation in the select-field and you can also control which layers will be affected by the chosen operation through the layer checkboxes.
<!DOCTYPE html>
<html>
<head>
<title>Blend Modes</title>
<link rel="stylesheet" href="https://openlayers.org/en/v5.3.0/css/ol.css" type="text/css">
<!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
<style>
.map{
background-repeat: repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAApSURBVBiVY7x///5/BjSgqKjIiC7GhC6ACwygQgxHMzAwMGDz4FDwDAD5/wevjSk4mwAAAABJRU5ErkJggg==);
}
</style>
</head>
<body>
<div id="map" class="map"></div>
<form class="form-horizontal">
<label>
<select id="blend-mode" class="form-control">
<option value="source-over">source-over (default)</option>
<option>source-in</option>
<option>source-out</option>
<option>source-atop</option>
<option>destination-over</option>
<option>destination-in</option>
<option>destination-out</option>
<option>destination-atop</option>
<option>lighter</option>
<option>copy</option>
<option>xor</option>
<option>multiply</option>
<option>screen</option>
<option>overlay</option>
<option>darken</option>
<option>lighten</option>
<option>color-dodge</option>
<option>color-burn</option>
<option>hard-light</option>
<option>soft-light</option>
<option selected>difference</option>
<option>exclusion</option>
<option>hue</option>
<option>saturation</option>
<option>color</option>
<option>luminosity</option>
</select>
Canvas compositing / blending mode
</label>
<label>
<input type="checkbox" id="affect-red" checked>
Red circle affected
</label>
<label>
<input type="checkbox" id="affect-green" checked>
Green circle affected
</label>
<label>
<input type="checkbox" id="affect-blue" checked>
Blue circle affected
</label>
</form>
<script>
import Feature from 'ol/Feature.js';
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import Point from 'ol/geom/Point.js';
import VectorLayer from 'ol/layer/Vector.js';
import VectorSource from 'ol/source/Vector.js';
import {Circle as CircleStyle, Fill, Stroke, Style} from 'ol/style.js';
// Create separate layers for red, green an blue circles.
//
// Every layer has one feature that is styled with a circle, together the
// features form the corners of an equilateral triangle and their styles overlap
var redLayer = new VectorLayer({
source: new VectorSource({
features: [new Feature(new Point([0, 0]))]
}),
style: new Style({
image: new CircleStyle({
fill: new Fill({
color: 'rgba(255,0,0,0.8)'
}),
stroke: new Stroke({
color: 'rgb(255,0,0)',
width: 15
}),
radius: 120
})
})
});
var greenLayer = new VectorLayer({
source: new VectorSource({
// 433.013 is roughly 250 * Math.sqrt(3)
features: [new Feature(new Point([250, 433.013]))]
}),
style: new Style({
image: new CircleStyle({
fill: new Fill({
color: 'rgba(0,255,0,0.8)'
}),
stroke: new Stroke({
color: 'rgb(0,255,0)',
width: 15
}),
radius: 120
})
})
});
var blueLayer = new VectorLayer({
source: new VectorSource({
features: [new Feature(new Point([500, 0]))]
}),
style: new Style({
image: new CircleStyle({
fill: new Fill({
color: 'rgba(0,0,255,0.8)'
}),
stroke: new Stroke({
color: 'rgb(0,0,255)',
width: 15
}),
radius: 120
})
})
});
// Create the map, the view is centered on the triangle. Zooming and panning is
// restricted to a sane area
var map = new Map({
layers: [
redLayer,
greenLayer,
blueLayer
],
target: 'map',
view: new View({
center: [250, 220],
extent: [0, 0, 500, 500],
resolution: 4,
minResolution: 2,
maxResolution: 32
})
});
// Get the form elements and bind the listeners
var select = document.getElementById('blend-mode');
var affectRed = document.getElementById('affect-red');
var affectGreen = document.getElementById('affect-green');
var affectBlue = document.getElementById('affect-blue');
/**
* This method sets the globalCompositeOperation to the value of the select
* field and it is bound to the precompose event of the layers.
*
* @param {module:ol/render/Event~RenderEvent} evt The render event.
*/
var setBlendModeFromSelect = function(evt) {
evt.context.globalCompositeOperation = select.value;
};
/**
* This method resets the globalCompositeOperation to the default value of
* 'source-over' and it is bound to the postcompose event of the layers.
*
* @param {module:ol/render/Event~RenderEvent} evt The render event.
*/
var resetBlendModeFromSelect = function(evt) {
evt.context.globalCompositeOperation = 'source-over';
};
/**
* Bind the pre- and postcompose handlers to the passed layer.
*
* @param {module:ol/layer/Vector} layer The layer to bind the handlers to.
*/
var bindLayerListeners = function(layer) {
layer.on('precompose', setBlendModeFromSelect);
layer.on('postcompose', resetBlendModeFromSelect);
};
/**
* Unind the pre- and postcompose handlers to the passed layers.
*
* @param {module:ol/layer/Vector} layer The layer to unbind the handlers from.
*/
var unbindLayerListeners = function(layer) {
layer.un('precompose', setBlendModeFromSelect);
layer.un('postcompose', resetBlendModeFromSelect);
};
/**
* Handler for the click event of the 'affect-XXX' checkboxes.
*
* @this {HTMLInputElement}
*/
var affectLayerClicked = function() {
var layer;
if (this.id == 'affect-red') {
layer = redLayer;
} else if (this.id == 'affect-green') {
layer = greenLayer;
} else {
layer = blueLayer;
}
if (this.checked) {
bindLayerListeners(layer);
} else {
unbindLayerListeners(layer);
}
map.render();
};
// Rerender map when blend mode changes
select.addEventListener('change', function() {
map.render();
});
// Unbind / bind listeners depending on the checked state when the checkboxes
// are clicked
affectRed.addEventListener('click', affectLayerClicked);
affectGreen.addEventListener('click', affectLayerClicked);
affectBlue.addEventListener('click', affectLayerClicked);
// Initially bind listeners
bindLayerListeners(redLayer);
bindLayerListeners(greenLayer);
bindLayerListeners(blueLayer);
</script>
</body>
</html>