HTML5 context.drawImage() crop is not working after calling context.translate() - javascript

> What I want here are 10 canvases, 5 on the top and 5 on the bottom, each with width 17% and height 25.5%, and appropriate spacing between (see bottom image). Each image drawn on a canvas corresponds to the same area of the full image (see top image). This is kind of like a destination-in, but this is really just a crop of the full image onto smaller canvases using context.translate and context.drawImage(). Please see the two lines inside the innermost for loop with comments that deal wtih context.translate and context.drawImage for clue as to what might be going on.
Please see the attached image of what I am trying to achieve with html5 context.translate() and context.drawImage().
Any help greatly appreciated. Thank you.
//get parent's width and height
var parent = document.getElementById("parent");
var parentWidth = parent.offsetWidth;
var parentHeight = parent.offsetHeight;
//get below canvas
var belowCanvas = document.getElementById('belowCanvas');
var belowCtx = belowCanvas.getContext('2d');
//create temporary canvas
var tmpCanvas = document.createElement('canvas');
var tmpCtx = tmpCanvas.getContext('2d');
//initialize width and height of temporary canvas and below canvas to equal parent
tmpCanvas.width = belowCanvas.width = parentWidth;
tmpCanvas.height = belowCanvas.height = parentHeight;
//draw below canvas in black for visual aid of how things are cropped in above canvases
belowCtx.rect(0,0,parentWidth,parentHeight);
belowCtx.fillStyle = 'black';
belowCtx.fill();
//draw temporary canvas
var centerX = parentWidth/4;
var centerY = parentHeight/4;
var radius = parentHeight/4;
tmpCtx.rect(0,0,parentWidth,parentHeight);
tmpCtx.fillStyle = 'blue';
tmpCtx.fill();
tmpCtx.beginPath();
tmpCtx.arc(centerX, centerY, radius*1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'green';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(parentWidth - centerX, centerY, radius*1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'purple';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(parentWidth/2, parentHeight/2, radius*2, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'white';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(centerX, parentHeight - centerY, radius*1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'red';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(parentWidth - centerX, parentHeight - centerY, radius*1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'yellow';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
//set spacing between canvases
var horizontalSpacing = parentWidth*0.025
var verticalSpacing = parentWidth*0.03
//initialize canvases width and height
var widthCanvas = parentWidth*0.17;
var heightCanvas = parentWidth*0.255;
var xStart;
var yStart = verticalSpacing;
for(var i=0; i< 2; i++)
{
xStart = horizontalSpacing;
for(var j=0; j < 5; j++)
{
//get specific destinationInCanvas by id and its respective context
var canvas = document.getElementById("canvas" + ((i*5)+j));
var ctx = canvas.getContext('2d');
canvas.width = widthCanvas;
canvas.height = heightCanvas;
/***!!!
Problem next two lines
if the next line is commented out, each canvas is drawn, but of course not translated; so you only see last canvas, canvas9
if the next line is NOT commented out, only canvas0 is drawn and translated, the rest of the canvases 1-9 are not drawn*/
ctx.translate(xStart, yStart);//comment out this line to see effect
ctx.drawImage(tmpCanvas, xStart, yStart, widthCanvas, heightCanvas, 0, 0, widthCanvas, heightCanvas);
xStart += (horizontalSpacing + widthCanvas);
}
yStart += (verticalSpacing + heightCanvas);
}
#parent {
width: 1000px;
height: 600px;
}
#belowCanvas{
position: absolute;
z-index:-1;
}
#canvas0 {
position: absolute;
z-index:0;
}
#canvas1 {
position: absolute;
z-index:1;
}
#canvas2 {
position: absolute;
z-index:2;
}
#canvas3 {
position: absolute;
z-index:3;
}
#canvas4 {
position: absolute;
z-index:4;
}
#canvas5 {
position: absolute;
z-index:5;
}
#canvas6 {
position: absolute;
z-index:6;
}
#canvas7 {
position: absolute;
z-index:7;
}
#canvas8 {
position: absolute;
z-index:8;
}
#canvas9 {
position: absolute;
z-index:9;
}
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style-test.css">
</head>
<body>
<div id="parent">
<canvas id="belowCanvas"></canvas>
<canvas id="canvas0"></canvas>
<canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas>
<canvas id="canvas3"></canvas>
<canvas id="canvas4"></canvas>
<canvas id="canvas5"></canvas>
<canvas id="canvas6"></canvas>
<canvas id="canvas7"></canvas>
<canvas id="canvas8"></canvas>
<canvas id="canvas9"></canvas>
</div>
<script type="text/javascript" src="script-test.js"></script>
</body>
</html>

