I'm currently trying to create a speechbubble for my canvas using javascript.
Problem 1:
I'm adding the width in the following way: ctx.measureText(text).width + 40 to make sure the width of the speechbubble is flexible and depends on the var text = 'Speechbubble test'; input. Also adding 40 pixels to add a margin left and right!
How could I get the height of the text to make it flexible also? I've been checking but couldn't find something similare to the measureText method and it doesn't seem to exist.
Is there any way to get the actual height of the text? I'm asking because I'm planning to add a line break to it. eG: multi-line support (Break the text line after a specific amount of letters).
Problem 2:
I'm trying to add a little speech bubble direction pointer to the speechbubble. Sorry for the way I'm describing it this way, don't know the word for it. But I'm talking about the following:
How could I add the following to my speechbubble?
I appreciate any sugestions and help.
The current example:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext("2d");
ctx.font = "13px Helvetica";
var text = 'Speechbubble test';
component(ctx, ctx.measureText(text).width, 70, "#37f", 10, 10, 16, text);
function component(ctx, width, height, color, x, y, radius, text)
{
// Variables
var width = width + 40;
var height = height;
var x = x;
var y = y;
var radius = Math.min(radius, Math.min(width, height) / 2);
var color = color;
var pi2 = Math.PI * 2;
var r = radius;
var w = width;
var h = height;
// Transparent background
//ctx.fillStyle = "rgba(255, 255, 255, 0.5)";
ctx.fillStyle = color;
ctx.setTransform(1, 0, 0, 1, x, y);
// Draw arc
ctx.beginPath();
ctx.arc(r , r , r, pi2 * 0.5 , pi2 * 0.75);
ctx.arc(w - r, r, r, pi2 * 0.75, pi2);
ctx.arc(w - r, h - r, r, 0, pi2 * 0.25);
ctx.arc(r , h - r, r, pi2 * 0.25, pi2 * 0.5);
ctx.fill();
ctx.setTransform(1, 0, 0, 1, 0, 0);
// Text fillstyle
ctx.fillStyle = "#fff";
ctx.fillText(text, x + 20, y + 40);
}
<canvas width="500" height="500" id="canvas"></canvas>
The problem is that although the specs defines a way to measure height via a the TextMetrics object, no vendor has actually implemented it at current time.
So we have to cheat a bit to get the actual size from the font.
One way to do this is to temporary insert a DOM element and get the height from that:
console.log(getTextHeight("Hello", "20px sans-serif"));
function getTextHeight(txt, font) {
var el = document.createElement('div'), height;
el.style.cssText = "position:fixed;padding:0;left:-9999px;top:-9999px;font:" + font;
el.textContent = txt;
document.body.appendChild(el);
height = parseInt(getComputedStyle(el).getPropertyValue('height'), 10);
document.body.removeChild(el);
return height
}
For use with canvas just pass in context.font as your font argument:
var fontHeight = getTextHeight("Hello", ctx.font);
As to problem two:
You can break up the bottom line path so to speak, of the rounded rectangle by inserting lineTo() calls, like:
var ctx = c.getContext("2d"), r = 20, w = 290, h = 100, pi2 = Math.PI*2;
var ap = w * 0.2, aw = 20, ah = 30; //arrow position, width, height
ctx.translate(1.5,0.5);
// Rounded rectangle
ctx.beginPath();
ctx.arc(r , r , r, pi2 * 0.5 , pi2 * 0.75);
ctx.arc(w - r, r, r, pi2 * 0.75, pi2);
ctx.arc(w - r, h - r, r, 0, pi2 * 0.25); // bottom right corner
ctx.lineTo(w - ap, h); // inserts an arrow (following clock-wise)
ctx.lineTo(w - ap, h + ah);
ctx.lineTo(w - ap - aw, h);
ctx.arc(r , h - r, r, pi2 * 0.25, pi2 * 0.5); // bottom left corner
ctx.closePath();
ctx.stroke();
<canvas id=c></canvas>
Related
i need help to create a function to create x quant of lines with circles in each end of the line using parameters to define the angle of rotation, width,height and color of the line and fill the space between the lines, the propours of this is making a kind of rotation max and min angle of the human arm and shoulder.
this is a image of ilustrative example what i need to do, the image of the person model is fine in png i need just to create dynamic lines.
this is the code i have so far:
function drawLine(deg,width,height,canvasId,color){
const canvas = document.getElementById(canvasId);
const ctx = canvas.getContext('2d');
ctx.rotate(deg);
ctx.fillStyle = color;
ctx.fillRect(0, 0, width, height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
drawLine(0,200,3,'canvas','red')
drawLine(120,200,3,'canvas1','blue')
canvas{
position: absolute;
}
<canvas id="canvas"></canvas>
<canvas id="canvas1"></canvas>
thanks very much
Based on the picture I will assume 180deg is the reference point of 0. Anything out from there is where we start counting degrees. If that is the case you will want to run a function that calculates the angle between a solid line at 180deg and two other lines from that point.
In total you will need four points. You starting point for all references (pointB in this example), another point (pointD) will be used to set the angle reference to 0 degrees. We will measure the next two angles from this line.
PointA and PointC can be adjusted as needed and we then calculate the angle from pointD/pointB vector. We can use Math.atan2 to calculate the angles of BA and BC move away from BD.
let angle1 = Math.atan2(distBC_x * distBD_y - distBC_y * distBD_x, distBC_x * distBD_x + distBC_y * distBD_y);
In this snippet I changed the color of the lines to make it easier to see what is what. You also won't need to draw the pink line. This is static so you will need to create limits and a dynamic method to change the angles.
Change the x value of pointA and pointC to change the Min and Max. Keep in mind I have to restrictions set for you to be able to switch them.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 500;
//change pointA and pointC x value.
//pointA sould be your Min
//PointC should be your Max
let pointA = {x: 200, y: 250};
let pointB = {x: 250, y: 100}; //common point
let pointC = {x: 120, y: 250};
//only used to set reference to 0 at 180 degrees
let pointD = {x: pointB.x, y: pointB.y + 150};
//creating our vectors length
let distBA_x = pointB.x - pointA.x;
let distBA_y = pointB.y - pointA.y;
let distBC_x = pointB.x - pointC.x;
let distBC_y = pointB.y - pointC.y;
let distBD_x = pointB.x - pointD.x;
let distBD_y = pointB.y - pointD.y;
//calculate angle between pink and purple
let angle1 = Math.atan2(distBC_x * distBD_y - distBC_y * distBD_x, distBC_x * distBD_x + distBC_y * distBD_y);
if(angle1 < 0) {angle1 = angle1 * -1;}
let degree_angle1 = angle1 * (180 / Math.PI);
//calculate angle between purple and red
let angle2 = Math.atan2(distBA_x * distBD_y - distBA_y * distBD_x, distBA_x * distBD_x + distBA_y * distBD_y);
if(angle2 < 0) {angle2 = angle2 * -1;}
let degree_angle2 = angle2 * (180 / Math.PI);
function draw() {
ctx.textStyle = 'black';
ctx.font = '20px Arial';
ctx.fillText('Max = '+ degree_angle1, 100, 20);
ctx.fillText('Min = '+ degree_angle2, 100, 50);
//Lines
ctx.strokeStyle = 'purple';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(pointA.x, pointA.y);
ctx.lineTo(pointB.x, pointB.y);
ctx.stroke();
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.moveTo(pointB.x, pointB.y);
ctx.lineTo(pointC.x, pointC.y);
ctx.stroke();
ctx.strokeStyle = 'pink';
ctx.beginPath();
ctx.moveTo(pointB.x, pointB.y);
ctx.lineTo(pointD.x, pointD.y);
ctx.stroke();
//Points
ctx.fillStyle = 'purple';
ctx.beginPath();
ctx.arc(pointB.x, pointB.y, 5, 0, Math.PI*2);
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(pointA.x, pointA.y, 5, 0, Math.PI*2);
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(pointC.x, pointC.y, 5, 0, Math.PI*2);
ctx.fill();
ctx.closePath();
//Fill
ctx.fillStyle = 'rgba(113, 0, 158, 0.3)'
ctx.beginPath();
ctx.moveTo(pointA.x, pointA.y);
ctx.lineTo(pointB.x, pointB.y);
ctx.lineTo(pointC.x, pointC.y);
ctx.fill();
ctx.closePath();
}
draw()
<canvas id='canvas'></canvas>
Problem: Im drawing a spaceship on the canvas. Upon hovering over it's x/y, im drawing an arc on the canvas, indicating the starships weapons angle and range (considering the starships current Baring/facing). Currently the determined angle is being drawn in green and extends as far as the weapons range value allows.
However, i would like to use a gradiant to fill the determined arc to indicate a drop-off in accuracy (i.e. gradiant begins at green, moves to orange, turns red the further away from the starships Position the angle is).
However, i dont know how i could replace my stock ctx.fill() on the drawn arc with a gradiant.
var ship {
loc: {x, y}, // lets say 100, 100
facing: facing // lets say facing 0, i.e. straight right
weapons: objects (range, startArc, endArc) // lets say 50, 300, 60 -> 120 degree angle, so -60 and +60 from facing (0/360)
}
for (var i = 0; i < weapon.arc.length; i++){
var p1 = getPointInDirection(weapon.range, weapon.arc[i][0] + angle, pos.x, pos.y);
var p2 = getPointInDirection(weapon.range, weapon.arc[i][1] + angle, pos.x, pos.y)
var dist = getDistance( {x: pos.x, y: pos.y}, p1);
var rad1 = degreeToRadian(weapon.arc[i][0] + angle);
var rad2 = degreeToRadian(weapon.arc[i][1] + angle);
fxCtx.beginPath();
fxCtx.moveTo(pos.x, pos.y);
fxCtx.lineTo(p1.x, p1.y);
fxCtx.arc(pos.x, pos.y, dist, rad1, rad2, false);
fxCtx.closePath();
fxCtx.globalAlpha = 0.3;
fxCtx.fillStyle = "green";
fxCtx.fill();
fxCtx.globalAlpha = 1;
}
is it possible to replace the arc/globalalpha/fill so use a gradiant flow instead of it being colored fixed and if so, how ?
thanks
To fill an arc with a gradient, animated just for the fun.
Uses a radial gradient and set colour stops as a fraction of distance.
The function createRadialGradient takes 6 numbers the position x,y and start radius and the position x,y and end radius of the gradient.
Colour stops are added via the gradient object addColorStop function that takes a value 0 inner to 1 outer part of the gradient and the colour as a CSS color string. "#F00" or "rgba(200,0,0,0.5)" or "RED"
Then just use the gradient as the fill style.
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
function update(time) {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// position of zones in fractions
var posRed = 0.8 + Math.sin(time / 100) * 0.091;
var posOrange = 0.5 + Math.sin(time / 200) * 0.2;
var posGreen = 0.1 + Math.sin(time / 300) * 0.1;
var pos = {
x: canvas.width / 2,
y: canvas.height / 2
};
var dist = 100;
var ang1 = 2 + Math.sin(time / 1000) * 0.5;
var ang2 = 4 + Math.sin(time / 1300) * 0.5;
var grad = ctx.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, dist);
grad.addColorStop(0, "#0A0");
grad.addColorStop(posGreen, "#0A0");
grad.addColorStop(posOrange, "#F80");
grad.addColorStop(posRed, "#F00");
grad.addColorStop(1, "#000");
ctx.fillStyle = grad;
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
ctx.arc(pos.x, pos.y, dist, ang1, ang2);
ctx.fill();
requestAnimationFrame(update);
}
requestAnimationFrame(update);
I'm making a project using canvas and svg. I've drawn a pattern using canvas with 4 Circles and in each 4 circles there's one inner circle. The problem is, I now need to make those 4 circles and inner circles smaller in order to insert more of them, up to 30, on my screen. Here's my code.
function telaCirculos(x,y,r,angIn,angFim,corFundo,corLinha){
pintor.fillStyle=corFundo;
pintor.strokeStyle=corLinha;
pintor.beginPath();
pintor.arc(x,y,r,angIn,angFim);
pintor.closePath();
pintor.stroke(); pintor.fill();
}
then I just call my function in the script like so:
telaCirculos(250,500,250,Math.PI,-2*Math.PI,"#449779","#449779");
telaCirculos(250,500,200,Math.PI,-2*Math.PI,"#013D55","#013D55");
telaCirculos(500,250,250,Math.PI/2,3*Math.PI/2,"#E6B569","#E6B569");
telaCirculos(500,250,200,Math.PI/2,3*Math.PI/2,"#AA8D49","#AA8D49");
telaCirculos(0,250,250,Math.PI/2,-3*Math.PI/2,"#E6B569","#E6B569");
telaCirculos(0,250,200,Math.PI/2,-3*Math.PI/2,"#AA8D49","#AA8D49");
telaCirculos(250,0,250,0,-Math.PI,"#449779","#449779");
telaCirculos(250,0,200,0,-Math.PI,"#013D55","#013D55");
This draws the circles with my desired coordinates. Now I need to fill my screen with more of these. I'll post some screenshots.
What I have done:
What I need to do:
An alternative to GameAlchemist's solution, is to think of the pattern as rows and columns of circles. You can use nested loops to draw the rows and columns of circles. Each row partially overlaps the previous row by half a circle. Every other row offset is horizonally offset by half a circle. Since rows overlap, each row covers a vertical distance of radius. To compute number of rows, you basically divide height by radius. Since the columns do not overlap, each column covers a horizonal distance of 2 * radius. To compute number of columns, you basically divide width by radius. Since the first circle can be half outside the area being painting, you actually have to add radius to height and width before dividing by 2 * radius and radius, respectively. You can use arrays to hold the colors and offsets. Then the function to fill a rectangular area with circles could look like...
function drawCircles(x, y, width, height, outerRadius, innerRadius) {
var outerColors = ["#449779", "#E6B569"];
var innerColors = ["#013d55", "#AA8D49"];
var offsets = [0, outerRadius];
var startAngle = 0;
var endAngle = 2 * Math.PI;
var iMax = (width + outerRadius) / (outerRadius);
for (i = 0; i < iMax; i++) {
var outerColor = outerColors[i % outerColors.length];
var innerColor = innerColors[i % innerColors.length];;
var offset = offsets[i % offsets.length];
var jMax = (height + outerRadius - offset) / (2 * outerRadius);
for (j = 0; j < jMax; j++) {
var cx = x + j * 2 * outerRadius + offset;
var cy = y + height - i * outerRadius;
telaCirculos(cx, cy, outerRadius, startAngle, endAngle, outerColor, outerColor)
telaCirculos(cx, cy, innerRadius, startAngle, endAngle, innerColor, innerColor);
}
}
}
You would then call the function with the dimensions of the canvas and the desired size of circles...
var outerRadius = canvasWidth / numberOfCircles;
var innerRadius = 0.8 * outerRadius;
drawCircles(0, 0, canvasWidth, canvasHeight, outerRadius, innerRadius);
Your best take here is to create a pattern out of your circle, then to fill your main canvas with that pattern.
To do that, create a temporary canvas on which you draw your pattern then create a pattern out of it.
document.body.style.margin = 0;
var cv = document.getElementById('cv');
var mainContext = cv.getContext('2d');
var canvasWidth;
var canvasHeight;
function update() {
canvasWidth = cv.width = window.innerWidth;
canvasHeight = cv.height = window.innerHeight;
drawCircles();
}
update();
window.onresize = update;
function drawCircles() {
mainContext.save();
// full screen rect
mainContext.rect(0, 0,
mainContext.canvas.width, mainContext.canvas.height);
// scale to put more circles inside ( :-) )
mainContext.scale(1 / 10, 1 / 10);
mainContext.fillStyle = buildPattern();
mainContext.fill();
mainContext.restore();
}
function buildPattern() {
var tempCv = document.createElement('canvas');
tempCv.width = 500;
tempCv.height = 500;
var pintor = tempCv.getContext('2d');
telaCirculos(250, 500, 250, Math.PI, -2 * Math.PI, "#449779", "#449779");
telaCirculos(250, 500, 200, Math.PI, -2 * Math.PI, "#013D55", "#013D55");
telaCirculos(500, 250, 250, Math.PI / 2, 3 * Math.PI / 2, "#E6B569", "#E6B569");
telaCirculos(500, 250, 200, Math.PI / 2, 3 * Math.PI / 2, "#AA8D49", "#AA8D49");
telaCirculos(0, 250, 250, Math.PI / 2, -3 * Math.PI / 2, "#E6B569", "#E6B569");
telaCirculos(0, 250, 200, Math.PI / 2, -3 * Math.PI / 2, "#AA8D49", "#AA8D49");
telaCirculos(250, 0, 250, 0, -Math.PI, "#449779", "#449779");
telaCirculos(250, 0, 200, 0, -Math.PI, "#013D55", "#013D55");
var pattern = pintor.createPattern(tempCv, 'repeat');
return pattern;
function telaCirculos(x, y, r, angIn, angFim, corFundo, corLinha) {
pintor.fillStyle = corFundo;
pintor.strokeStyle = corLinha;
pintor.beginPath();
pintor.arc(x, y, r, angIn, angFim);
pintor.closePath();
pintor.stroke();
pintor.fill();
}
}
<canvas id='cv'></canvas>
I am working on creating cylinder using canvas and I have coded for to fill the cylinder (animate) based on the percentage.
When I enter value in the textbox it start fill in back wards (Fill). But it has to fill (Green) in front direction direction how the top of the cylinder look like.
If we change the cy value can direction of fill will change. Any Suggestion / solution
Here is the Draw Cylinder code
function draw()
{
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// draw grey main cylinder
drawCylinder(cx, cy, width, height, height / 8, '#b1b1b1', '#000');
// draw green progression cylinder
var currHeight = someValue * height / 100;
var newCenterY = cy - currHeight / 2 + height / 2;
//Here is where green fill starts
drawCylinder(cx, newCenterY, width, currHeight, height / 4, '#81ae45', null);
// draw (fill and stroke) the top of the progression cylinder
drawCylinderTop(cx, newCenterY, width, 100, height / 4, '#9a9a9a', null);
// stroke the top of the main cylinder
}
Here is my code for draw cylinder
function drawCylinder(x, y, w, h, vRadius, fillStyle, strokeStyle) {
var w2 = w / 2;
var h2 = h / 2;
var ytop = -h2;
var cpYtop = -h2 - vRadius;
var ybottom = h2;
var cpYbottom = h2 + vRadius;
ctx.save();
ctx.translate(x, y);
ctx.beginPath();
ctx.moveTo(-w2, ytop);
ctx.bezierCurveTo(-w2, cpYtop, w2, cpYtop, w2, ytop);
ctx.lineTo(w2, ybottom);
ctx.bezierCurveTo(w2, cpYbottom, -w2, cpYbottom, -w2, ybottom);
ctx.closePath();
performDraw(fillStyle);
ctx.restore();
}
If i try to change the bezierCurveTo in to static value still it was not changing.
I don't have reputation to put exact output image what I want.
Here is the updated fiddle link.
I have modified the below line of code
var newCenterY = cy + currHeight / 2 + height / 2;
Previously it was
var newCenterY = cy - currHeight / 2 + height / 2;
I am struggling to change the direction of fill color help needed.
Please do the needful.
Fiddle Link
Thanks in advance
M
I have added one more function two change the direction of filling
I have created one more function call drawCylinderprogress
function drawCylinderprogress(x, y, w, h, vRadius, fillStyle, strokeStyle) {
var w2 = w / 2;
var h2 = h / 2;
var ytop = -h2;
var cpYtop = -h2 - vRadius;
var ybottom = h2;
var cpYbottom = h2 + vRadius;
ctx.save();
ctx.translate(x, y);
ctx.beginPath();
ctx.moveTo(-w2, ytop);
ctx.bezierCurveTo(-w2, cpYtop/360, w2, cpYtop/360 , w2 , ytop);
ctx.lineTo(w2, ybottom);
ctx.bezierCurveTo(w2, cpYbottom, -w2, cpYbottom, -w2, ybottom);
ctx.closePath();
performDraw(fillStyle);
ctx.restore();
}
Regards
Mahadevan
I am trying to cut an image into a particular shape using the canvas clip() method.
I have followed the following steps to do so:
Draw a rectangle.
Draw semi circles on each side. The right and bottom semi circles protrude outwards and the left and top semi circles are inwards.
The code snippet is provided below:
var canvasNode = this.hasNode();
var ctx = canvasNode && canvasNode.getContext('2d');
var image = new Image();
image.onload = function() {
ctx.drawImage(image, 0, 0, 512, 384);
};
image.src = "images/image.png";
var startX = 200;
var startY = 0;
var rectWidth = 150;
var rectHeight = 150;
var radius = 30;
//Main Rect
ctx.rect(startX, startY, rectWidth, rectHeight);
//Right arc
ctx.moveTo(startX+=rectWidth, startY+=(rectHeight/2));
ctx.arc(startX, startY, radius, 3 * Math.PI / 2, Math.PI / 2, false);
//Left arc
ctx.moveTo(startX-=(rectWidth / 2), startY+=(rectHeight / 2));
ctx.arc(startX, startY, radius, 0, Math.PI, true);
ctx.moveTo(startX-=(rectWidth / 2), startY-=(rectWidth / 2));
ctx.arc(startX, startY, radius, 3 * Math.PI / 2, Math.PI / 2, false);
ctx.clip();
The image that I am using is of size 800 x 600 (png). Please help me understand what I am doing wrong here.
Firstly, why are you using clip? You are currently just drawing semicircles, which works without clip.
Secondly, you are creating paths and clipping, but you never stroke the path. As a result, you won't see anything on the screen.
If you just stroke instead of clip, it works partially for me: http://jsfiddle.net/r6yAN/. You did not include the top semicircle though.
Edit: It seems like you're not using the best way of clipping. You draw a rectangle, but this also includes a line in the semicircle. You'd be better off if you draw each line/arc yourself like this: http://jsfiddle.net/CH6qB/6/.
The main idea is to move from point to point as in this image:
So first start at (startX, startY), then a line to (startX + lineWidth, startY), then an arc at (startX + rectWidth / 2, startY) from pi to 0 (counterclockwise), etc.
If you want to stroke the path as well after having drawn the image, it is a good idea to unclip again. Otherwise, the stroke will not be of great quality.
var canvasNode = document.getElementById('cv');
var ctx = canvasNode && canvasNode.getContext('2d');
var image = new Image();
image.onload = function() {
// draw the image, region has been clipped
ctx.drawImage(image, startX, startY);
// restore so that a stroke is not affected by clip
// (restore removes the clipping because we saved the path without clip below)
ctx.restore();
ctx.stroke();
};
image.src = "http://www.lorempixum.com/200/200/";
var startX = 200;
var startY = 0;
var rectWidth = 150;
var rectHeight = 150;
var radius = 30;
var lineWidth = rectWidth / 2 - radius;
var lineHeight = rectHeight / 2 - radius;
// typing pi is easier than Math.PI each time
var pi = Math.PI;
ctx.moveTo(startX, startY);
ctx.lineTo(startX + lineWidth, startY);
ctx.arc(startX + rectWidth / 2, startY,
radius,
pi, 0, true);
ctx.lineTo(startX + rectWidth, startY);
ctx.lineTo(startX + rectWidth, startY + lineHeight);
ctx.arc(startX + rectWidth, startY + rectHeight / 2,
radius,
-pi / 2, pi / 2, false);
ctx.lineTo(startX + rectWidth, startY + rectHeight);
ctx.lineTo(startX + rectWidth - lineWidth, startY + rectHeight);
ctx.arc(startX + rectWidth / 2, startY + rectHeight,
radius,
0, pi, false);
ctx.lineTo(startX, startY + rectHeight);
ctx.lineTo(startX, startY + rectHeight - lineHeight);
ctx.arc(startX, startY + rectHeight / 2,
radius,
pi/2, pi*3/2, true);
ctx.lineTo(startX, startY);
ctx.save(); // Save the current state without clip
ctx.clip();