Draws a customizable heart outline on a canvas.

by Arjan Haverkamp

HTML

<canvas id="canvas" width="400" height="400"></canvas>

JavaScript

/**
 * Draw a smooth heart outline that keeps its aspect ratio.
 * The tip is softly rounded; increase tipRound for an even blunter bottom.
 *
 * @param {CanvasRenderingContext2D} ctx
 * @param {Object} [opts]
 * @param {number} [opts.padding=20]        Inner margin in pixels
 * @param {number} [opts.strokeWidth=4]     Stroke width
 * @param {string} [opts.strokeStyle='#333'] Stroke color
 * @param {number} [opts.tipRound=0.14]     0..0.25 — how rounded the bottom is
 */
function drawNiceHeart(ctx, opts = {}) {
  const {
    padding = 20,
    strokeWidth = 4,
    strokeStyle = '#333',
    tipRound = 0.14
  } = opts;

  const { width: W, height: H } = ctx.canvas;

  // --- Heart path in a unit box [0..1]x[0..1] ------------------------------
  // Control-point ratios chosen to produce a soft, elegant heart.
  // We model half and mirror it; tipRound lifts and widens the bottom.
  function buildPath(scale, offsetX, offsetY) {
    const p = new Path2D();

    // Key shape parameters (tweak to taste)
    const topY   = 0.28;        // where the top indentation sits
    const lobeX  = 0.22;        // how far the lobes bulge outward
    const lobeY  = 0.02;        // how high the lobe control points are
    const rightX = 1 - lobeX;

    // Bottom rounding: raise and widen the tip
    const tipY   = 1 - tipRound * 0.2;     // lift a bit from the very bottom
    const tipOut = 0.22 + tipRound * 0.35; // widen the “hips” near the bottom
    const tipCtrlY = 0.92 - tipRound * 0.35;

    // Helper to convert unit coords to canvas coords
    const X = x => offsetX + x * scale;
    const Y = y => offsetY + y * scale;

    // Start slightly above the bottom so the tip becomes rounded
    p.moveTo(X(0.5), Y(tipY));

    // Right half (two cubics)
    p.bezierCurveTo(
      X(0.5 + tipOut*0.45), Y(tipCtrlY),   // control 1 near the bottom
      X(1.00),               Y(0.70),      // control 2 (outer shoulder)
      X(rightX),   ...