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:
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:
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))
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!
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:
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
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();
Related
I am drawing five horizontal lines to an HMTL 5 2D canvas:
var canvas_ctx = my_canvas.getContext("2d");
canvas_ctx.lineWidth = 0.5;
canvas_ctx.strokeStyle = "black";
{
let line_x = 0;
let line_length = canvas_ctx.width;
let offset = 5;
let numLines = 5;
let numYincrement = 10;
for (let i=0;i<numLines * numYincrement;i+=numYincrement) {
//canvas_ctx.beginPath();
canvas_ctx.moveTo(line_x,i + offset);
canvas_ctx.lineTo(line_length,i + offset);
canvas_ctx.stroke();
//canvas_ctx.closePath();
}
}
This should, ideally result in 5 black lines. Instead, the color of the lines seems to fade with each new line (as if it's a gradient!), so that line 5 is gray. If I uncomment canvas_ctx.beginPath(); and canvas_ctx.closePath();, all lines become gray. Why is this happening??
Strokes do overlap from both sides of the coordinates.
var ctx = c.getContext('2d');
ctx.strokeStyle="red";
// draw big
ctx.scale(30, 30);
ctx.beginPath();
ctx.moveTo(5, 0);
ctx.lineTo(5, 10);
ctx.stroke();
drawPixelGrid();
function drawPixelGrid() {
// simply renders where the pixel bounds are
ctx.beginPath();
// remove the zoom
ctx.setTransform(1,0,0,1,0,0);
ctx.strokeStyle = 'gray';
ctx.lineWidth = 2; // avoid the problem we are demonstrating by using a perfect lineWidth ;-)
for(let y=0; y<=300; y+=30) {
ctx.moveTo(0, y);
ctx.lineTo(300, y);
for(let x=0; x<=300; x+=30) {
ctx.moveTo(x, 0);
ctx.lineTo(x, 300);
}
}
ctx.stroke();
}
<canvas id="c" height=300></canvas>
But obviously, a pixel can't be set to two colors at the same time. So browsers apply antialiasing, which will fade your pixel color to an other color, being the result of mixing the background and the foreground color.
So for a black stroke over a white or transparent background, this leads to actual gray pixels being rendered. Here I'll keep using red as an example:
var ctx = c.getContext('2d');
ctx.strokeStyle="red";
// first draw as on a 10*10 canvas
ctx.beginPath();
ctx.moveTo(5, 0);
ctx.lineTo(5, 10);
ctx.stroke();
// zoom it
ctx.imageSmoothingEnabled = 0;
ctx.globalCompositeOperation = 'copy';
ctx.drawImage(c, 0,0,9000,9000);
drawPixelGrid();
// this is not red...
function drawPixelGrid() {
ctx.globalCompositeOperation = 'source-over';
ctx.beginPath();
ctx.setTransform(1,0,0,1,0,0);
ctx.strokeStyle = 'gray';
ctx.lineWidth = 2;
for(let y=0; y<=300; y+=30) {
ctx.moveTo(0, y);
ctx.lineTo(300, y);
for(let x=0; x<=300; x+=30) {
ctx.moveTo(x, 0);
ctx.lineTo(x, 300);
}
}
ctx.stroke();
}
<canvas id="c" height=300></canvas>
One way to avoid it is generally to apply an offset on your coordinates so that the line extends correctly on pixels boundaries. E.g for a 1px lineWidth, you would apply a 0.5 offset:
var ctx = c.getContext('2d');
ctx.strokeStyle="red";
// first draw as on a 10*10 canvas
ctx.beginPath();
ctx.moveTo(5.5, 0); // offset +0.5px
ctx.lineTo(5.5, 10);
ctx.stroke();
// zoom it
ctx.imageSmoothingEnabled = 0;
ctx.globalCompositeOperation = 'copy';
ctx.drawImage(c, 0,0,9000,9000);
drawPixelGrid();
// now we've got a real red
function drawPixelGrid() {
ctx.globalCompositeOperation = 'source-over';
ctx.beginPath();
ctx.setTransform(1,0,0,1,0,0);
ctx.strokeStyle = 'gray';
ctx.lineWidth = 2;
for(let y=0; y<=300; y+=30) {
ctx.moveTo(0, y);
ctx.lineTo(300, y);
for(let x=0; x<=300; x+=30) {
ctx.moveTo(x, 0);
ctx.lineTo(x, 300);
}
}
ctx.stroke();
}
<canvas id="c" height=300></canvas>
But in your case, you are drawing at 0.5px lineWidth, so no offset will be able to get rid of this antialiasing.
So if you want perfect color, choose a correct lineWidth.
am having trouble doing something in Canvas - I want to create a grid that only exists within that shape. I am able to create the gird to fit the shape if it is a square but if the shape is an unusual shape I do not know how to create the grid.
Here is what I have so far: I only want the grid to exist within the shape
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75, 50);
ctx.lineTo(40, 65);
ctx.lineTo(100, 105);
ctx.lineTo(200, 15);
ctx.fillStyle = 'green';
ctx.fill();
function drawGrid(context) {
context.globalCompositeOperation = 'destination-out ';
for (var x = 40.5; x < 300; x += 10) {
context.moveTo(x, 0);
context.lineTo(x, 300);
}
for (var y = 0.5; y < 501; y += 10) {
context.moveTo(0, y);
context.lineTo(300, y);
}
context.strokeStyle = "#ddd";
context.stroke();
}
drawGrid(ctx)
https://jsfiddle.net/fom9gtb6/
context.globalCompositeOperation = 'destination-out ';
should be
context.globalCompositeOperation = 'destination-out';
You can use context.globalCompositeOperation = 'source-atop';
From MDN
The new shape is only drawn where it overlaps the existing canvas content.
This way the strokeStyle is actually used for the grid instead of erasing parts of the shape, which destination-out will do.
I am drawing a white rectangle on top of a bunch of filled blue circles. This is the code for filling a circle:
function fillCircle(color, radius, x, y){
console.log("New Circle With Color: " + color + " Radius: " + radius + "X: " + x + "Y: " + y);
ctx.save();
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fill();
ctx.restore();
}
This is the code that calls the above function:
var draw = function(){
//draws map with ships
for(var k = 1; k < 6; k++){
for(var i = 0; i < 24; i++){
var point = polarToReal(k, i * Math.PI / 12);
fillCircle("blue", 4, point[0], point[1]);
}
}
}
Finally, here is the code that draws a rectangle:
function winMessage(color, text){
ctx.fillStyle = "white";
ctx.fillRect(WIDTH/4, HEIGHT/4, WIDTH/2, HEIGHT/2)
ctx.font = WIDTH/20+"px Arial";
ctx.strokeStyle = "black";
ctx.rect(WIDTH/4, HEIGHT/4, WIDTH/2, HEIGHT/2);
ctx.stroke();
ctx.fillStyle = color;
ctx.textAlign = "center";
ctx.fillText(text, WIDTH/2, HEIGHT/2);
}
When I first call draw() and then winMessage(), the white rectangle has the outline of one of the circles showing through (see image). I am not sure why the stroke queue isn't cleared. Please use the jsbin I provided to see the issue more closely.
JSBIN
The last circle you draw is still saved by the canvas context, and it's being redrawn when you call ctx.stroke(). To clear it, you need to add a ctx.beginPath() before you draw your rectangle.
Alternatively you can use ctx.strokeRect() instead, and skip ctx.stroke().
Updated fiddle: https://jsfiddle.net/r7bcmLpq/4/
I want to change a line width but why all lines width in canvas is also changed?
Bellow is my code snippet
let c_canvas = document.getElementById("c");
let context = c_canvas.getContext("2d");
let gradientFill = context.createLinearGradient(400, 0, 95, 305);
gradientFill.addColorStop(0, "rgba(195, 42, 28, 1.000)");
gradientFill.addColorStop(0.6, "rgba(252, 239, 55, 1.000)");
gradientFill.addColorStop(1, "rgba(12, 151, 206, 1.000)");
context.fillStyle = gradientFill;
context.fillRect(0, 0, 500, 500);
context.beginPath();
for (let x = 0.5; x <= 501; x += 100) {
context.moveTo(x, 0);
context.lineTo(x, 500);
}
for (let y = 0.5; y <= 501; y += 100) {
context.moveTo(0, y);
context.lineTo(500, y);
}
context.lineWidth = 1;
context.stroke(); // Draw it
let frectx = 100;
let frecty = 450;
let lrectx = 250;
let lrecty = 340;
let radius = 15; // for example
let font = "bold " + radius + "px serif";
let text = "1";
let rand =[];
for(let i=0; i<5; i++)
{
rand[i] = Math.floor((Math.random() * 6) + 1);
}
rand.forEach(function(entry,i) {
text = i+1;
frectx = entry*70;
frecty = Math.floor((Math.random() * 9) + 1)*50;
context.moveTo(frectx, frecty);
context.lineTo(lrectx, lrecty);
context.lineWidth = 8;
context.strokeStyle = "#ddd";
context.stroke();
context.fillStyle = "white";
context.beginPath();
context.arc(frectx, frecty, 10, 0, 2 * Math.PI);
context.stroke();
context.closePath();
context.fill();
context.fillStyle = "black"; // font color to write the text with
context.font = font;
context.textBaseline = "top";
context.fillText(text, frectx - radius / 4, frecty - radius / 2);
context.fillStyle = "white";
context.beginPath();
context.arc(lrectx, lrecty, 10, 0, 2 * Math.PI);
context.stroke();
context.closePath();
context.fill();
context.fillStyle = "black"; // font color to write the text with
context.font = font;
context.textBaseline = "top";
context.fillText(text, lrectx - radius / 4, lrecty - radius / 2);
})
<canvas id="c" width="501px" height="501px"></canvas>
or you can see in jsfiddle:
https://jsfiddle.net/dyaskur/t4fgLs73/
How to only change width in that lines inside the box?
My second question is how to make my line and circle transform to glow/change color when i hover on it?
A context.beginPath() is missing between
context.lineWidth = 1;
context.stroke(); // Draw it
and
context.lineTo(lrectx, lrecty);
context.lineWidth = 8;
context.strokeStyle = "#ddd";
context.stroke();
Without the beginPath call you are simply re-stroking all or the paths and subpaths already defined with the new stroke style and width.
The general answer to the second part of your question is that you don't get to do this.
Painting a canvas is equivalent to drawing an image. You can work out where the mouse is over the image, but you would then need to work out (in your program) if the mouse is over some pixels you want to change and redraw the canvas if it is.
If you want to use CSS :hover pseudo classes to change presentation you will need to construct the source code of an SVG element for the graphic, create the element from the source code generated, and supply appropriate CSS for the SVG element's child nodes that will be affected by mouse position.
How can I stop filling my circle at some point?
Like only fill 50% of the circle or 25% of the circle and leave the left over. Just like progress bar.
below is the code Im using but it is filling entire circle.
Kindly please give me suggestions.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
ctx.lineCap = "round";
var y = ch - 10;
var drawingColor = "#0092f9";
animate();
function animate() {
if (y > 0) {
requestAnimationFrame(animate);
}
ctx.clearRect(0, 0, cw, ch);
ctx.save();
drawContainer(0, null, null);
drawContainer(15, drawingColor, null);
drawContainer(7, "white", "white");
ctx.save();
ctx.clip();
drawLiquid();
ctx.restore();
ctx.restore();
y--;
}
function drawLiquid() {
ctx.beginPath();
ctx.moveTo(0, y);
for (var x = 0; x < 300; x += 1) {
ctx.quadraticCurveTo(x + 5, y+5, x + 5, y);
}
ctx.lineTo(cw, ch);
ctx.lineTo(0, ch);
ctx.closePath();
ctx.fillStyle = drawingColor;
ctx.fill();
}
function drawContainer(linewidth, strokestyle, fillstyle) {
ctx.beginPath();
ctx.arc(cw/2, ch/2,cw/2-30,0,2*Math.PI);
ctx.lineWidth = linewidth;
ctx.strokeStyle = strokestyle;
ctx.stroke();
if (fillstyle) {
ctx.fillStyle = fillstyle;
ctx.fill();
}
}
<canvas id="canvas" width=200 height=200></canvas>
I know zit at javascript but: Just change the limit of y in animate. I put 100 instead of 0 and it fills half the circle.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
ctx.lineCap = "round";
var y = ch - 10;
var drawingColor = "#0092f9";
animate();
function animate() {
if (y > 100) {
requestAnimationFrame(animate);
}
ctx.clearRect(0, 0, cw, ch);
ctx.save();
drawContainer(0, null, null);
drawContainer(15, drawingColor, null);
drawContainer(7, "white", "white");
ctx.save();
ctx.clip();
drawLiquid();
ctx.restore();
ctx.restore();
y--;
}
function drawLiquid() {
ctx.beginPath();
ctx.moveTo(0, y);
for (var x = 0; x < 300; x += 1) {
ctx.quadraticCurveTo(x + 5, y+5, x + 5, y);
}
ctx.lineTo(cw, ch);
ctx.lineTo(0, ch);
ctx.closePath();
ctx.fillStyle = drawingColor;
ctx.fill();
}
function drawContainer(linewidth, strokestyle, fillstyle) {
ctx.beginPath();
ctx.arc(cw/2, ch/2,cw/2-30,0,2*Math.PI);
ctx.lineWidth = linewidth;
ctx.strokeStyle = strokestyle;
ctx.stroke();
if (fillstyle) {
ctx.fillStyle = fillstyle;
ctx.fill();
}
}
<canvas id="canvas" width=200 height=200></canvas>