Let\'s say I have the following code.
You can achieve the spotlight effect by positioning a canvas directly over the image. Make the canvas the same size as the image and set the compositing operation to xor
so that two black pixels drawn in the same place cancel each other out.
context.globalCompositeOperation = 'xor';
Now you can paint the canvas black and fill a black circle around the mouse cursor. The result is a hole in the black surface, showing the image underneath.
// Paint the canvas black.
context.fillStyle = '#000';
context.clearRect(0, 0, width, height);
context.fillRect(0, 0, width, height);
// Paint a black circle around x, y.
context.beginPath();
context.arc(x, y, spotlightRadius, 0, 2 * Math.PI);
context.fillStyle = '#000';
context.fill();
// With xor compositing, the result is a circular hole.
To make a spotlight with blurry edges, define a radial gradient centered on the mouse position and fill a square around it.
var gradient = context.createRadialGradient(x, y, 0, x, y, spotlightRadius);
gradient.addColorStop(0, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(0.9, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
context.fillStyle = gradient;
context.fillRect(x - spotlightRadius, y - spotlightRadius,
2 * spotlightRadius, 2 * spotlightRadius);
The following snippet demonstrates both approaches using pure JavaScript. To change from a crisp-edged spotlight to a blurry-edged spotlight, click on the checkbox above the image.
function getOffset(element, ancestor) {
var left = 0,
top = 0;
while (element != ancestor) {
left += element.offsetLeft;
top += element.offsetTop;
element = element.parentNode;
}
return { left: left, top: top };
}
function getMousePosition(event) {
event = event || window.event;
if (event.pageX !== undefined) {
return { x: event.pageX, y: event.pageY };
}
return {
x: event.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft,
y: event.clientY + document.body.scrollTop +
document.documentElement.scrollTop
};
}
window.onload = function () {
var spotlightRadius = 60,
container = document.getElementById('container'),
canvas = document.createElement('canvas'),
image = container.getElementsByTagName('img')[0],
width = canvas.width = image.width,
height = canvas.height = image.height,
context = canvas.getContext('2d');
context.globalCompositeOperation = 'xor';
container.insertBefore(canvas, image.nextSibling);
container.style.width = width + 'px';
container.style.height = height + 'px';
var offset = getOffset(canvas, document.body);
clear = function () {
context.fillStyle = '#000';
context.clearRect(0, 0, width, height);
context.fillRect(0, 0, width, height);
};
clear();
image.style.visibility = 'visible';
canvas.onmouseout = clear;
canvas.onmouseover = canvas.onmousemove = function (event) {
var mouse = getMousePosition(event),
x = mouse.x - offset.left,
y = mouse.y - offset.top;
clear();
if (document.getElementById('blurry').checked) {
var gradient = context.createRadialGradient(x, y, 0, x, y, spotlightRadius);
gradient.addColorStop(0, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(0.875, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
context.fillStyle = gradient;
context.fillRect(x - spotlightRadius, y - spotlightRadius,
2 * spotlightRadius, 2 * spotlightRadius);
} else {
context.beginPath();
context.arc(x, y, spotlightRadius, 0, 2 * Math.PI);
context.fillStyle = '#000';
context.fill();
}
};
};
* {
margin: 0;
padding: 0;
}
.control {
font-family: sans-serif;
font-size: 15px;
padding: 10px;
}
#container {
position: relative;
}
#container img, #container canvas {
position: absolute;
left: 0;
top: 0;
}
#container img {
visibility: hidden;
}
#container canvas {
cursor: none;
}
blurry edges