Graphics on the Web

A hands-on showcase comparing PNG, SVG, and HTML5 Canvas. Learn how pixel-based, vector-based, and programmatic rendering differ in quality, performance, and flexibility.

PNG
Spotify Logo

Raster (PNG) uses fixed pixels and shows quality loss when enlarged.

SVG

Vector (SVG) scales infinitely with perfect sharpness at any size.

See the difference

Spotify Logo (Raster PNG) PNG (Raster)
Spotify Logo (Vector SVG) SVG (Vector)

Examples

Canvas

HTML5 Canvas is a pixel-based drawing API that allows direct, low-level rendering of graphics using JavaScript.

This example demonstrates how the Spotify logo can be drawn and animated on a canvas using manually defined Bézier curves and dynamic scaling, highlighting the differences between pixel-based rendering and vector-based SVG animations.

<canvas id="example-panel-1" height="250px" width="250px">
</canvas>

function drawSpotifyLogo(canvas, color) {
    var ctx = canvas.getContext("2d");



    // Design space is 400x400; compute scale + offset to center the logo
    var designSize = 400;
    var scale = Math.min(canvas.width, canvas.height) / designSize;
    var offsetX = (canvas.width - designSize * scale) / 2;
    var offsetY = (canvas.height - designSize * scale) / 2;


    ctx.save();
    ctx.translate(offsetX, offsetY);
    ctx.scale(scale, scale);

    // Clip to the green circle in design coordinates
    ctx.beginPath();
    
    var cx = designSize / 2;
    var cy = designSize / 2;
    var r = designSize / 2;
    // Magic constant for circle approximation
    const k = 0.5522847498307936; // ≈ 4*(√2-1)/3

    const ox = r * k; // control point offset horizontal
    const oy = r * k; // control point offset vertical

    const x0 = cx - r;
    const x1 = cx - r;
    const x2 = cx - ox;
    const x3 = cx;
    const x4 = cx + ox;
    const x5 = cx + r;

    const y0 = cy - r;
    const y1 = cy - oy;
    const y2 = cy;
    const y3 = cy + oy;
    const y4 = cy + r;

    ctx.beginPath();

    // Start at the rightmost point of the circle
    ctx.moveTo(cx + r, cy);

    // Top-right quadrant
    ctx.bezierCurveTo(cx + r, cy - oy, cx + ox, cy - r, cx, cy - r);

    // Top-left quadrant
    ctx.bezierCurveTo(cx - ox, cy - r, cx - r, cy - oy, cx - r, cy);

    // Bottom-left quadrant
    ctx.bezierCurveTo(cx - r, cy + oy, cx - ox, cy + r, cx, cy + r);

    // Bottom-right quadrant
    ctx.bezierCurveTo(cx + ox, cy + r, cx + r, cy + oy, cx + r, cy);

    ctx.closePath();

    ctx.fillStyle = color;
    ctx.fill();

    ctx.clip();

    // top curve
    ctx.beginPath();
    ctx.moveTo(318, 178);
    ctx.bezierCurveTo(254, 140, 147, 136, 86, 155);
    ctx.bezierCurveTo(76, 158, 66, 152, 63, 143);
    ctx.bezierCurveTo(60, 133, 66, 123, 75, 120);
    ctx.bezierCurveTo(146, 99, 263, 103, 337, 147);
    ctx.bezierCurveTo(346, 152, 349, 164, 344, 173);
    ctx.bezierCurveTo(339, 180, 327, 183, 318, 178);
    ctx.closePath();
    ctx.fillStyle = spotifyGray;
    ctx.fill();

    // mid curve
    ctx.beginPath();
    ctx.moveTo(316, 234);
    ctx.bezierCurveTo(311, 241, 302, 244, 295, 239);
    ctx.bezierCurveTo(241, 206, 159, 196, 96, 216);
    ctx.bezierCurveTo(88, 218, 79, 214, 77, 206);
    ctx.bezierCurveTo(75, 198, 79, 189, 87, 187);
    ctx.bezierCurveTo(160, 165, 250, 176, 312, 214);
    ctx.bezierCurveTo(318, 217, 321, 227, 316, 234);
    ctx.closePath();
    ctx.fillStyle = spotifyGray;
    ctx.fill();

    // bottom curve
    ctx.beginPath();
    ctx.moveTo(292, 289);
    ctx.bezierCurveTo(288, 295, 281, 297, 274, 293);
    ctx.bezierCurveTo(228, 264, 169, 258, 99, 274);
    ctx.bezierCurveTo(92, 276, 86, 271, 84, 265);
    ctx.bezierCurveTo(82, 258, 87, 252, 93, 250);
    ctx.bezierCurveTo(169, 233, 235, 240, 287, 272);
    ctx.bezierCurveTo(294, 275, 295, 283, 292, 289);
    ctx.closePath();
    ctx.fillStyle = spotifyGray;
    ctx.fill();

    ctx.restore(); // restore pre-clip
    ctx.restore(); // restore scale/translate
}
                            