You've got some problem while incrementing yStart and xStart:
You should do so by multiplying heightCanvas/widthCanvas by i/j state of your loop.
Also, in your CSS, if you do add position:absolute without setting marginsor left/top values, your canvases will get in stack and you'll only be able to see last one. Only add position:absolute to your #belowCanvas. Actually, if you only need a black background, you might consider wrapping all your canvases into a div with background:black;. It would be easier to align your elements then.
Finally, you won't need ctx.translate since ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); already does the translation on source image (sx, sy).
Edit after I talked to OP on chat:
Actually, he wanted the canvases' holes to fit to a layout, created with a context.globalCompositeOperation='destination-out';.
I did remove the belowCanvas and replaced it with a divwhich background is set to black via CSS.
To prevent ghost margins with your small canvases, you'll need to set vertical-align:bottom; float: left since browsers display canvas element as a character.
I refactored the code to create the small canvases programmatically, this will allow you to change the grid format.
For the example, I added click listener to show/hide layers:
left click triggers destinationOutLayer and right click triggers small canvases layer.
//get parent's width and height
var parent = document.getElementById("parent");
var parentWidth = parent.offsetWidth;
var parentHeight = parent.offsetHeight;
var destinationOutCanvas = document.getElementById('destinationOutCanvas');
var destinationOutCtx = destinationOutCanvas.getContext('2d');
//create temporary canvas
var tmpCanvas = document.createElement('canvas');
var tmpCtx = tmpCanvas.getContext('2d');
//initialize width and height of temporary canvas and destOut canvas to equal parent
destinationOutCanvas.width = tmpCanvas.width = parentWidth;
destinationOutCanvas.height = tmpCanvas.height = parentHeight;
//draw temporary canvas
var centerX = parentWidth / 4;
var centerY = parentHeight / 4;
var radius = parentHeight / 4;
tmpCtx.rect(0, 0, parentWidth, parentHeight);
tmpCtx.fillStyle = 'blue';
tmpCtx.fill();
tmpCtx.beginPath();
tmpCtx.arc(centerX, centerY, radius * 1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'green';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(parentWidth - centerX, centerY, radius * 1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'purple';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(parentWidth / 2, parentHeight / 2, radius * 2, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'white';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(centerX, parentHeight - centerY, radius * 1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'red';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
tmpCtx.beginPath();
tmpCtx.arc(parentWidth - centerX, parentHeight - centerY, radius * 1.5, 0, 2 * Math.PI, false);
tmpCtx.fillStyle = 'yellow';
tmpCtx.fill();
tmpCtx.lineWidth = 2
tmpCtx.strokeStyle = '#003300';
tmpCtx.stroke();
//set the grid
//Strange bugs can occur with some fractions (e.g 18 rows);
var rows = 5;
var lines = 2;
//set spacing between canvases
//rounded to the nearest even number, since we divide this by 2 below
var horizontalSpacing = 2*Math.round((parentWidth*0.025)/2); // or whatever you want
var verticalSpacing = 2*Math.round((parentHeight*0.03)/2); // or whatever you want
//initialize canvases width and height
var widthCanvas = 2*(Math.round(parentWidth / rows)/2);
var heightCanvas = 2*(Math.round(parentHeight / lines)/2);
destinationOutCtx.drawImage(tmpCanvas, 0, 0);
destinationOutCtx.globalCompositeOperation = 'destination-out';
destinationOutCtx.fillStyle = 'orange';
for (var i = 0; i < lines; i++) {
for (var j = 0; j < rows; j++) {
//get specific destinationInCanvas by id and its respective context
//var canvas = document.getElementById("canvas" + ((i * rows) + j));
//create the canvases on the go
var canvas= document.createElement('canvas');
canvas.width = widthCanvas;
canvas.height = heightCanvas;
var ctx = canvas.getContext('2d');
//only needed for the demonstration toggler
canvas.className = "small";
//set the transform variables
var hS = horizontalSpacing/2,
vS = verticalSpacing/2,
xStart = (widthCanvas*j)+hS,
yStart = (heightCanvas*i)+vS,
cropedWidth = widthCanvas-hS*2,
cropedHeight = heightCanvas-hS*2;
ctx.drawImage(tmpCanvas, xStart, yStart, cropedWidth, cropedHeight, hS, vS, cropedWidth, cropedHeight);
destinationOutCtx.fillRect(xStart, yStart, cropedWidth, cropedHeight);
parent.appendChild(canvas);
}
}
//Toggle opacity with right and left click
destinationOutCanvas.addEventListener('click', function () {
if (this.style.opacity == 0) {
this.style.opacity = 1
} else {
this.style.opacity = 0
}
});
destinationOutCanvas.style.opacity = 1;
destinationOutCanvas.addEventListener('contextmenu', function (e) {
console.log('triggered');
e.preventDefault();
var smalls = document.querySelectorAll('.small');
console.log(smalls[0].style.opacity);
if (smalls[0].style.opacity == 0) {
for (i = 0; i < smalls.length; i++) {
smalls[i].style.opacity = 1
}
} else {
for (i = 0; i < smalls.length; i++) {
smalls[i].style.opacity = 0;
};
}
});
#parent {
width: 1000px !important;
height: 600px;
background:#000;
}
html, body {
margin:0
}
canvas {
vertical-align:bottom;
float: left
}
.destinationOutLayer, #tmp {
position: absolute;
z-index: 2;
top:0;
}
<div id="parent">
</div>
<canvas id="destinationOutCanvas" class="destinationOutLayer"></canvas>

Related

Pan around the canvas within bounds smoothly

Most other solutions I have found relate to images as the bounds and work through css, and I can't get them to work with a canvas.
I want to create the bounds by defining an arbitrary percent size of the canvas (e.g 2x - twice the size) that expands from the center of the canvas and then have the canvas view (red) move/pan around within the bounds (green) by mouse hovering inside the canvas - that it moves to all corners of the bounds from the center of the canvas.
like this behavior in terms of the direction with panning
http://jsfiddle.net/georgedyer/XWnUA/2/
Additionally, I would like
the solution to work with a responsive canvas sizes rather than fixed canvas sizes
the panning to be smooth using easing such as CubicInOut easing
to detect the mouse hover inside the circle drawn. Might have to change the coordinate from screen to world to get it to work properly. But maybe theirs an easier way?
formulas
Here's my attempt at figuring out the formulas to get it working! I'll just focus on just one axis (x-axis) from left to right of the mouse, but the same formulas should apply to the y-axis.
First I get the "percentX" of the mouse along the width. If the "percentX" is 0 then the "newX" for the pan will be the value of the leftSide, else if it's 1 then it'll be the rightSide of the boundsBorder (green). So the "percentX" determines which side of the boundsBorder/limit the canvas view will extend/move to.
Only problem is that this doesn't work. So my formula must be wrong. The mouse inside the circle event is also inaccurate because of my canvas translate. Combining everything together is difficult to figure out!
Code
<html>
<head>
<meta charset="UTF-8">
<title>Panning</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
}
body {
background: #eee;
padding: 20px 0;
}
.canvas-container {
height: 400px;
width: 100%;
margin-bottom: 40px;
border-radius: 10px;
background: #fff;
overflow: hidden;
}
canvas {
width: 100%;
height: inherit;
}
.container {
width: 60%;
margin: 0 auto;
}
</style>
</head>
<body>
<!-- CANVAS -->
<div class="container">
<div class="canvas-container">
<canvas id="canvas"></canvas>
</div>
</div>
<!-- SCRIPT -->
<script type="text/javascript">
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var width = canvas.width = canvas.clientWidth;
var height = canvas.height = canvas.clientHeight;
var panX = 0, panY = 0;
var scaleBorderFactor = 2; /* scale factor for the bounds */
var mouse = {
x: 0,
y: 0
}
function Circle(x, y, radius, color){
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
this.draw = function(ctx){
ctx.beginPath();
ctx.arc(this.x,this.y,this.radius,0,Math.PI*2,false);
ctx.fillStyle = this.color;
ctx.fill();
ctx.closePath();
}
}
/* Rect Stroke Borders for visual reference */
function RectBorder(x, y, w, h, c){
this.x = x;
this.y = y;
this.width = w;
this.height = h;
this.color = c;
this.draw = function(ctx){
ctx.strokeStyle = this.color;
ctx.lineWidth = 2;
ctx.strokeRect(this.x, this.y, this.width, this.height);
};
/* Draw rect from a center point */
this.drawAtCenter = function(ctx){
ctx.strokeStyle = this.color;
ctx.lineWidth = 2;
ctx.strokeRect(this.x, this.y, this.width, this.height);
};
this.toString = function(){
console.log("x: "+this.x+", y: "+this.y+", w: "+this.width+", h: "+this.height+", color = "+this.color);
}
}
function getMousePos(canvas, event) {
var rect = canvas.getBoundingClientRect();
return {
x: (event.clientX - rect.left),
y: (event.clientY - rect.top)
};
}
function insideCircle(circle, mouse){
var dist = Math.sqrt(Math.pow(circle.x - mouse.x,2) + Math.pow(circle.y - mouse.y,2));
console.log(circle.radius+", d = "+dist)
return dist <= circle.radius;
}
function lerp(start, end, percent){
return start*(1 - percent) + percent*end;
}
/* t: current time, b: beginning value, c: change in value, d: duration */
function easeInOutCubic(t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
}
var canvasBorder = new RectBorder(0,0,canvas.width,canvas.height,'red');
var boundsBorder = new RectBorder(-(canvas.width*scaleBorderFactor - canvas.width)/2,-(canvas.height*scaleBorderFactor - canvas.height)/2,canvas.width*scaleBorderFactor,canvas.height*scaleBorderFactor,'green');
var circle = new Circle(200,200,40,'blue');
/* Draw Update */
update();
function update(){
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
width = canvas.width = canvas.clientWidth;
height = canvas.height = canvas.clientHeight;
ctx.clearRect(0,0,width,height);
ctx.save();
/* MOUSE */
percentX = (mouse.x / width);
percentY = (mouse.y / height);
/* the 2 sides of boundsBorder */
var leftSide = (width*scaleBorderFactor - width)/2 - width;
var rightSide = (width*scaleBorderFactor - width)/2 + width;
var topSide = (height*scaleBorderFactor - height)/2 - width;
var bottomSide = (height*scaleBorderFactor - height)/2 + height;
newX = rightSide * percentX + leftSide * (1 - percentX);
newY = bottomSide * percentY + topSide * (1 - percentY);
/* maybe use easeInOutCubic for better smoothness */
panX = lerp(-newX, mouse.x, 0.1);
panY = lerp(-newY, mouse.y, 0.1);
if (insideCircle(circle, mouse)){
circle.color = "pink";
} else {
circle.color = "blue";
}
ctx.translate(panX,panY);
/* Draw both borders only for reference */
canvasBorder.draw(ctx);
boundsBorder.drawAtCenter(ctx);
/* Draw Circle */
circle.draw(ctx);
ctx.restore();
requestAnimationFrame(update);
}
/* Events */
function mousemove(e) {
mouse = getMousePos(canvas, e);
}
/* Event Listeners */
canvas.addEventListener('mousemove', mousemove);
</script>
</body>
</html>
EDIT
I have managed to get the pan functionality to work. I just realized that all I need to do is offset by the positive and negative "widthGap"(see picture below) amount when panning. So it goes from positive "widthGap" (moves to the rght) to negative "widthGap" (moves to the left) when the "percentX" changes value like mentioned previously.
To get the smooth movement, I instead applied the lerp to the "percentX" values.
The code below is what should be replaced from the previous code above. It also shows the new variables I defined, which will make some of the variables from the previous code redundant.
UPDATED Code
var canvasBorder = new RectBorder(0,0,canvas.width,canvas.height,'red');
/* widthGap is the new leftSide formula.
* it is the gap differance between border and canvas
*/
var widthGap = (canvas.width*scaleBorderFactor - canvas.width)/2;
var heightGap = (canvas.height*scaleBorderFactor - canvas.height)/2;
var boundsBorder = new RectBorder(-widthGap,-heightGap,canvas.width+widthGap*2,canvas.height+heightGap*2,'green');
var circle = new Circle(200,200,40,'blue');
/* Draw Update */
update();
var newPercentX = 0, newPercentY = 0;
var percentX = 0, percentY = 0;
function update(){
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
width = canvas.width = canvas.clientWidth;
height = canvas.height = canvas.clientHeight;
ctx.clearRect(0,0,width,height);
ctx.save();
newPercentX = (mouse.x / width);
newPercentY = (mouse.y / height);
/* MOUSE */
percentX = lerp(percentX,newPercentX,0.05);
percentY = lerp(percentY,newPercentY,0.05);
panX = (widthGap) * percentX + (-widthGap) * (1 - percentX);
panY = (heightGap) * percentY + (-heightGap) * (1 - percentY);
ctx.translate(-panX,-panY);
if (insideCircle(circle, mouse)){
circle.color = "pink";
} else {
circle.color = "blue";
}
/* Draw both borders only for reference */
canvasBorder.draw(ctx);
boundsBorder.drawAtCenter(ctx);
/* Draw Circle */
circle.draw(ctx);
ctx.restore();
requestAnimationFrame(update);
}

How to rotate dynamic text from input in Canvas and JS?

I'm working on a box styling project with Canvas and Javascript, and I can't rotate the text the way I want (written from bottom to the top).
(source: hostpic.xyz)
I followed a tutorial (https://newspaint.wordpress.com/2014/05/22/writing-rotated-text-on-a-javascript-canvas/) and tried to adapt it in my code, but I couldn't make it.
You can find the code on the JSFiddle link below, there's an input where you can type your text and it should be written as the "brand" word in the JSFiddle example.
https://jsfiddle.net/ParkerIndustries/mgne9x5u/5/
Everything is in the init() function:
function init() {
var elem = document.getElementById('elem');
var circle_canvas = document.createElement("canvas");
var context = circle_canvas.getContext("2d");
var img = new Image();
img.src = "https://unblast.com/wp-content/uploads/2018/11/Vertical-Product-Box-Mockup-1.jpg";
context.drawImage(img, 0, 0, 500, 650);
circle_canvas.width = 500;
circle_canvas.height = 650;
context.fillStyle = "#000000";
//context.textAlign = 'center';
var UserInput = document.getElementById("UserInput");
context.save();
context.translate( circle_canvas.width - 1, 0 );
UserInput.oninput = function() {
context.clearRect(0, 0, 500, 650);
context.drawImage(img, 0, 0, 500, 650);
var text = UserInput.value;
console.log(text);
if (text.length < 12) {
context.rotate(3*Math.PI/2);
console.log("-12");
context.font = "50px Righteous";
context.fillText(text, -350, -170);
context.restore();
} else {
context.rotate(3*Math.PI/2);
context.font = "25px Righteous";
context.fillText(text, -350, -170);
context.restore();
}
}
elem.appendChild(circle_canvas);
}
init();
I tried a lot a values in the context.rotate() function but in any way my text is upside down.
(source: hostpic.xyz)
You're pretty close here. I suggest performing the canvas translation to the middle of the screen (width / 2, height / 2) and then rotating by 270 degrees:
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(270 * Math.PI / 180);
Beyond that, I recommend a round of code cleanup to avoid inconsistent variable names and hardcoded numbers (try to make everything proportional to img.width and img.height, then avoid literal values. This makes it easier to adjust your code dynamically without having to re-type all of the values. You can access img.width and img.height after the img.onload function fires.
Another useful function is context.measureText(text), which makes it simpler to proportionally scale text size.
Full example:
function init() {
var userInput = document.getElementById("user-input");
var elem = document.getElementById("elem");
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
var img = new Image();
img.onload = function () {
canvas.width = img.width / 3;
canvas.height = img.height / 3;
context.drawImage(img, 0, 0, canvas.width, canvas.height);
context.fillStyle = "#000000";
context.textAlign = "center";
elem.appendChild(canvas);
userInput.oninput = function () {
var text = userInput.value;
var textSizePx = 50 - context.measureText(text).width / 10;
context.font = textSizePx + "px Righteous";
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(img, 0, 0, canvas.width, canvas.height);
context.save();
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(270 * Math.PI / 180);
context.fillText(text, 0, -canvas.width / 20);
context.restore();
}
};
img.src = "https://unblast.com/wp-content/uploads/2018/11/Vertical-Product-Box-Mockup-1.jpg";
}
init();
<div id="elem">
<input type="text" id="user-input" maxlength="15">
</div>
With that proof-of-concept working, one problem is that scaling the image on canvas causes visual artifacts. This problem can be overcome with JS or by scaling down the source image itself, but at this point, it's best to make the image as an element and circumvent the entire problem.
Similarly, there's no obvious reason to render text on canvas either; we can make an element for this as well. Moving text to HTML/CSS gives more power over how it looks (I didn't do much with style here, so it's an exercise to make it blend more naturally into the image).
Here's a rewrite without canvas that looks much cleaner and should be more maintainable:
function init() {
var userInput = document.querySelector("#user-input");
var img = document.querySelector("#product-img");
var imgRect = img.getBoundingClientRect();
var overlayText = document.querySelector("#overlay-text");
var overlay = document.querySelector("#overlay");
overlay.style.width = (imgRect.width * 0.86) + "px";
overlay.style.height = imgRect.height + "px";
userInput.addEventListener("input", function () {
overlayText.innerText = userInput.value;
overlayText.style.fontSize = (50 - userInput.value.length * 2) + "px";
});
}
init();
#product-img {
position: absolute;
}
#overlay {
position: absolute;
display: flex;
}
#overlay-text {
font-family: Verdana, sans-serif;
color: #333;
transform: rotate(270deg);
margin: auto;
cursor: default;
}
<div>
<input type="text" id="user-input" maxlength="15">
</div>
<div id="product-container">
<img id="product-img" src="https://unblast.com/wp-content/uploads/2018/11/Vertical-Product-Box-Mockup-1.jpg" width=666 height=500 />
<div id="overlay">
<div id="overlay-text"></div>
</div>
</div>

