How do I make text gradient in canvas? - javascript

I want to fill my text with a top from bottom gradient. I've followed four or five different tutorials on how to achieve this but it doesn't work.
I have two different dynamic text sources, something like this:
<div id="first-name" contentEditable="true">Olaf</div>
<div id="last-name" contentEditable="true">Smith</div>
I want to draw whatever the user writes on the canvas (after a button press). I know how to fetch the values, so let's ignore that part and focus on two things:
1) How to know how wide the content is to fill with a gradient.
2) How to fill anything with a gradient to begin with...
They say this is how you should do it:
var d_canvas = document.getElementById('canvas')
var context = d_canvas.getContext('2d')
var firstNameGradient = context.createLinearGradient(6,38,6,70) //no idea what values I should use
firstNameGradient.colorStop(0, '#eede85')
firstNameGradient.colorStop(1, '#fea700')
context.fillStyle = firstNameGradient
context.font = "bold 26px Tahoma"
context.fillText(firstName, 6, 38)
context.font = "bold 36px Tahoma"
context.fillText(lastName, whereFirstNameEnds, 38)
However, whatever values I try to fill with it doesn't work. In, fact nothing gets drawn in at all.
So I have no idea how to solve either 1) or 2). Does anyone know?
Edit: Just noticed an error:
TypeError: firstNameGradient.colorStop is not a function

context.createLinearGradient(6,38,6,70) //no idea what values I should use
This is defining a line from (x1, y1) to (x2, y2) which in turn describes the angle of the gradient. Seems OK here, might need some fine tuning.
Change these:
firstNameGradient.colorStop(0, '#eede85')
firstNameGradient.colorStop(1, '#fea700')
to use addColorStop() instead
firstNameGradient.addColorStop(0, '#eede85')
firstNameGradient.addColorStop(1, '#fea700')
To find the width (in pixels) you can use measureText() and the property width of the returned object:
var tw = firstNameGradient.measureText(firstName).width;
Text is by default drawn with y representing the baseline. You can change this by setting text-baseline to top - this makes it easier to provide the gradient line:
firstNameGradient.textBaseline = "top";
The specs do provide a way to get the height of a font as well, but no browers has yet implemented this part so you need to either guess or use a DOM element to measure height.

Sorry for being so naive as to use w3Schools, but If you reverse engineer this snippet you should get the job done:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.font="20px Georgia";
ctx.fillText("Hello World!",10,50);
ctx.font="30px Verdana";
// Create gradient
var gradient=ctx.createLinearGradient(0,0,c.width,0);
gradient.addColorStop("0","magenta");
gradient.addColorStop("0.5","blue");
gradient.addColorStop("1.0","red");
// Fill with gradient
ctx.fillStyle=gradient;
ctx.fillText("Big smile!",10,90);
Should you need any more advice check it out here
NOTE: Please, for the love of all things holy, don't use w3Schools, please try to use MDN
NOTE 2: I have no experience with canvas, so if I am making myself out to be an idiot, I am very sorry.

You can use ctx.measureText(txt) to get the right width for the canvas. Here's an example that works as required:
var context = document
.querySelector('canvas')
.getContext('2d');
var pos = {x: 50, y: 50};
var txt = 'Hello!';
var txtHeight = 50;
context.font = txtHeight + 'px Verdana';
var txtWidth = context.measureText(txt).width;
var gradient = context.createLinearGradient(
pos.x, pos.y, txtWidth, txtHeight);
gradient.addColorStop(0,"red");
gradient.addColorStop(1,"blue");
context.fillStyle = gradient;
context.fillText(txt,pos.x,pos.y);
And here's the demo on JSFiddle

Related

Is there a bug in HTML Canvas' fillText function?

I just came across some unexpected behaviour on the HTML Canvas element; I tried to strip the problem down as much as I could. In short, it appears the ctx.fillText fails to render the text in specific regions of the canvas.
This is the smallest script I could write to consistently reproduce the bug (I tested it on different machines, OSs and browsers). It creates a black canvas (1.25×1.25 in drawing space units, 1000×1000 in pixels, the drawing space origin is in the middle) and draws red dots as the mouse passes over it, but there are several horizontal stripes in which it fails to do so.
// define boundaries of drawing space
const left = -.625;
const tops = -.625;
const scale = 800;
const width = 1.25;
const height = 1.25;
// create canvas
let canvas = document.createElement("canvas");
canvas.width = scale * width;
canvas.height = scale * height;
canvas.style.backgroundColor = "black";
document.body.appendChild(canvas);
// create context
let ctx = canvas.getContext("2d");
ctx.scale(scale, scale);
ctx.translate(-left, -tops);
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.font = `.03px arial`;
ctx.fillStyle = "rgba(208, 64, 64, 1)";
// coordinates follow mouse
addEventListener("mousemove", event => {
let mouseX = left + event.layerX / scale;
let mouseY = tops + event.layerY / scale;
ctx.fillText(".", mouseX, mouseY);
});
You can paste it in your dev-tools in about:blank and see for yourself. In any case here's a gif too:
As you can see, there are several horizontal stripes left untouched, even though I pass over them with the mouse. Also, the entirety of the bottom 80% of the canvas is unaffected.
A few important notes:
This occurs with any text, but I used the dot because it takes up the least amount of space, while bigger symbols easily bridged the gaps in the upper portion and made it more difficult to see.
The mousemove event is not the culprit, mouseX and mouseY update properly and smoothly.
It is not due to my mouse being dragged too quickly, it leaves the gaps no matter how slow I move.
It is not due to how small the scale is, as the x dimension has the same scaling as the y dimension, but the former doesn't present this issue.
This does not occur with the ctx.strokeText method, which works fine.
It also does not occur with ctx.fillPath.
Am I doing something wrong? Or is this actually a bug?
The problem is the extremely small font size you're trying to render. You shouldn't use values like .03px - it makes sense for renderers to not be able to render something like that correctly, considering the typical smallest paintable size on a display is 1 pixel (a little smaller than that on high DPI displays, but probably not much less than .25px). It may work for painting simple lines, but rendering is more complicated than that (e.g. hinting).
Try the following values:
scale = 80
width = 12.5
height = 12.5
ctx.font = `.3px arial`;
Alternatively, try to paint a dot or a square rather than a "." string.
As a side note, I was able to reproduce the problem on Chrome, but on Firefox it actually renders fine.