Color picker

Pick and adjust colors using an always-visible color picker. You can change hue, saturation, and brightness, preview the selected color, and copy its value in Hex, RGB, or HSL format.

Animation

An SVG animation that draws and erases the Spotify logo as a continuous line. The effect is achieved by animating stroke-dash properties along the path, showcasing SVG’s strength in clean, scalable vector animations.

                                
<svg width="250px" height="250px" viewBox="0 0 48 48" version="1.1"
    xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
    id="spotifySVG">
        <g id="Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
            <g id="Color-" transform="translate(-200.000000, -460.000000)" fill="#00DA5A">
                <path d="M238.16,481.36 C230.48,476.8 217.64,476.32 210.32,478.6 C209.12,478.96 207.92,478.24 207.56,477.16 C207.2,475.96 207.92,474.76 209,474.4 C217.52,471.88 231.56,472.36 240.44,477.64 C241.52,478.24 241.88,479.68 241.28,480.76 C240.68,481.6 239.24,481.96 238.16,481.36 M237.92,488.08 C237.32,488.92 236.24,489.28 235.4,488.68 C228.92,484.72 219.08,483.52 211.52,485.92 C210.56,486.16 209.48,485.68 209.24,484.72 C209,483.76 209.48,482.68 210.44,482.44 C219.2,479.8 230,481.12 237.44,485.68 C238.16,486.04 238.52,487.24 237.92,488.08 M235.04,494.68 C234.56,495.4 233.72,495.64 233,495.16 C227.36,491.68 220.28,490.96 211.88,492.88 C211.04,493.12 210.32,492.52 210.08,491.8 C209.84,490.96 210.44,490.24 211.16,490 C220.28,487.96 228.2,488.8 234.44,492.64 C235.28,493 235.4,493.96 235.04,494.68 M224,460 C210.8,460 200,470.8 200,484 C200,497.2 210.8,508 224,508 C237.2,508 248,497.2 248,484 C248,470.8 237.32,460 224,460"
                    id="Spotify">
                </path>
            </g>
        </g>
</svg>
                            

// Attach SVG.js to the existing inline SVG element
const draw = SVG('#spotifySVG')

// Select the Spotify logo path by its ID
const logo = draw.findOne('#Spotify')

// Measure the total length of the SVG path
// This value is crucial for stroke-dash animations
const length = logo.length()

// ---------- INITIAL STROKE SETUP ----------

// Remove any fill so the logo is drawn only as a line
logo
    .fill('none')

    // Configure the stroke (outline) appearance
    .stroke({
        color: '#00DA5A',   // Spotify green
        width: 0.6,         // Thin line for a clean draw effect
        linecap: 'round',   // Rounded line ends for smooth drawing
        linejoin: 'round',  // Smooth joins between path segments

        // dasharray defines the visible dash + hidden gap
        // Using the full path length makes one continuous dash
        dasharray: length,

        // dashoffset shifts the dash completely off the path
        // This makes the path fully invisible at the start
        dashoffset: length
    })

// ---------- ANIMATION LOOP FUNCTION ----------

function animateDrawUndraw() {

    // DRAW PHASE:
    // Animate dashoffset from `length` → `0`
    // This reveals the stroke progressively along the path
    logo
        .animate(3000, '<>')   // 3 seconds, smooth ease-in-out
        .stroke({ dashoffset: 0 })
        .after(() => {

            // UNDRAW PHASE:
            // Animate dashoffset from `0` → `-length`
            // This slides the stroke past the path, erasing it
            logo
                .animate(3000, '<>')  // 3 seconds undraw
                .stroke({ dashoffset: -length })

                // When undraw finishes, restart the cycle
                .after(animateDrawUndraw)
        })
}

// ---------- START THE ANIMATION ----------

// Kick off the first draw → undraw cycle
animateDrawUndraw()