HTML Canvas Trying to create an animated chain of rectangle with slight delay/distance between them

I am trying to create multiple animated rectangles using Html Canvas with requestAnimationFrame. As for now, I managed to do exactly what I wanted with only one animated rectangle, but I can't find out how to create more rectangles that would simply be in line and follow each other with an equal distance.
Also, there's a random data (I, II or III) inside each rectangle.
Here's my actual code:
//Referencing canvas
var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
//Make Canvas fullscreen and responsive
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize, false); resize();
//FPS
var framesPerSecond = 60;
//Default Y pos to center;
var yPos = canvas.height / 2;
//Default X pos offset
var xPos = -150;
//Speed (increment)
var speed = 2;
//Our array to store rectangles objects
var rectangles = [] ;
//Dynamic Number from database
var quote = ["I", "II", "III"];
//Random number for testing purpose
var rand = quote[Math.floor(Math.random() * quote.length)];
//Draw Rectangle
function drawRectangle () {
setTimeout(function() {
requestAnimationFrame(drawRectangle);
ctx.clearRect(0, 0, canvas.width, canvas.height);
//Background color
ctx.fillStyle = "yellow";
//Position, size.
var rectWidth = 70;
var rectHeigth = 55;
ctx.fillRect(xPos,yPos,rectWidth,rectHeigth);
ctx.font = "32px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "black";
//Data Layer
var dataLayer = ctx.fillText(rand,xPos+(rectWidth/2),yPos+(rectHeigth/2));
xPos += speed;
//Infinite loop for test
if (xPos > 1080) {
xPos = -150;
}
}, 1000 / framesPerSecond);
}
drawRectangle ();
canvas {background-color: #131217}
body { margin: 0; overflow: hidden; }
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Moving Blocks</title>
<style>
canvas {background-color: #131217}
body { margin: 0; overflow: hidden; }
</style>
</head>
<body>
<canvas id="my-canvas"></canvas>
</body>
</html>
Animating arrays of objects.
For animations you are best of using a single render function that renders all the objects once a frame, rather than create a separate render frame per object.
As for the squares there are many ways that you can get them to do what you want. It is a little difficult to answer as what you want is not completely clear.
This answer will use a rectangle object that has everything needed to be rendered and move. The rectangles will be kept in an array and the main render function will update and render each rectangle in turn.
There will be a spawn function that creates rectangles untill the limit has been reached.
// constants up the top
const quote = ["I", "II", "III"];
// function selects a random Item from an array
const randItem = (array) => array[(Math.random() * array.length) | 0];
// array to hold all rectangles
const rectangles = [];
var maxRectangles = 20;
const spawnRate = 50; // number of frames between spawns
var spawnCountdown = spawnRate;
//Referencing canvas
const ctx = canvas.getContext("2d");
var w, h; // global canvas width and height.
resizeCanvas(); // size the canvas to fit the page
requestAnimationFrame(mainLoop); // this will start when all code below has been run
function mainLoop() {
// resize in the rendering frame as using the resize
// event has some issuse and this is more efficient.
if (w !== innerWidth || h !== innerHeight) {
resizeCanvas();
}
ctx.clearRect(0, 0, w, h);
spawnRectangle(); // spawns rectangles
updateAllRectangles(); // moves all active rectangles
drawAllRectangles(); // I will let you gues what this does... :P
requestAnimationFrame(mainLoop);
}
function resizeCanvas() {
w = canvas.width = innerWidth;
h = canvas.height = innerHeight;
// and reset any canvas constants
ctx.font = "32px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
}
// function to spawn a rectangle
function spawnRectangle() {
if (rectangles.length < maxRectangles) {
if (spawnCountdown) {
spawnCountdown -= 1;
} else {
rectangles.push(
createRectangle({
y: canvas.height / 2, // set at center
text: randItem(quote),
dx: 2, // set the x speed
})
);
spawnCountdown = spawnRate;
}
}
}
// define the default rectangle
const rectangle = {
x: -40, // this is the center of the rectangle
y: 0,
dx: 0, // delta x and y are the movement per frame
dy: 0,
w: 70, // size
h: 55,
color: "yellow",
text: null,
textColor: "black",
draw() { // function to draw this rectangle
ctx.fillStyle = this.color;
ctx.fillRect(this.x - this.w / 2, this.y - this.h / 2, this.w, this.h);
ctx.fillStyle = this.textColor;
ctx.fillText(this.text, this.x, this.y);
},
update() { // moves the rectangle
this.x += this.dx;
this.y += this.dy;
if (this.x > canvas.width + this.w / 2) {
this.x = -this.w / 2;
// if the rectangle makes it to the other
// side befor all rectangles are spawnd
// then reduce the number so you dont get any
// overlap
if (rectangles.length < maxRectangles) {
maxRectangles = rectangles.length;
}
}
}
}
// creats a new rectangle. Setting can hold any unique
// data for the rectangle
function createRectangle(settings) {
return Object.assign({}, rectangle, settings);
}
function updateAllRectangles() {
var i;
for (i = 0; i < rectangles.length; i++) {
rectangles[i].update();
}
}
function drawAllRectangles() {
var i;
for (i = 0; i < rectangles.length; i++) {
rectangles[i].draw();
}
}
canvas {
position: absolute;
top: 0px;
left: 0px;
background-color: #131217;
}
body {
margin: 0;
overflow: hidden;
}
<canvas id="canvas"></canvas>

Draw SVG on canvas [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
I have a set of coordinates and for every pair I'd like to place a SVG on the canvas. The problem is in getCircle function. The SVG doesn't work, but if I draw a circle(see commented code) it works.
function getCircle() {
var circle = document.createElement("canvas"),
ctx = circle.getContext("2d"),
r2 = radius + blur;
circle.width = circle.height = r2 * 2;
/*
ctx.shadowOffsetX = ctx.shadowOffsetY = r2 * 2;
ctx.shadowBlur = blur;
ctx.shadowColor = "purple";
ctx.beginPath();
ctx.arc(-r2, -r2, radius, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
*/
var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
};
img.src = "https://upload.wikimedia.org/wikipedia/en/0/09/Circle_Logo.svg";
return circle;
}
var radius = 5;
var blur = 1;
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
canvas.width = 400;
canvas.height = 200;
var circle = getCircle();
ctx.clearRect(0, 0, canvas.width, canvas.height);
var data = [[38,20,2]];
for (var i = 0, len = data.length, p; i < len; i++) {
p = data[i];
ctx.drawImage(circle, p[0] - radius, p[1] - radius);
}
#c {
width: 400px;
height: 200px;
}
<canvas id="c"></canvas>
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
canvas.width = 400;
canvas.height = 200;
var data = [[0,20,2],[125,20,2],[250,20,2]];
drawImage();
function drawImage() {
var img = new Image();
img.onload = function() {
for (var i = 0, len = data.length, p; i < len; i++) {
p = data[i];
ctx.drawImage(this, p[0] , p[1] , 150, 150);
}
};
img.src = "https://upload.wikimedia.org/wikipedia/en/0/09/Circle_Logo.svg";
}
#c {
width: 400px;
height: 200px;
}
<canvas id="c"></canvas>
in img.onload() draw your image in specified position with widht and height using drawImage().

