Dynamically locate button inside HTML <canvas> - javascript

Inside canvas I draw a line, that line will create dynamically. In the end of line, i want to create an HTML button. But I don't know how to create.
Plz, help me.
HTML
<canvas id="c"></canvas>
<input id="btn" type="button">
Javascript
var line = document.createElement("canvas");
var ctx = line.getContext("2d");
document.getElementById("c").style.height = "300px";
ctx.fillStyle = "red";
ctx.fillRect(10, 1, line.width, line.height);
var ctx = c.getContext("2d");
drawLine(20, 20, 150, 70);
function drawLine(x1, y1, x2, y2) {
var dx = x2 - x1,
dy = y2 - y1,
len = (Math.sqrt(dx*dx+dy*dy)-1)|0,
ang = Math.atan2(dy, dx);
ctx.translate(x1|0, y1|0);
ctx.rotate(ang);
ctx.drawImage(line, 0, 0, len, 1);
ctx.setTransform(1,0,0,1,0,0)
}
ctx.arc(20, 20, 5, 0, 2 * Math.PI);
ctx.fillStyle = 'green';
ctx.fill();
ctx.strokeStyle = 'red'
ctx.stroke();
DEMO

You can't put or draw an HTML element in a canvas. You can create the button and then use position: absolute with the same left and top as the end of the line's x and y.
Take a look at this snippet:
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
ctx.fillStyle = 'green';
ctx.strokeStyle = 'red';
drawLine(20, 20, 150, 70);
ctx.arc(20, 20, 5, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
function drawLine(x1, y1, x2, y2) {
var button = createButtonOnCanvas(x2, y2);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.closePath();
}
function createButtonOnCanvas(x, y) {
var btn = document.createElement("button");
document.body.appendChild(btn);
btn.style.position = "absolute";
btn.style.left = x + "px";
btn.style.top = y + "px";
btn.innerHTML = "btn";
return btn;
}
<canvas id="c" width="300" height="100"></canvas>
Since you want to create a button, instead of moving an already existing one, I made a function that creates a completely new button and moves it over a certain (x, y) of the canvas.
You should also use the lineTo() method to draw a line and resize the canvas' width and height attributes. If the width and height attributes are 100 and 100 while the CSS width and height styles are 200px and 200px, those 100x100 pixels will be stretched to fill a 200x200 canvas and everything drawn will get blurry. For more information, take a look at this question.

Related

How do I rotate an object in canvas and js; my example is not rotating how I expect

I'm trying to rotate a rectangle about it's center but it's not rotating how I expect.
Here's an example:
https://jsfiddle.net/37ur8dfk/1/
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let x = 100;
let y = 100;
let w = 100;
let h = 50;
// Draw a red dot to highlight the point I want to rotate the rectangle around
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(x, y, 4, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
// Attempt to rotate the rectangle aroumd x,y
ctx.save();
ctx.fillStyle = '#000000';
ctx.translate(-x, -y);
ctx.rotate(10 * Math.PI/180);
ctx.translate(x, y);
ctx.fillRect(x - w/2, y - h/2, w, h);
ctx.restore();
I have the center of the rectangle as x,y coords. I then translate it by -x,-y to change it's origin to 0,0. Then I rotate it by some degrees, but it does not seem to be rotating about the 0,0 coords. It's my understanding that rotate should rotate the entire context about the origin, or 0,0.
Please take a look at the jsfiddle to see what I mean.
What am I missing here?
You got it inversed.
You are not translating the rectangle, but the context's transformation matrix.
Think of this as a sheet of paper and an arm with pen.
When you translate your context, the arm is moving in the direction provided. When you rotate the context, the arm is rotating.
So to set your rectangle's center as the rotation origin you first need to move the arm so that the pen is in the center of the rectangle, then you'll be able to rotate. And we move back the arm to its initial position so that the x and y coords match.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let x = 100;
let y = 100;
let w = 100;
let h = 50;
// Attempt to rotate the rectangle aroumd x,y
ctx.save();
ctx.fillStyle = '#000000';
// move to transformation-origin
ctx.translate(x, y);
// transform
ctx.rotate(10 * Math.PI/180);
// go back to where we were
ctx.translate(-x, -y);
ctx.fillRect(x - w/2, y - h/2, w, h);
ctx.restore();
// Draw a red dot to highlight the point I want to rotate the rectangle around
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(x, y, 4, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
<canvas id="canvas" width="500" height="400"></canvas>
Try (This will allow a rotation around the dot)
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let x = 100;
let y = 100;
let w = 100;
let h = 50;
let angle = Math.PI/8;
ctx.save();
ctx.fillStyle = '#000000';
ctx.translate(x, y);
ctx.rotate(angle);
ctx.fillRect(0, 0, w, h);
ctx.restore();
ctx.save();
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(x, y, 4, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
ctx.restore();
canvas {
border: 1px solid black;
}
<canvas id="canvas" width="500" height="400"></canvas>

Adding outer stroke to colliding canvas rectangles

I'm adding multiple rectangles in canvas which could collide with each other. The outer stroke should be displayed on the outer part of both rectangles or the rectangle shapes should be merged in to one producing the expected result.
See picture bellow
It has to be cut because it will display the content under the canvas. See live example with background image: https://jsfiddle.net/0qpgf5un/
In the code example bellow rectangles are being added on top of each other as you can see in the first example of the picture.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var offsetX = 150;
var offsetY = 150;
var w = 200;
var h = 100;
ctx.fillStyle = "red";
ctx.rect(0, 0, 600, 600);
ctx.fill();
ctx.clearRect(offsetX,offsetY, w, h);
ctx.strokeRect(offsetX, offsetY, w, h);
ctx.clearRect(offsetX-50,offsetY+50, w, h);
ctx.strokeRect(offsetX-50, offsetY+50, w, h);
Is there ways to achieve it without writing complex calculations of each path, since the collision of rectangles can be unintentional and diverse ?
Edit:
What I am trying to achieve is a similar functionality like in youtube's feedback form where when editing screenshot you can highlight items and the border then is merged.
Just add one more clearRect() (the first one)
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var offsetX = 150;
var offsetY = 150;
var w = 200;
var h = 100;
ctx.fillStyle = "red";
ctx.rect(0, 0, 600, 600);
ctx.fill();
ctx.clearRect(offsetX,offsetY, w, h);
ctx.strokeRect(offsetX, offsetY, w, h);
ctx.clearRect(offsetX-50,offsetY+50, w, h);
ctx.strokeRect(offsetX-50, offsetY+50, w, h);
ctx.clearRect(offsetX,offsetY, w, h);
https://jsfiddle.net/kt3yjhpc/
You can skip clearing the first rectangle and then clear it after you stroke the second one.
The clearPrev function will clear the area inside the strokes of the initial rectangle.
let canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
offsetX = 70,
offsetY = 20,
w = 200,
h = 100,
strokeWidth = 5;
ctx.fillStyle = '#F00'
ctx.rect(0, 0, 600, 600);
ctx.fill();
ctx.strokeStyle = '#0FF';
ctx.lineWidth = strokeWidth;
//ctx.clearRect(offsetX, offsetY, w, h); <-- Do not need to do this, if we clear below...
ctx.strokeRect(offsetX, offsetY, w, h);
ctx.clearRect(offsetX - 50, offsetY + 50, w, h);
ctx.strokeRect(offsetX - 50, offsetY + 50, w, h);
clearPrev(ctx, offsetX, offsetY, w, h); // Clear previous
function clearPrev(ctx, x, y, w, h) {
let startOffset = Math.round(ctx.lineWidth / 2) - 1,
endOffset = strokeWidth - 1;
ctx.clearRect(x + startOffset, y + startOffset, w - endOffset, h - endOffset);
}
<canvas id="canvas" width="290" height="190"></canvas>
When you want to clear the canvas with complex shapes, forget about clearRect, it's not the only one able to produce transparent pixels.
Instead, have a look at compositing.
So your shape is really border-line, but I think you'll benefit from using this already:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var offsetX = 150;
var offsetY = 150;
var w = 200;
var h = 100;
ctx.lineWidth = 2;
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 600, 600);
// declare our complex shape as a single sub-path
ctx.beginPath()
ctx.rect(offsetX,offsetY, w, h);
ctx.rect(offsetX-50, offsetY+50, w, h);
// now we can paint it
// first the stroke, because we want to erase what's inside the fill-area
ctx.stroke();
// now to erase, we switch to destination-out compositing mode
ctx.globalCompositeOperation = 'destination-out';
// fill the inner path
ctx.fill();
// we're done
// If you wish to go back to normal mode later
ctx.globalCompositeOperation = 'source-over';
body { background: linear-gradient(blue,yellow); }
<canvas id="canvas" width="600" height="600"></canvas>

Draw canvas text outside the rectangle along the edges

I am trying to write the description of each edge along the rectangle. The reason is to describe the length of each edge inside and outside rectangles (alongside). Is there a way I can achieve it?
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
// Clip a rectangular area
ctx.rect(50, 20, 200, 120);
ctx.stroke();
ctx.clip();
// Draw red rectangle after clip()
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 150, 100);
This should show 200 above the top edge (outside) and 150 along the left edge (outside)
Using #stealththeninja's comment (pointing to this answer - text in html canvas) and this jsfiddle (for text rotation), I was able to build the code below. Hope it fits within your specs.
Screenshot of the result attached.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
const rectPosX = 50;
const rectPosY = 50;
const rectLength = 200;
const rectHeight = 150;
ctx.fillStyle = "red";
ctx.fillRect(rectPosX, rectPosY, rectLength, rectHeight);
ctx.fillStyle = "blue";
ctx.fillText('200', rectPosX + rectLength / 2, rectPosY);
ctx.fillText('150', rectPosX, rectPosY + rectHeight / 2);
ctx.fillText('200', rectPosX + rectLength / 2, rectPosY + rectHeight);
ctx.save();
ctx.translate(rectPosX + rectLength, rectPosY + rectHeight / 2);
ctx.rotate(0.5*Math.PI);
ctx.fillText('150', 0, 0);
ctx.restore();
<canvas id="myCanvas" width="400" height="300"></canvas>

HTML 5 Canvas, rotate everything

I made a cylinder gauge, very similar to this one:
It is drawn using about 7 or so functions... mine is a little different. It is very fleixble in that I can set the colors, transparency, height, width, whether there is % text shown and a host of other options. But now I have a need for the same thing, but all rotated 90 deg so that I can set the height long and the width low to generate something more like this:
I found ctx.rotate, but no mater where it goes all the shapes fall apart.. ctx.save/restore appears to do nothing, I tried putting that in each shape drawing function. I tried modifying, for example, the drawOval function so that it would first rotate the canvas if horizontal was set to one; but it appeared to rotate it every single iteration, even with save/restore... so the top cylinder would rotate and the bottom would rotate twice or something. Very tough to tell what is really happening. What am I doing wrong? I don't want to duplicate all this code and spend hours customizing it, just to produce something I already have but turned horizontal. Erg! Help.
Option 1
To rotate everything just apply a transform to the element itself:
canvas.style.transform = "rotate(90deg)"; // or -90 depending on need
canvas.style.webkitTransform = "rotate(90deg)";
Option 2
Rotate context before drawing anything and before using any save(). Unlike the CSS version you will first need to translate to center, then rotate, and finally translate back.
You will need to make sure width and height of canvas is swapped before this is performed.
ctx.translate(ctx.canvas.width * 0.5, ctx.canvas.height * 0.5); // center
ctx.rotate(Math.PI * 0.5); // 90°
ctx.translate(-ctx.canvas.width * 0.5, -ctx.canvas.height * 0.5);
And of course, as an option 3, you can recalculate all your values to go along the other axis.
Look at the rotate function in this example. You want to do a translation to the point you want to rotate around.
example1();
example2();
function rotate(ctx, degrees, x, y, fn) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(degrees * (Math.PI / 180));
fn();
ctx.restore();
}
function rad(deg) {
return deg * (Math.PI / 180);
}
function example2() {
var can = document.getElementById("can2");
var ctx = can.getContext('2d');
var w = can.width;
var h = can.height;
function drawBattery() {
var percent = 60;
ctx.beginPath();
ctx.arc(35,50, 25,0,rad(360));
ctx.moveTo(35+percent+25,50);
ctx.arc(35+percent,50,25,0,rad(360));
ctx.stroke();
ctx.beginPath();
ctx.fillStyle = "rgba(0,255,0,.5)";
ctx.arc(35,50,25,0,rad(360));
ctx.arc(35+percent,50,25,0,rad(360));
ctx.rect(35,25,percent,50);
ctx.fill();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "#666666";
ctx.moveTo(135,25);
ctx.arc(135,50,25, rad(270), rad(269.9999));
//ctx.moveTo(35,75);
ctx.arc(35,50,25,rad(270),rad(90), true);
ctx.lineTo(135,75);
ctx.stroke();
}
drawBattery();
can = document.getElementById("can3");
ctx = can.getContext('2d');
w = can.width;
h = can.height;
rotate(ctx, -90, 0, h, drawBattery);
}
function example1() {
var can = document.getElementById('can');
var ctx = can.getContext('2d');
var color1 = "#FFFFFF";
var color2 = "#FFFF00";
var color3 = "rgba(0,155,255,.5)"
var text = 0;
function fillBox() {
ctx.save();
ctx.fillStyle = color3;
ctx.fillRect(0, 0, can.width / 2, can.height);
ctx.restore();
}
function drawBox() {
ctx.save();
ctx.beginPath();
ctx.strokeStyle = ctx.fillStyle = color1;
ctx.rect(10, 10, 50, 180);
ctx.font = "30px Arial";
ctx.fillText(text, 25, 45);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = color2;
ctx.lineWidth = 10;
ctx.moveTo(10, 10);
ctx.lineTo(60, 10);
ctx.stroke();
ctx.restore();
}
fillBox();
rotate(ctx, 90, can.width, 0, fillBox);
text = "A";
drawBox();
color1 = "#00FFFF";
color2 = "#FF00FF";
text = "B";
rotate(ctx, 90, can.width, 0, drawBox);
centerRotatedBox()
function centerRotatedBox() {
ctx.translate(can.width / 2, can.height / 2);
for (var i = 0; i <= 90; i += 10) {
var radians = i * (Math.PI / 180);
ctx.save();
ctx.rotate(radians);
ctx.beginPath();
ctx.strokeStyle = "#333333";
ctx.rect(0, 0, 50, 50)
ctx.stroke();
ctx.restore();
}
}
}
#can,
#can2,
#can3 {
border: 1px solid #333333
}
<canvas id="can" width="200" height="200"></canvas>
<canvas id="can2" width="200" height="100"></canvas>
<canvas id="can3" width="100" height="200"></canvas>

