ndg
ndg

Reputation: 2645

Drawing text with an outer stroke with HTML5's canvas

I'm currently using HTML5's canvas to render a number of strings using the fillText method. This works fine, but I'd also like to give each string a 1px black outer stroke. Unfortunately the strokeText function seems to apply an inner stroke. To counter this, I've written a drawStrokedText function that achieves the effect I'm after. Unfortunately it's horrible slow (for obvious reasons).

Is there a fast, cross-browser way of achieving a 1px outer stroke using native canvas functionality?

drawStrokedText = function(context, text, x, y)
{
    context.fillStyle = "rgb(0,0,0)";
    context.fillText(text, x-1, y-1);
    context.fillText(text, x+1, y-1);
    context.fillText(text, x-1, y);
    context.fillText(text, x+1, y);
    context.fillText(text, x-1, y+1);
    context.fillText(text, x+1, y+1);

    context.fillStyle = "rgb(255,255,255)";
    context.fillText(text, x, y);
};

Here's an example of the effect at work:

enter image description here

Upvotes: 29

Views: 37062

Answers (4)

Jack
Jack

Reputation: 2285

Simon's answer is a good solution, yet it may have mitering glitches in some cases, especially with capital 'M', 'V', & 'W':

drawStroked("MVW", 50, 150);

http://jsfiddle.net/hwG42/1/

In this case, it's best to utilize:

ctx.miterLimit=2;

http://jsfiddle.net/hwG42/3/

Best of luck!

Upvotes: 33

aaronedmistone
aaronedmistone

Reputation: 999

The above answers are great, using some of these solutions* and some of my own ideas, I made a quick reference and some creative alternatives in the below fiddle.

*All credits given where due in the fiddle code

drawStrokedText   ( text, x, y );
drawShadowedText  ( text, x, y, shadowBlur);
drawGlowingText   ( text, x, y, glowColorHex, glowDistance);
drawBlurredText   ( text, x, y, blurAmount);
drawReflectedText ( text, x, y, reflectionScale, reflectionOpacity);

https://jsfiddle.net/vtmnyea8/

// Author: Aaron Edmistone
// Text effects using HTML5 Canvas with 2D Context.
// https://stackoverflow.com/questions/7814398/a-glow-effect-on-html5-canvas

var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');

// prepare the presentation of the canvas
ctx.fillStyle = 'black';
ctx.fillRect(0,0,250,450);
ctx.fillStyle = 'gray';
ctx.fillRect(250,0,250,450);
ctx.fillStyle = 'white';
ctx.fillRect(500,0,250,450);
ctx.fillStyle = '#0066CC';
ctx.fillRect(750,0,250,450);

// prepare the font and fill
ctx.font = "80px Sans-serif";
ctx.fillStyle = "white";

function drawStrokedText(text, x, y)
{
        // using the solutions from @Simon Sarris and @Jackalope from
    // https://stackoverflow.com/questions/7814398/a-glow-effect-on-html5-canvas
        ctx.save();
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 8;
    ctx.lineJoin="round";
      ctx.miterLimit=2;
    ctx.strokeText(text, x, y);
    ctx.fillText(text, x, y);
    ctx.restore();
}

function drawShadowedText(text, x, y, shadowBlur = 3)
{
        ctx.save();
    ctx.shadowBlur = shadowBlur;
    ctx.shadowColor = "#000000";
    ctx.shadowOffsetX = 4;
    ctx.shadowOffsetY = 4;
    ctx.fillText(text, x, y);
    ctx.restore();
}

function drawGlowingText(text, x, y, glowColorHexString, glowDistance = 10)
{
        ctx.save();
    ctx.shadowBlur = glowDistance;
    ctx.shadowColor = glowColorHexString;
    ctx.strokeText(text, x, y);
    
    for(let i = 0; i < 3; i++)
        ctx.fillText(text, x, y); //seems to be washed out without 3 fills
      
    ctx.restore();
}

function drawBlurredText(text, x, y, blur = 5)
{
    //using technique from https://www.html5rocks.com/en/tutorials/canvas/texteffects/
    ctx.save();
  let width = ctx.measureText(text).width + blur * 2;
  ctx.shadowColor = ctx.fillStyle;
  ctx.shadowOffsetX = width + x + ctx.canvas.width;
  ctx.shadowOffsetY = 0;
  ctx.shadowBlur = blur;
  ctx.fillText(text, -width + -ctx.canvas.width, y);
  ctx.restore();
}

function drawReflectedText(text, x, y, reflectionScale = 0.2, reflectionAlpha = 0.10)
{
    ctx.save();
  ctx.fillText(text, x, y);
    ctx.scale(1, -reflectionScale);
  ctx.globalAlpha = reflectionAlpha;
  ctx.shadowColor = ctx.fillStyle;
  ctx.shadowBlur = 15;
    ctx.fillText(text, x, -(y * (1 / reflectionScale)));
  ctx.restore();
}

for(let i = 0; i < 4; i++)
{
    drawStrokedText     ("MVW", 20 + i * 250, 80 * 1);
    drawShadowedText    ("MVW", 20 + i * 250, 80 * 2, 3);
  drawGlowingText       ("MVW", 20 + i * 250, 80 * 3, "#FF0000", 10);
  drawBlurredText       ("MVW", 20 + i * 250, 80 * 4, 5);
  drawReflectedText ("MVW", 20 + i * 250, 80 * 5, 0.5, 0.5);
}
<canvas id="myCanvas" width="1000" height="500"></canvas>

Output of the fiddle:

Output of the below fiddle

What it supports:

  • Outline text
  • Shadow text
  • Glowing text
  • Blurred text
  • Reflected text

Some performance metrics:

Considering using this in a game or at high frame rates? Check out this jsperf using the above methods.

https://jsperf.com/various-text-effects-html5-2d-context

Upvotes: 12

Simon Sarris
Simon Sarris

Reputation: 63802

What's wrong with stroke? Since half the stroke will be outside of the shape, you can always draw the stroke first with a line width of double what you want. So if you wanted a 4px outer stroke you could do:

function drawStroked(text, x, y) {
    ctx.font = '80px Sans-serif';
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 8;
    ctx.strokeText(text, x, y);
    ctx.fillStyle = 'white';
    ctx.fillText(text, x, y);
}


drawStroked("37°", 50, 150);

Which makes:

enter image description here

live fiddle here: http://jsfiddle.net/vNWn6/


IF that happens to not look as accurate at smaller text rendering scales, you can always draw it large but scale it down (in the above case you'd do ctx.scale(0.25, 0.25))

Upvotes: 83

Abaybay De Abaycious
Abaybay De Abaycious

Reputation: 111

For a smooth shadow you can try this

ctx.beginPath();
ctx.fillStyle = 'white';
ctx.font = "bold 9pt Tahoma";
ctx.shadowBlur = 3;
ctx.textAlign = "center";
ctx.shadowColor = "#000000";
ctx.shadowOffs = 0;                 
ctx.fillText('www.ifnotpics.com', 100, 50);        
ctx.closePath();

Upvotes: 5

Related Questions