Canvas particles arch

It's about Cavas animation.
How do I make my particles are not cubes but circles?
Codepen Link
CSS:
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
background:#000000;
}
JS
var cvs = document.createElement('canvas'),
context = cvs.getContext("2d");
document.body.appendChild(cvs);
var numDots = 300,
n = numDots,
currDot,
maxRad = 900,
minRad = 100,
radDiff = maxRad-minRad,
dots = [],
PI = Math.PI,
centerPt = {x:0, y:0};
resizeHandler();
window.onresize = resizeHandler;
while(n--){
currDot = {};
currDot.radius = minRad+Math.random()*radDiff;
currDot.radiusV = 0+Math.random()*200,
currDot.radiusVS = (0.5-Math.random()*10)*0.00000005,
currDot.radiusVP = Math.random()*0,
currDot.ang = (1-Math.random()*2)*PI;
currDot.speed = (1+Math.random()*0);
//currDot.speed = 1-Math.round(Math.random())*2;
//currDot.speed = 1;
currDot.intensityP = Math.random()*PI;
currDot.intensityS = Math.random()*0.0005;
currDot.intensityO = 64+Math.round(Math.random()*64);
currDot.intensityV = Math.min(Math.random()*255, currDot.intensityO);
currDot.intensity = Math.round(Math.random()*255);
currDot.fillColor = "rgb("+currDot.intensity+","+currDot.intensity+","+currDot.intensity+")";
dots.push(currDot);
}
function drawPoints(){
n = numDots;
var _centerPt = centerPt,
_context = context,
dX = 0,
dY = 0;
_context.clearRect(0, 0, cvs.width, cvs.height);
var radDiff;
//draw dots
while(n--){
currDot = dots[n];
currDot.radiusVP += currDot.radiusVS;
radDiff = currDot.radius+Math.sin(currDot.radiusVP)*currDot.radiusV;
dX = _centerPt.x+Math.sin(currDot.ang)*radDiff;
dY = _centerPt.y+Math.cos(currDot.ang)*radDiff;
//currDot.ang += currDot.speed;
currDot.ang += currDot.speed*radDiff/400000;
currDot.intensityP += currDot.intensityS;
currDot.intensity = Math.round(currDot.intensityO+Math.sin(currDot.intensityP)*currDot.intensityV);
//console.log(currDot);
_context.fillStyle= "rgb("+currDot.intensity+","+currDot.intensity+","+currDot.intensity+")";;
_context.fillRect(dX, dY, 2, 2);
} //draw dot
window.requestAnimationFrame(drawPoints);
}
function resizeHandler(){
var box = cvs.getBoundingClientRect();
var w = box.width;
var h = box.height;
cvs.width = w;
cvs.height = h;
centerPt.x = Math.round(w/2);
centerPt.y = Math.round(h/2);
}
drawPoints();
Thanks everyone
EDIT
You're not configuring your colors correctly! When you render circles you set a fillStyle and a strokeStyle, and since you aren't setting a strokeStyle, the circles aren't rendering (see canvas with white background)
After beginPath(), before the .stroke() command, you need to set the stroke:
_context.strokeStyle= "rgb("+currDot.intensity+","+currDot.intensity+","+currDot.intensity+")";
You're already rendering squares, which means that you have generated a point for each object to occupy. Now pick a radius, probably something small like 1 or 2, and use _context.arc in place of _context.fillRect, like so:
_context.beginPath();
_context.arc(dX, dY, 2, 0, 2 * Math.PI);
_context.stroke();
This will make replaces the squares with circles of a radius of 2 (as specified by the 3rd parameter)
I'm not sure what you want, but if you want to draw a circle on an HTML5 canvas, this is the code:
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI);
context.stroke();
Where:
center(X,Y) is your initial point on the canvas (which from looking at your code will be random)
radius is self-explanatory, you probably want it small for your case
0 is the starting angle for the arch
(2*Math.PI) is the end angle of the arch (in terms of Pi), which will complete a full circle
Hopefully this helped!

Categories

Resources