##Spotlight
Create a spotlight effect using canvas over specified co-ordinates.
##Spotlight
Create a spotlight effect using canvas over specified co-ordinates.
| var initMap = function() { | |
| var map, layer; | |
| var tiles = new MM.TemplatedLayer("http://spaceclaw.stamen.com/toner/{Z}/{X}/{Y}.png"); | |
| map = new MM.Map("map", tiles); | |
| layer = new SpotlightLayer(); | |
| layer.spotlight.radius = 40; | |
| map.addLayer(layer); | |
| map.setCenterZoom(new MM.Location(37.7819, -122.3365), 12); | |
| var locations = [ | |
| new MM.Location(37.804, -122.252), | |
| new MM.Location(37.764, -122.419) | |
| ]; | |
| locations.forEach(function(loc) { | |
| layer.addLocation(loc); | |
| }); | |
| } |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset=utf-8 /> | |
| <title>Modest Maps JS Demo | Spotlight</title> | |
| <link rel='stylesheet' href='style.css'> | |
| </head> | |
| <body onload='initMap()'> | |
| <div id='map' /> | |
| <script src='https://rawgithub.com/stamen/modestmaps-js/master/modestmaps.min.js'></script> | |
| <script src='spotlight.js'></script> | |
| <script src='app.js'></script> | |
| </body> | |
| </html> |
| /** | |
| * A Spotlight object instance draws "punched out" circles on a canvas. The | |
| * first argument should be an HTML Canvas instance. The second is an optional | |
| * canvas fillStyle (presumably, any CSS color string, e.g. "rgba(0,0,0,.5)" | |
| * for 50% transparent black). | |
| */ | |
| var Spotlight = function(canvas, fillStyle) { | |
| this.canvas = canvas; | |
| this.ctx = canvas.getContext("2d"); | |
| this.clear(); | |
| if (fillStyle) { | |
| this.fillStyle = fillStyle; | |
| } | |
| }; | |
| Spotlight.prototype = { | |
| fillStyle: "rgba(0,0,0,.6)", | |
| radius: 40, | |
| // clearing resets the canvas and fills it with the fillStyle | |
| clear: function() { | |
| this.canvas.width = this.canvas.width; | |
| this.ctx.globalCompositeOperation = "source-over"; | |
| this.fill(); | |
| }, | |
| // fill the canvas with the color defined by fillStyle | |
| fill: function() { | |
| this.ctx.fillStyle = this.fillStyle; | |
| this.ctx.beginPath(); | |
| this.ctx.rect(0, 0, this.canvas.width, this.canvas.height); | |
| this.ctx.fill(); | |
| }, | |
| /** | |
| * Draw an array of points ({x, y}) as white circles. Each circle may | |
| * define its own radius, or we fall back on the value radius argument. | |
| */ | |
| drawPoints: function(points) { | |
| this.ctx.fillStyle = "white"; | |
| var TWO_PI = Math.PI * 2, | |
| radius = this.radius; | |
| /* | |
| * NOTE: we have to draw each circle as a distinct path because | |
| * otherwise their endpoints are connected as though each arc is a | |
| * subpath. | |
| */ | |
| for (var i = 0; i < points.length; i++) { | |
| var p = points[i], | |
| r = ("radius" in p) | |
| ? p.radius | |
| : radius; | |
| this.ctx.beginPath(); | |
| this.ctx.arc(p.x, p.y, r, 0, TWO_PI, true); | |
| this.ctx.closePath(); | |
| this.ctx.fill(); | |
| } | |
| }, | |
| /** | |
| * Draw an array of points ({x, y}) as circles "punched out" from the fill | |
| * color. This produces the "spotlight" effect. | |
| */ | |
| punchout: function(points) { | |
| var time = +new Date(); | |
| this.clear(); | |
| this.ctx.globalCompositeOperation = "destination-out"; | |
| this.drawPoints(points); | |
| console.log(points.length, "pts took", (new Date() - time), "ms"); | |
| } | |
| }; | |
| var SpotlightLayer = function(canvas, fillStyle) { | |
| this.parent = canvas || document.createElement("canvas"); | |
| this.parent.style.position = "absolute"; | |
| this.spotlight = new Spotlight(this.parent, fillStyle); | |
| this.locations = []; | |
| }; | |
| SpotlightLayer.prototype = { | |
| positioned: false, | |
| locations: null, | |
| addLocation: function(loc) { | |
| if (this.map) { | |
| loc.coord = this.map.locationCoordinate(loc); | |
| } | |
| this.locations.push(loc); | |
| this.draw(); | |
| }, | |
| removeLocation: function(loc) { | |
| var len = this.locations.length, | |
| removed = false; | |
| for (var i = 0; i < len; i++) { | |
| if (this.locations[i] === loc) { | |
| this.locations.splice(i, 1); | |
| removed = true; | |
| break; | |
| } | |
| } | |
| if (removed) { | |
| this.draw(); | |
| } | |
| }, | |
| addLocations: function(locs) { | |
| var len = locs.length; | |
| if (this.map) { | |
| for (var i = 0; i < len; i++) { | |
| locs[i].coord = this.map.locationCoordinate(locs[i]); | |
| } | |
| } | |
| for (var i = 0; i < len; i++) { | |
| this.locations.push(locs[i]); | |
| } | |
| this.draw(); | |
| }, | |
| removeAllLocations: function() { | |
| this.locations = []; | |
| this.draw(); | |
| }, | |
| draw: function() { | |
| var map = this.map, | |
| canvas = this.parent; | |
| if (canvas.parentNode != map.parent) { | |
| map.parent.appendChild(canvas); | |
| } | |
| canvas.width = map.dimensions.x; | |
| canvas.height = map.dimensions.y; | |
| if (this.locations && this.locations.length) { | |
| var points = this.locations.map(function(loc) { | |
| var coord = loc.coord || (loc.coord = map.locationCoordinate(loc)), | |
| point = map.coordinatePoint(coord); | |
| if ("radius" in loc) point.radius = loc.radius; | |
| return point; | |
| }); | |
| this.spotlight.punchout(points); | |
| } else { | |
| this.spotlight.clear(); | |
| } | |
| } | |
| }; |
| body { | |
| font: 13px/22px 'Helvetica Neue', Helvetica, sans; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| #map { | |
| position: absolute; | |
| top: 0; left: 0; | |
| width: 100%; | |
| height: 100%; | |
| } |