Generate a heart-shaped vignette on a canvas
by Arjan Haverkamp
CSS
body {
background-color: #fff
}
JavaScript
function vignetteHeart(width, height, innerPercent) {
// Clamp innerPercent between 0 and 1
const pct = Math.max(0, Math.min(innerPercent, 1));
// Create and size the canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
//
// Draw white vignette (transparent center -> white edges)
//
const cx = width / 2;
const cy = height / 2;
const rx1 = width / 2;
const ry1 = height / 2;
ctx.save();
ctx.translate(cx, cy);
ctx.scale(rx1, ry1); // unit circle -> ellipse
const r0 = pct; // inner (transparent) radius
const r1 = Math.SQRT2; // reaches corners
const grad = ctx.createRadialGradient(0, 0, r0, 0, 0, r1);
grad.addColorStop(0, 'rgba(255,0,255,0)');
grad.addColorStop(1, 'rgba(255,0,255,0.85)');
ctx.fillStyle = grad;
ctx.fillRect(-1, -1, 2, 2);
ctx.restore();
//
// Punch out the heart
//
// SVG heart path (viewBox ~ 0 0 16 16). We'll fill it to erase pixels.
const HEART_PATH = 'M8 1.314C12.438-3.248 23.534 4.735 8 15C-7.534 4.736 3.562-3.248 8 1.314';
const path = new Path2D(HEART_PATH);
// Size the heart with some padding, keep aspect, center it
const padding = Math.round(Math.min(width, height) / 15);
const size = Math.min(width, height) - 2 * padding; // max heart box
const scale = size / 16; // the path is ~16x16 units
const drawW = 16 * scale;
const drawH = 16 * scale;
const ox = (width - drawW) / 2;
const oy = (height - drawH) / 2;
// Erase (make transparent) where the heart is
ctx.save();
ctx.globalCompositeOperation = 'destination-out';
ctx.translate(ox, oy);
ctx.scale(scale, scale);
ctx.fill(path); // fill to remove the interior
ctx.restore();
// (Optional) draw a visible outline on top:
ctx.save();
ctx.translate(ox, oy);
ctx.scale(scale, scale);
ctx.lineWidth = 0.1;
ctx.strokeStyle =...