Adding alert to canvas faces

I'm currently trying to make so that when you click on one of the happy face you get an alert box which says "thanks for the feed back", but I'm currently not sure to to incoperate that in tho my code! Thanks! Here is the fiddle http://jsfiddle.net/bsjs9/
<html lang="en">
<head>
<meta charset="utf-8" />
<title>SmileMore</title>
</head>
<body>
<h1>
Bring your Charts to life with HTML5 Canvas</h1>
</hgroup>
<p>
Rendering Dynamic charts in JS
</p>
<div class="smile">
<canvas id="myDrawing" width="200" height="200" style="border:1px solid #EEE"></canvas>
<canvas id="canvas" width="200" height="200" style="border:1px solid #EEE"></canvas>
</div>
<script>
var FacePainter = function(canvasName)
{
var canvas = document.getElementById(canvasName);
var ctx = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
var startAngle = 0;
var endAngle = 2 * Math.PI;
function drawFace() {
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle);
ctx.stroke();
ctx.fillStyle = "yellow";
ctx.fill();
}
function drawSmile(startAngle, endAngle)
{
var x = canvas.width / 2;
var y = 150;
var radius = 40;
ctx.beginPath();
ctx.arc(x, y, radius, startAngle * Math.PI, endAngle * Math.PI);
ctx.lineWidth = 7;
// line color
ctx.strokeStyle = 'black';
ctx.stroke();
}
function drawEyes() {
var centerX = 40;
var centerY = 0;
var radius = 10;
// save state
ctx.save();
// translate context so height is 1/3'rd from top of enclosing circle
ctx.translate(canvas.width / 2, canvas.height / 3);
// scale context horizontally by 50%
ctx.scale(.5, 1);
// draw circle which will be stretched into an oval
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
// restore to original state
ctx.restore();
// apply styling
ctx.fillStyle = 'black';
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
//left eye
var centerX = -40;
var centerY = 0;
var radius = 10;
// save state
ctx.save();
// translate context so height is 1/3'rd from top of enclosing circle
ctx.translate(canvas.width / 2, canvas.height / 3);
// scale context horizontally by 50%
ctx.scale(.5, 1);
// draw circle which will be stretched into an oval
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
// restore to original state
ctx.restore();
// apply styling
ctx.fillStyle = 'black';
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
}
this.drawHappyFace = function() {
drawFace();
drawEyes();
drawSmile(1.1, 1.9);
}
this.drawSadFace = function() {
drawFace();
drawEyes();
drawSmile(1.9, 1.1);
;
}
}
new FacePainter('canvas').drawHappyFace();
new FacePainter('myDrawing').drawSadFace();
</script>
</body>
</html>
</body>
</html>
As an extra assignment I would like to know if anyone knows how to fix the "happy" smile, its kinda way off! Thanks all!
Since you draw the happy face on its own canvas, you can simple put an onclick handler on the canvas.
<canvas id="myDrawing" width="200" height="200" style="border:1px solid #EEE" onclick="alert('thanks');"></canvas>
Regarding the smiles, I added a new ofsy parameter to drawSmile, which offsets the arc origin vertically.
Here is the updated fiddle.
If you only want to show the alert, when the user clicks inside the face, you need to get the click coordinates and hittest it against the circle. You can see this in this fiddle.

Categories

Resources