HTML5 Canvas Balloon
Today we’re going to draw a balloon on the HTML5 canvas element. Why not, right? Here’s the final result:
The balloon shape is basically a circle stretched out in various places. However, it’s not that straightforward to simply draw a circle and then stretch it with the canvas API. Instead, we need to recreate a circle using four cubic Bézier curves:
After beginning this process, I discovered that it is actually impossible to create a perfect circle using cubic Bézier curves. Instead, you can only get a very close approximation using a constant called “kappa”. So to calculate the length of the “handles” (or the distance from a control point to a corresponding point on the curve), we use the following formula:
var KAPPA = (4 * (Math.sqrt(2) - 1))/3;
var handleLength = KAPPA * radius;
Now we can create a near-perfect circle using four, separate cubic Bézier curves, created with the bezierCurveTo method:
// Begin balloon path
gfxContext.beginPath();
// Top Left Curve
var topLeftCurveStartX = centerX - radius;
var topLeftCurveStartY = centerY;
var topLeftCurveEndX = centerX;
var topLeftCurveEndY = centerY - radius;
gfxContext.moveTo(topLeftCurveStartX, topLeftCurveStartY);
gfxContext.bezierCurveTo(topLeftCurveStartX, topLeftCurveStartY - handleLength,
topLeftCurveEndX - handleLength, topLeftCurveEndY,
topLeftCurveEndX, topLeftCurveEndY);
// Top Right Curve
var topRightCurveStartX = centerX;
var topRightCurveStartY = centerY - radius;
var topRightCurveEndX = centerX + radius;
var topRightCurveEndY = centerY;
gfxContext.bezierCurveTo(topRightCurveStartX + handleLength, topRightCurveStartY,
topRightCurveEndX, topRightCurveEndY - handleLength,
topRightCurveEndX, topRightCurveEndY);
// Bottom Right Curve
var bottomRightCurveStartX = centerX + radius;
var bottomRightCurveStartY = centerY;
var bottomRightCurveEndX = centerX;
var bottomRightCurveEndY = centerY + radius;
gfxContext.bezierCurveTo(bottomRightCurveStartX, bottomRightCurveStartY + handleLength,
bottomRightCurveEndX + handleLength, bottomRightCurveEndY,
bottomRightCurveEndX, bottomRightCurveEndY);
// Bottom Left Curve
var bottomLeftCurveStartX = centerX;
var bottomLeftCurveStartY = centerY + radius;
var bottomLeftCurveEndX = centerX - radius;
var bottomLeftCurveEndY = centerY;
gfxContext.bezierCurveTo(bottomLeftCurveStartX - handleLength, bottomLeftCurveStartY,
bottomLeftCurveEndX, bottomLeftCurveEndY + handleLength,
bottomLeftCurveEndX, bottomLeftCurveEndY);
Once we have our circle in place, we can stretch out the curves to create the balloon shape. I just fiddled around and came out with arbitrary values for the factors used to stretch the shape. For example, I stretched out the bottom using a heightDiff value, calculated as follows:
var heightDiff = (radius * 0.4);
We can now modify the above circle equation to use these values:
var balloonBottomY = centerY + radius + heightDiff
// Updated Bottom Right Curve Value
var bottomRightCurveEndY = balloonBottomY;
// Updated Bottom Left Curve Value
var bottomLeftCurveStartY = baloonBottomY;
To add color to the balloon, I used a radial gradient with, again, some arbitrary values thrown in there:
var gradientOffset = (radius/3);
var balloonGradient =
gfxContext.createRadialGradient(centerX + gradientOffset,
centerY - gradientOffset, 3,
centerX, centerY,
radius + heightDiff);
balloonGradient.addColorStop(0, this.lightColor.rgbString());
balloonGradient.addColorStop(0.7, this.darkColor.rgbString());
gfxContext.fillStyle = balloonGradient;
gfxContext.fill();
To calculate the gradient colors, I found this fantastic color manipulation library, that makes working with colors a snap:
this.baseColor = new Color(color);
this.darkColor = (new Color(color)).darken(0.3);
this.lightColor = (new Color(color)).lighten(0.3);
Check out the full script!