How to centrally align text in HTML canvas?

I was making a small JavaScript game when I ran into this small issue: The text is not centrally aligned.
A simple example:
var txt="Lorem Ipsum";
context.fillText(txt,100,100);
Now the problem is that the beginning of the text is at the point 100,100. So later when I change the value of txt into a longer sentence, it is still drawn staring from 100,100 , reducing the aesthetic appeal of the program.
My question is, is there a way to draw text in such a way that the coordinates given mark the center of the text and not the beginning?
You can use textAlign:
context.textAlign = "center";
You can use the measureText() method of the context, to get the width of the text, and then make adjustments based on it. So let's suppose that you want to put it in the center of the canvas, then
(canvas.width / 2) - (ctx.measureText(txt).width / 2)
gives the position where the text should go. In the vertical case:
(canvas.height / 2) - (ctx.measureText(txt).height / 2)
Where canvas is the HTML5 Canvas itself, and ctx is the 2DContext object:
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");

Html 5 canvas and background

after trying a few ways to preserve a background under a canvas drawing a moving rectangle (animation code not reproduced), here is my best try :
canvas {background-image:url('background.png');}
var x,y, pixels;
function draw() {
if(pixels) {
context.putImageData(pixels,x,y);
}
x = //calculate new X
y = //calculate new Y
pixels = context.getImageData(x, y, 10, 10);
context.fillStyle = 'red';
context.fillRect(x, y, 10, 10);
}
My first question is : why won't replacing the first two lines of draw() with :
context.fillStyle = 'rgba(10,10,10,0)';
context.fillRect(x,y,10,10);
work to clear the previously drawn pixels?
And my second question is : is there really no better way than get and putImageData(), which are very labor-intensive?
EDIT: in particular is there a div containing the background image trick that would maybe work without the get and putImageData calls?
Thanks!
By default, whatever you draw gets drawn on top of each other, instead of being replaced.
Use context.clearRect(x,y,10,10); instead.
There is also the context.globalCompositeOperation property, which could be set to "destination-out" before using fillRect .. but you're better off with clearRect.

Using Canvas fillText() - HTML5, JavaScript

I have a plugin that will fill text based on an image using the following:
function draw() {
//grab font to use
var fFamily = $(".activeText a .fontType-cont").val();
ctx.font = startFontSize + 'px ' + fFamily;
ctx.fillText(text, 10, 100);
ctx.globalCompositeOperation = 'source-in';
ctx.drawImage(img, 10, 20, 500, 100);
}
var canvas = document.getElementById(current),
ctx = canvas.getContext('2d'),
img = document.createElement('img');
//draw it
img.onload = draw;
img.src = $(".activeGColor").find('img').attr('src');
What happens is that the canvas will take that image, display it in the canvas and as the text gets bigger, you see it as the fill for the text. My issue is that, How can I make it so it does not look stretched or whatnot? Do I have an option to do so?
What are your ideas?
We need to see an example of whats going on here. Your question makes it sound like you have some sort of animation going on... that changes the font-size to a larger and larger size. The way things work by default, is that the text will render proportionally. However if your using any thing like ctx.scale() you obviously could throw that off. Also if your not clearing your canvas each frame, you could get more of a bleed effect, that you may be describing as stretching.

creating custom drawing with jquery

Here is my code:
$('#add_shape').click(function() {
var rectHeight = $('#rect_height_input').val();
var rectWidth = $('#width_input').val();
$('<canvas>').attr({
id: 'canvas'
}).css({
height: function() {
if (rectHeight > 0) {
return rectHeight + 'px';
}
else {
return rectWidth + 'px';
}
},
width: rectWidth + 'px'
}).appendTo('#work_area');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.fillStyle = $('#color_list option:selected').val();
ctx.fillRect(0, 0, rectWidth, rectHeight);
});
When the #add_shape button is clicked, the rectangle does not show up. What am I missing here? Please help.
In response to previous revision:
You retrieved existing canvas element using document.getElementById() and then you created another one using jQuery $('<canvas>') and appended it to #work_area.
Change $('<canvas>') into $(canvas) to use the already existing canvas.
Did you really want to call document.getElementById('canvas') instead of document.createElement('canvas')? In the 1st case there must be already a canvas element in the DOM with the id="canvas" which looks suspicious.
Edit #1 (in response to current revision):
If that was just a wrote code in the answer then check your HTML (which you should also provide). Your code is working as demonstrated in this fiddle.
Make sure that IDs are correct - You are using #rect_height_input for height, but #width_input for width and double-check values for the color options - maybe the rectangle is drawin using white color.
Edit #2 (in response to the fiddle in comments):
For each shape you create a new canvas element and all those elements have same (!) id. This is incorrect. Attribute id of an element should be unique. In your code you will always get the first canvas and draw on it - now matter how many shapes you crated. Rest of canvas elements are empty.
Your code is drawing rectangles correctly (apart from the "same canvas" problem). Try to draw rectangle with big height and width - it is working. When the width/height are very small then the canvas is too small to show the rectangle. Setting minimum width/height or using one big canvas and drawing shapes onto it is a way to go.

Categories

Resources