clearRect coordinates not matching mouse position on scaled canvas - javascript

$(document).ready(function(){
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.src = 'http://fariskassim.com/stage/strip/v3/img/before1.jpg';
function draw() {
//drawImageProp(ctx, this, 0, 0, canvas.width, canvas.height);
drawImageProp(ctx, this, 0, 0, canvas.width, canvas.height, 0.1, 0.5);
}
ERASE_W=150;
ERASE_H=70;
rzr=$("#razor2");
RAZOR_W=rzr.width();
RAZOR_H=rzr.height();
/**
* By Ken Fyrstenberg Nilsen
*
* drawImageProp(context, image [, x, y, width, height [,offsetX, offsetY]])
*
* If image and context are only arguments rectangle will equal canvas
*/
function drawImageProp(ctx, img, x, y, w, h, offsetX, offsetY) {
if (arguments.length === 2) {
x = y = 0;
w = ctx.canvas.width;
h = ctx.canvas.height;
}
/// default offset is center
offsetX = typeof offsetX === 'number' ? offsetX : 0.5;
offsetY = typeof offsetY === 'number' ? offsetY : 0.5;
/// keep bounds [0.0, 1.0]
if (offsetX < 0) offsetX = 0;
if (offsetY < 0) offsetY = 0;
if (offsetX > 1) offsetX = 1;
if (offsetY > 1) offsetY = 1;
var iw = img.width,
ih = img.height,
r = Math.min(w / iw, h / ih),
nw = iw * r, /// new prop. width
nh = ih * r, /// new prop. height
cx, cy, cw, ch, ar = 1;
/// decide which gap to fill
if (nw < w) ar = w / nw;
if (nh < h) ar = h / nh;
nw *= ar;
nh *= ar;
/// calc source rectangle
cw = iw / (nw / w);
ch = ih / (nh / h);
cx = (iw - cw) * offsetX;
cy = (ih - ch) * offsetY;
/// make sure source rectangle is valid
if (cx < 0) cx = 0;
if (cy < 0) cy = 0;
if (cw > iw) cw = iw;
if (ch > ih) ch = ih;
/// fill image in dest. rectangle
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}
});
var hand = $('#razor2')[0];
(function() {
var origin = {
x: $(window).height(),
y: $(window).width()
}
window.onmousemove = handleMouseMove;
function handleMouseMove(event) {
event = event || window.event; // IE-ism
// event.clientX and event.clientY contain the mouse position
hand.style.left = event.clientX-92+"px";
hand.style.top = event.clientY-5+"px";
var leftSide = false;
var d = {
x: origin.x - event.clientX,
y: origin.y - event.clientY
};
var angle = Math.atan2(d.x, d.y) * 90 / Math.PI * -1;
if (leftSide) {
//angle = angle * -1;
}
hand.style["-webkit-transform"] = "rotate("+parseInt(angle,10)+"deg)";
hand.style["transform"] = "rotate("+parseInt(angle,10)+"deg)";
}
})();
function erase(e){
var cvxw = ctx.canvas.width;
var parentOffset = $("#razor2").parent().offset();
//or $(this).offset(); if you really just want the current element's offset
var relX = e.pageX + 0;
var relY = e.pageY + 0;
ctx.clearRect((relX-(RAZOR_W/2)),(relY-(RAZOR_H/2)),ERASE_W,ERASE_H);
}
function draw_razor(e){
var parentOffset = $("#razor2").parent().offset();
//or $(this).offset(); if you really just want the current element's offset
var relX = e.pageX - parentOffset.left;
var relY = e.pageY - parentOffset.top;
rzr.show();
rzr.css('margin-top','-'+(RAZOR_H/2)+'px');
rzr.css('margin-left','-'+(RAZOR_W/2)+'px');
rzr.css('left',relX+'px');
rzr.css('top',relY+'px');
};
$(document).mousemove(function(e){
draw_razor(e);
});
$('#razor2').bind('mousedown', function(e){
$('#razor2').bind('mousemove', function(e){
erase(e);
hand.className = "active";
});
$('#razor2').bind('mouseup',function(){
$('#razor2').unbind('mousemove')
hand.className = "";
});
});
$('#razor2').on('dragstart', function(e) {
e.preventDefault();
});
* {
padding: 0;
margin: 0;
}
#game {
position: relative;
display: block;
width: 100vw;
font-size: 0;
-webkit-box-shadow: 3px 3px 7px 1px rgba(0, 0, 0, 0.8);
box-shadow: 3px 3px 7px 1px rgba(0, 0, 0, 0.8);
margin:0;
overflow:hidden;
}
body {
background-color: navajo;
background-position: center top;
background-repeat: no-repeat;
margin:0;
}
.canvas_wrap {
width: 100%;
height: 0;
padding-bottom: 50%;
position:relative;
overflow: hidden;
}
canvas {
/*cursor: none;*/
position: absolute;
background: transparent;
top:0;
left: 0;
z-index: 3;
width:100%;
height: 100%;
object-fit: cover;
}
#kitten{
width: 100%;
height: 100%;
object-fit: cover;
position:absolute;
top:0;
left: 0;
z-index: 2;
}
#razor2 {
background-image:url(http://fariskassim.com/stage/strip/v3/img/razor.png);
/*cursor: none;*/
position: absolute;
z-index: 98;
width: 800px;
height: 476px;
margin-top: -200px !important;
margin-left: -200px !important;
background-size: cover;
object-position: -99999px 99999px;
}
#razor2.active {
background-position: 0 100% !important;
}
#razor {
height: 0;
width: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="game">
<img id="razor" src="img/razor.png">
<div id="razor2"></div>
<div class="canvas_wrap">
<canvas id="canvas" width="1730" height="870">Los ti browser!</canvas>
<img id="kitten" src="http://fariskassim.com/stage/strip/v3/img/after1.jpg">
</div>
</div>
As you'll see, when you start dragging your mouse across the canvas, you'll see that the clearRect (shaving hair) is not taking place on the mouse position. its a little off to the left
i believe this is because i scaled the canvas to make it responsive.
Anyone knows how i modify my code to make the clearRect work on the position of the mouse?

Related

clickable object or shape inside a canvas viewbox window

<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<style>
body {
font-family: "Lato", sans-serif;
}
.sidenav {
height: 100%;
width: 0;
position: fixed;
z-index: 1;
top: 0;
left: 0;
background-color: #111;
overflow-x: hidden;
transition: 0.5s;
padding-top: 60px;
}
.sidenav a {
padding: 8px 8px 8px 32px;
text-decoration: none;
font-size: 25px;
color: #818181;
display: block;
transition: 0.3s;
}
.sidenav a:hover {
color: #f1f1f1;
}
.sidenav .closebtn {
position: absolute;
top: 0;
right: 25px;
font-size: 36px;
margin-left: 50px;
}
#main {
transition: margin-left .5s;
padding: 16px;
}
#media screen and (max-height: 450px) {
.sidenav {padding-top: 15px;}
.sidenav a {font-size: 18px;}
}
</style>
</head>
<body>
<div id="mySidenav" class="sidenav">
×
About
Services
Clients
Contact
</div>
<div id="main">
<span style="font-size:30px;cursor:pointer" onclick="openNav()">☰ Menu</span>
<canvas id="canvas" width="600" height="350" style="border:2px solid #d3d3d3;">></canvas>
</div>
<script>
var zoomIntensity = 0.1;
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = 90;
var height = 50;
var scale = 1;
var originx = 0;
var originy = 0;
var visibleWidth = width;
var visibleHeight = height;
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
function draw(){
context.fillStyle = "white";
context.fillRect(originx,originy,1000/scale,800/scale);
context.fillStyle = "blue";
context.fillRect(170,50,50,50);
context.fillStyle = "white";
context.fillText('click here',175,70);
}
setInterval(draw, 1000/60);
canvas.onwheel = function (event){
event.preventDefault();
var mousex = event.clientX - canvas.offsetLeft;
var mousey = event.clientY - canvas.offsetTop;
var wheel = event.deltaY < 0 ? 1 : -1;
var zoom = Math.exp(wheel*zoomIntensity);
context.translate(originx, originy);
originx -= mousex/(scale*zoom) - mousex/scale;
originy -= mousey/(scale*zoom) - mousey/scale;
context.scale(zoom, zoom);
context.translate(-originx, -originy);
scale *= zoom;
visibleWidth = width / scale;
visibleHeight = height / scale;
}
function openNav() {
document.getElementById("mySidenav").style.width = "200px";
document.getElementById("main").style.marginLeft = "200px";
}
function closeNav() {
document.getElementById("mySidenav").style.width = "0";
document.getElementById("main").style.marginLeft= "0";
}
</script>
</body>
</html>
is there away to make a shape or object clickable on canvas frame or viewbox, even when i zoom in or out, cause all the examples i saw was just a fixed clickable location. for instance google map locations when i zoom in i can click more objects.
is there away to make a shape or object clickable on canvas frame or viewbox, even when i zoom in or out, cause all the examples i saw was just a fixed clickable location. for instance google map locations when i zoom in i can click more objects.
There are two spaces you have to account for, the actual canvas space and the translated space. This means you need to translate the mouse coordinates in order to know where in the space the object is actually located and map it back to the canvas coordinates so you can know if you are clicking on it. This is achieved by keeping track of the offset value you provided in the var originx, but the problem is that there is no way based on my trails at least for you to translate the mouse click location if you use the context.translate(originx, originy) if you both scale and pan the object space.
I have rewritten the code without the use of the translate function by making my own translate function that enables you to translate between the canvas space and object space in order to register a click event on the object regardless of the panned or zoomed in location.
This function also has a click to pan feature so you can click and drag the object screen to where ever you want to object to be positioned.
/*jshint esversion: 8 */
(function() {
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
let canvasWidth = 600;
let canvasHeight = 600;
let offset = {
x: 0,
y: 0
};
let pan = {
x: 0,
y: 0
};
var zoomIntensity = 0.1;
let scale = 1;
let mouse = {
x: 0,
y: 0
};
let dragging = false;
let square;
let shapes = [{
x: 170,
y: 50,
w: 50,
h: 50,
color: "blue"
},
{
x: 85,
y: 25,
w: 50,
h: 50,
color: "red"
},
{
x: 50,
y: 100,
w: 50,
h: 50,
color: "green"
},
];
function init() {
canvas.width = canvasWidth;
canvas.height = canvasHeight;
renderCanvas();
}
function drawSquare(x, y, width, height, color) {
context.fillStyle = color;
context.fillRect(x, y, width, height);
}
function objectsToCanvasScreen(x, y) {
const canvasScreenX = Math.floor((x - offset.x) * scale);
const canvasScreenY = Math.floor((y - offset.y) * scale);
return {
x: canvasScreenX,
y: canvasScreenY
};
}
function canvasToObjectsScreen(x, y) {
return {
x: x / scale + offset.x,
y: y / scale + offset.y
};
}
function renderCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
shapes.forEach(({
x,
y,
w,
h,
color
}, index) => {
const {
x: csx,
y: csy
} = objectsToCanvasScreen(x, y);
shapes[index]._x = csx;
shapes[index]._y = csy;
shapes[index]._w = w * scale;
shapes[index]._h = h * scale;
drawSquare(csx, csy, w * scale, h * scale, color);
});
}
canvas.onwheel = function(e) {
const zoom = Math.exp((e.deltaY < 0 ? 1 : -1) * zoomIntensity);
const beforeZoom = canvasToObjectsScreen(mouse.x, mouse.y);
scale *= zoom;
const afterZoom = canvasToObjectsScreen(mouse.x, mouse.y);
offset.x += beforeZoom.x - afterZoom.x;
offset.y += beforeZoom.y - afterZoom.y;
renderCanvas();
};
canvas.onmousedown = function(e) {
if (e.button === 0) {
pan.x = mouse.x;
pan.y = mouse.y;
dragging = true;
}
};
canvas.onmouseup = (e) => (dragging = false);
canvas.onmousemove = function(e) {
mouse.x = e.offsetX;
mouse.y = e.offsetY;
if (dragging) {
offset.x -= (mouse.x - pan.x) / scale;
offset.y -= (mouse.y - pan.y) / scale;
pan.x = mouse.x;
pan.y = mouse.y;
renderCanvas();
}
};
canvas.onclick = function(e) {
shapes.forEach(({
_x,
_y,
_w,
_h,
color
}) => {
const {
x: mousex,
y: mousey
} = canvasToObjectsScreen(mouse.x, mouse.y);
const {
x: squarex,
y: squarey
} = canvasToObjectsScreen(_x, _y);
if (
mousex >= squarex &&
mousex <= _w / scale + squarex &&
mousey >= squarey &&
mousey <= _h / scale + squarey
) {
alert(`${color} clicked!`);
}
});
};
init();
})();
body {
font-family: "Lato", sans-serif;
}
#canvas {
background: #E1BC8B;
}
<body>
<div id="main">
<canvas id="canvas" width="300" height="350"></canvas>
</div>
</body>

How to transfer rectangle coordinates from one canvas to another in Html5?

I'm using some codes to dynamically transfer co-ordinates of a rectangle on canvas to the another canvas to draw rectangle on the exact position on canvas2.
I've gone through different similar questions and solutions like: link1 and link2 on stackoverflow, but none of them is working in my case.
sendimg() function is transferring rectangle co-ordinates from canvas1 to the canvas2. As the canvas2 image is scaled to large size, rectangles are not drawing on the correct position.
Please check the attached snippet or codepen link for detail.
var canvas = document.getElementById("canvas1");
var canvas2 = document.getElementById("canvas2");
var ctx = canvas.getContext("2d");
var ctx2 = canvas2.getContext("2d");
var img = new Image();
var rect = {};
var scale = 0;
var scale2 = 0;
var x = 0;
var y = 0;
var x2 = 0;
var y2 = 0;
var drag = false;
img.onload = function () {
//Setting dpi for canvas1
var dpi = window.devicePixelRatio || 1;
canvas.setAttribute('width', canvas.clientWidth * dpi);
canvas.setAttribute('height', canvas.clientHeight * dpi);
//Setting dpi for canvas2
var dpi = window.devicePixelRatio || 1;
canvas2.setAttribute('width', canvas2.clientWidth * dpi);
canvas2.setAttribute('height', canvas2.clientHeight * dpi);
ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
ctx.save();
ctx2.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
ctx2.save();
//fitting image to canvas fill
scale = Math.max(canvas.clientWidth / img.width, canvas.clientHeight / img.height); //canvas1 scale
scale2 = Math.max(canvas2.clientWidth / img.width, canvas2.clientHeight / img.height); //canvas2 scale
x = (canvas.clientWidth / 2) - (img.width / 2) * scale; //canvas1 x
y = (canvas.clientHeight / 2) - (img.height / 2) * scale; //canvas1 y
x2 = (canvas2.clientWidth / 2) - (img.width / 2) * scale2; //canvas2 x
y2 = (canvas2.clientHeight / 2) - (img.height / 2) * scale2; //canvas2 y
ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
ctx2.drawImage(img, x2, y2, img.width * scale2, img.height * scale2);
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
}
img.crossOrigin = "Anonymous";
img.src = 'https://i.imgur.com/1n8sbrF.jpg';
function mouseDown(e) {
rect.startX = e.clientX - this.offsetLeft;
rect.startY = e.clientY - this.offsetTop;
drag = true;
}
function mouseUp() {
drag = false;
console.log(rect);
}
function mouseMove(e) {
if (drag) {
ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
ctx.save();
ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
rect.w = (e.clientX - this.offsetLeft) - rect.startX;
rect.h = (e.clientY - this.offsetTop) - rect.startY;
ctx.lineWidth = 2;
ctx.strokeStyle = 'red';
ctx.strokeRect(rect.startX, rect.startY, rect.w, rect.h);
}
}
function sendimg()
{
if(rect.startX === undefined)
{
alert("draw any rectangle first on canvas1");
return false;
}
ctx2.lineWidth = 2;
ctx2.strokeStyle = "yellow";
//Following code is not drawing rectangle on correct position
ctx2.strokeRect(rect.startX * scale2 + x2 , rect.startY* scale2 + y2, rect.w * scale2 , rect.h * scale2);
}
html, body{
width: 90%;
height: 90%;
}
#div1 {
margin: 10px;
width: 800px;
height: 600px;
border: 2px solid red;
}
#div2 {
position: absolute;
top: 20px;
left: 900px;
margin: 10px;
width: 1200px;
height: 800px;
border: 2px solid red;
}
canvas {
width: 100%;
height: 100%;
}
<button type="button" onclick="sendimg();">Send Rectangle on Canvas2</button>
<div id="div1">
<canvas id="canvas1"></canvas>
</div>
<div id="div2">
<canvas id="canvas2"></canvas>
</div>
Some major and minor issues
IMPORTANT When you call ctx.save you push the current 2D canvas state to a stack. If you don't call ctx.restore there is no point calling ctx.save. Worse is that every call to save is chewing up memory.
window is the global this. You dont need to use it. eg window.devicePixelRatio is the same as devicePixelRatio
You don't need to use setAttribute. Only use setAttribute when you want the Markup to reflect a properties state and the property is not defined by the DOM.
When you set the canvas width or height the canvas is cleared and the state is reset to default. There is no need to clear the canvas after setting its size nor to save its state.
Simplify the math. You have something like (a / 2) - (b / 2) * c when you calculate the origin. It has a common divisor 2 and thus becomes (a - b) * c / 2
Coordinate system
Use a common coordinate system and convert when rendering to either canvas. As both canvas render the same image (and it seams that its the image that is the relevant coord system) use the image coordinate system.
You have 4 coordinate systems, the image, the scaled image canvas1, the scaled image canvas2 and rect as canvas1 pixels. The problem is that you are converting from canvas1 pixel coordinates (rect) to canvas2 image coordinates
To fix your scaling and positioning convert canvas1 pixel coordinates to the image coordinates (see example function mouseEvents). Then in your render function convert from image coords back to canvas pixels coords (see example function updateCanvas).
Example
I removed a lot of the unneeded and repeated code to reduce complexity and make the code more readable and maintainable. Used modern JS and added some UI
A single function to setup the canvas
A single function to handle the mouse events
A single function to render rectangle
Give feedback using cursor "crosshair" and "none"
For the example I removed the button (not relevant to the question), reduced the two canvas size to fit the very small snippet window, and have the second rectangle update live as the first rectangle is being plotted.
Note that rect is in image coordinates.
const COLOR1 = "#F00", COLOR2 = "#FF0", LINE_WIDTH = 2;
const ctx1 = canvas1.getContext("2d"), ctx2 = canvas2.getContext("2d");
const img = new Image;
const rect = {};
var drag = false;
img.src = "https://i.imgur.com/1n8sbrF.jpg";
img.addEventListener("load",() => {
setupCanvas(ctx1);
setupCanvas(ctx2);
canvas1.addEventListener("mousedown", mouseEvent);
canvas1.addEventListener("mouseup", mouseEvent);
canvas1.addEventListener("mousemove", mouseEvent);
}, {once: true});
function setupCanvas(ctx, coords = {}) {
const dPR = devicePixelRatio || 1;
const w = ctx.canvas.width = ctx.canvas.clientWidth * dPR;
const h = ctx.canvas.height = ctx.canvas.clientHeight * dPR;
const scale = coords.scale = Math.max(w / img.width, h / img.height);
const x = coords.x = (w - img.width * scale) / 2;
const y = coords.y = (h - img.height * scale) / 2;
ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
ctx.canvas.coords = coords;
}
function mouseEvent(e) {
var cursor = "crosshair";
const co = canvas1.coords;
const x = (e.clientX - this.offsetLeft - co.x) / co.scale;
const y = (e.clientY - this.offsetTop - co.y) / co.scale;
if (e.type === "mousedown") {
rect.x = x;
rect.y = y;
drag = true;
canvas1.title = "";
} else if (e.type === "mouseup") { drag = false }
if (drag) {
cursor = "none";
rect.w = x - rect.x;
rect.h = y - rect.y;
updateCanvas(ctx1, COLOR1, co);
updateCanvas(ctx2, COLOR2, canvas2.coords);
}
canvas1.style.cursor = cursor;
}
function updateCanvas(ctx, color, {x, y, scale}) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
ctx.lineWidth = LINE_WIDTH;
ctx.strokeStyle = color;
ctx.strokeRect(x + rect.x * scale, y + rect.y * scale, rect.w * scale, rect.h * scale);
}
html, body{
width: 90%;
height: 90%;
}
#div1 {
margin: 10px;
width: 200px;
height: 300px;
border: 2px solid red;
}
#div2 {
position: absolute;
top: 10px;
left: 240px;
width: 300px;
height: 200px;
border: 2px solid #FF0;
}
canvas {
width: 100%;
height: 100%;
}
<div id="div1">
<canvas id="canvas1" title="Click and drag to select area."></canvas>
</div>
<div id="div2">
<canvas id="canvas2"></canvas>
</div>
You need to reverse your scaling and translation of the first canvas before applying your transformation and scaling factors of the second canvas. This is like 90% of the way there, the Y axis is correct, but the X axis is translated wrong. See my call to ctx2.strokeRect and how it reverses the scaling and translation. My code is probably jacked because the canvas origin is in the top left and the formulas for translation and scaling assume it's the bottom left.
Edit: Fixed the code - it should be correct now.
var canvas = document.getElementById("canvas1");
var canvas2 = document.getElementById("canvas2");
var ctx = canvas.getContext("2d");
var ctx2 = canvas2.getContext("2d");
var img = new Image();
var rect = {};
var scale = 0;
var scale2 = 0;
var x = 0;
var y = 0;
var x2 = 0;
var y2 = 0;
var drag = false;
img.onload = function () {
//Setting dpi for canvas1
var dpi = window.devicePixelRatio || 1;
canvas.setAttribute('width', canvas.clientWidth * dpi);
canvas.setAttribute('height', canvas.clientHeight * dpi);
//Setting dpi for canvas2
var dpi = window.devicePixelRatio || 1;
canvas2.setAttribute('width', canvas2.clientWidth * dpi);
canvas2.setAttribute('height', canvas2.clientHeight * dpi);
ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
ctx.save();
ctx2.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
ctx2.save();
//fitting image to canvas fill
scale = Math.max(canvas.clientWidth / img.width, canvas.clientHeight / img.height); //canvas1 scale
scale2 = Math.max(canvas2.clientWidth / img.width, canvas2.clientHeight / img.height); //canvas2 scale
x = (canvas.clientWidth / 2) - (img.width / 2) * scale; //canvas1 x
y = (canvas.clientHeight / 2) - (img.height / 2) * scale; //canvas1 y
x2 = (canvas2.clientWidth / 2) - (img.width / 2) * scale2; //canvas2 x
y2 = (canvas2.clientHeight / 2) - (img.height / 2) * scale2; //canvas2 y
ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
ctx2.drawImage(img, x2, y2, img.width * scale2, img.height * scale2);
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
console.log(scale)
console.log(scale2)
}
img.crossOrigin = "Anonymous";
img.src = 'https://i.imgur.com/1n8sbrF.jpg';
function mouseDown(e) {
rect.startX = e.clientX - this.offsetLeft;
rect.startY = e.clientY - this.offsetTop;
drag = true;
}
function mouseUp() {
drag = false;
console.log(rect);
}
function mouseMove(e) {
if (drag) {
ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
ctx.save();
ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
rect.w = (e.clientX - this.offsetLeft) - rect.startX;
rect.h = (e.clientY - this.offsetTop) - rect.startY;
ctx.lineWidth = 2;
ctx.strokeStyle = 'red';
ctx.strokeRect(rect.startX, rect.startY, rect.w, rect.h);
}
}
function sendimg()
{
if(rect.startX === undefined)
{
alert("draw any rectangle first on canvas1");
return false;
}
ctx2.lineWidth = 2;
ctx2.strokeStyle = "yellow";
//Following code is not drawing rectangle on correct position
ctx2.strokeRect( ((rect.startX - x )/scale) * scale2 + x2,
((rect.startY - y )/scale) * scale2 + y2,
(rect.w/scale) * (scale2),
(rect.h/scale) * (scale2));
}
html, body{
width: 90%;
height: 90%;
}
#div1 {
margin: 10px;
width: 800px;
height: 600px;
border: 2px solid red;
}
#div2 {
position: absolute;
top: 20px;
left: 900px;
margin: 10px;
width: 1200px;
height: 800px;
border: 2px solid red;
}
canvas {
width: 100%;
height: 100%;
}
<button type="button" onclick="sendimg();">Send Rectangle on Canvas2</button>
<div id="div1">
<canvas id="canvas1"></canvas>
</div>
<div id="div2">
<canvas id="canvas2"></canvas>
</div>

Display Canvas behind DIV

This question title is relatively self explanatory. I'd like the canvas to be in the background of the page while the page's content starts where it normally would. I tried making a DIV for each with their own Z-index but this didn't do anything.
I need to figure out a way to send the canvas back, I've made the body background colour red so there only needs to be two layers (snow and content) rather than the three (background included).
I just need it so that the content displays on top as it would with any normal page, the snow should be a background element that goes along with the scrolling of the page.
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
window.requestAnimationFrame = requestAnimationFrame;
})();
var flakes = [],
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
flakeCount = 400,
mX = -100,
mY = -100
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function snow() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < flakeCount; i++) {
var flake = flakes[i],
x = mX,
y = mY,
minDist = 150,
x2 = flake.x,
y2 = flake.y;
var dist = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y)),
dx = x2 - x,
dy = y2 - y;
if (dist < minDist) {
var force = minDist / (dist * dist),
xcomp = (x - x2) / dist,
ycomp = (y - y2) / dist,
deltaV = force / 2;
flake.velX -= deltaV * xcomp;
flake.velY -= deltaV * ycomp;
} else {
flake.velX *= .98;
if (flake.velY <= flake.speed) {
flake.velY = flake.speed
}
flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
}
ctx.fillStyle = "rgba(255,255,255," + flake.opacity + ")";
flake.y += flake.velY;
flake.x += flake.velX;
if (flake.y >= canvas.height || flake.y <= 0) {
reset(flake);
}
if (flake.x >= canvas.width || flake.x <= 0) {
reset(flake);
}
ctx.beginPath();
ctx.arc(flake.x, flake.y, flake.size, 0, Math.PI * 2);
ctx.fill();
}
requestAnimationFrame(snow);
};
function reset(flake) {
flake.x = Math.floor(Math.random() * canvas.width);
flake.y = 0;
flake.size = (Math.random() * 3) + 2;
flake.speed = (Math.random() * 1) + 0.5;
flake.velY = flake.speed;
flake.velX = 0;
flake.opacity = (Math.random() * 0.5) + 0.3;
}
function init() {
for (var i = 0; i < flakeCount; i++) {
var x = Math.floor(Math.random() * canvas.width),
y = Math.floor(Math.random() * canvas.height),
size = (Math.random() * 3) + 2,
speed = (Math.random() * 1) + 0.5,
opacity = (Math.random() * 0.5) + 0.3;
flakes.push({
speed: speed,
velY: speed,
velX: 0,
x: x,
y: y,
size: size,
stepSize: (Math.random()) / 30,
step: 0,
opacity: opacity
});
}
snow();
};
canvas.addEventListener("mousemove", function(e) {
mX = e.clientX,
mY = e.clientY
});
window.addEventListener("resize",function(){
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
})
init();
body, html{
margin:0;
padding:0;
overflow-x: hidden;
user-select: none;
-moz-appearance: none;
-webkit-appearance: none;
background-color: #E71D36;
}
.page {
min-width: 100%;
max-width: 100%;
z-index: -1;
}
.contentwrap {
min-width: 100%;
max-width: 100%;
z-index: 1;
}
.content {
min-width: 50%;
max-width: 50%;
margin-left: auto;
margin-right: auto;
text-align: center;
z-index: 1;
}
#media only screen and (max-width: 500px) {
.content {
min-width: 90%;
max-width: 90%;
margin-left: auto;
margin-right: auto;
text-align: center;
z-index: 1;
}
}
.canvas-holder {
z-index: 0;
}
.canvas {
z-index: -1;
}
<div class="page">
<div class-"canvas-holder">
<canvas id="canvas">
</canvas>
</div>
<div class="content">
<h1>Content is below the canvas</h1>
</div>
</div>
Any tips would be appreciated.
Use position: absolute for the canvas and content. I've made some other changes to .content css to center the text (and added class="canvas" to the canvas html). I also removed z-index as it's not needed if you use absolute positioning.
You asked in a comment:
Any ideas how to make the canvas the height of the page, not just the
window. That'd be appreciated too
One option is to use position: fixed which will keep the canvas position fixed as the page scrolls.
Please see updated snippet.
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
window.requestAnimationFrame = requestAnimationFrame;
})();
var flakes = [],
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
flakeCount = 400,
mX = -100,
mY = -100
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function snow() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < flakeCount; i++) {
var flake = flakes[i],
x = mX,
y = mY,
minDist = 150,
x2 = flake.x,
y2 = flake.y;
var dist = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y)),
dx = x2 - x,
dy = y2 - y;
if (dist < minDist) {
var force = minDist / (dist * dist),
xcomp = (x - x2) / dist,
ycomp = (y - y2) / dist,
deltaV = force / 2;
flake.velX -= deltaV * xcomp;
flake.velY -= deltaV * ycomp;
} else {
flake.velX *= .98;
if (flake.velY <= flake.speed) {
flake.velY = flake.speed
}
flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
}
ctx.fillStyle = "rgba(255,255,255," + flake.opacity + ")";
flake.y += flake.velY;
flake.x += flake.velX;
if (flake.y >= canvas.height || flake.y <= 0) {
reset(flake);
}
if (flake.x >= canvas.width || flake.x <= 0) {
reset(flake);
}
ctx.beginPath();
ctx.arc(flake.x, flake.y, flake.size, 0, Math.PI * 2);
ctx.fill();
}
requestAnimationFrame(snow);
};
function reset(flake) {
flake.x = Math.floor(Math.random() * canvas.width);
flake.y = 0;
flake.size = (Math.random() * 3) + 2;
flake.speed = (Math.random() * 1) + 0.5;
flake.velY = flake.speed;
flake.velX = 0;
flake.opacity = (Math.random() * 0.5) + 0.3;
}
function init() {
for (var i = 0; i < flakeCount; i++) {
var x = Math.floor(Math.random() * canvas.width),
y = Math.floor(Math.random() * canvas.height),
size = (Math.random() * 3) + 2,
speed = (Math.random() * 1) + 0.5,
opacity = (Math.random() * 0.5) + 0.3;
flakes.push({
speed: speed,
velY: speed,
velX: 0,
x: x,
y: y,
size: size,
stepSize: (Math.random()) / 30,
step: 0,
opacity: opacity
});
}
snow();
};
canvas.addEventListener("mousemove", function(e) {
mX = e.clientX,
mY = e.clientY
});
window.addEventListener("resize",function(){
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
})
init();
body, html{
margin:0;
padding:0;
overflow-x: hidden;
user-select: none;
-moz-appearance: none;
-webkit-appearance: none;
background-color: #E71D36;
}
.page {
min-width: 100%;
max-width: 100%;
}
.contentwrap {
min-width: 100%;
max-width: 100%;
}
.content {
min-width: 100%;
max-width: 100%;
margin-left: auto;
margin-right: auto;
text-align: center;
position: absolute;
left: 0;
right: 0;
}
#media only screen and (max-width: 500px) {
.content {
min-width: 90%;
max-width: 90%;
margin-left: auto;
margin-right: auto;
text-align: center;
}
}
.canvas-holder {
}
.canvas {
position: fixed;
}
<div class="page">
<div class="canvas-holder">
<canvas id="canvas" class="canvas">
</canvas>
</div>
<div class="content">
<h1>Content is below the canvas</h1>
<p style="height: 900px;"></p>
</div>
</div>

How to resize Canvas Width on Viewport

I have a canvas demo that spawns some circles. I can't seem to figure out how to have it be responsive in setting either canvas.width or canvas.style.width (what's the difference here?) on a windows resize using window.innerWidth.
Code works fine but it rerenders strangely on smaller viewports. I tried adding this snippet of code at the bottom of my javascript file, but it broke animation.
// ADDING THESE LINES PREVENTS ANIMATION FROM RUNNING
if (window.innerWidth < 1000) {
canvas.width = window.innerWidth;
} else {
canvas.width = 1000;
}
var canvas = document.querySelector("canvas");
canvas.width = 1000;
canvas.height = 100;
var c = canvas.getContext("2d");
// Constructor Function (object blueprint)
function Circle(x, y, dx, dy, radius, counter) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.counter = counter;
this.draw = function() {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.strokeStyle = "white";
c.stroke();
c.fillStyle = "white";
c.fill();
};
this.update = function() {
if (this.y + this.radius > canvas.height) {
this.y = 0;
}
this.x += this.dx;
this.y += this.dy;
this.draw();
};
}
// Initialize array to store snow objects
var circleArray = [];
// Initialize objects with constructor
for (var i = 0; i < 50; i++) {
var radius = 1 + Math.random() * 5;
var x = Math.random() * canvas.width;
var y = 0 - Math.random() * 50; // start at top, render some circles off screen
var dx = (Math.random() - 0.5) * 2;
var dy = 0.5 + Math.random() * 0.5; // use gravity
circleArray.push(new Circle(x, y, dx, dy, radius, 0));
}
function animate() {
requestAnimationFrame(animate); // recurisvely run
c.clearRect(0, 0, innerWidth, innerHeight); // erases previously drawn content
for (var i = 0; i < circleArray.length; i++) {
circleArray[i].update();
}
// ADDING THESE LINES PREVENTS ANIMATION FROM RUNNING
//if (window.innerWidth < 1000) {
// canvas.width = window.innerWidth;
// } else {
// canvas.width = 1000;
// }
}
animate();
body {
background-color: grey;
display: flex;
justify-content: center;
}
.wrapper {
position: relative;
width: 1000px;
height: 110px;
min-height: 110px;
margin-top: 50vh;
}
.wrapper > * {
width: 1000px;
position: absolute;
}
canvas {
position: absolute;
}
img {
width: 100%;
height: 110px;
}
<div class="wrapper">
<img class="stars" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/867725/stars.png
" alt="" />
<img class="tress" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/867725/trees.png
" alt="" />
<img class="clouds" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/867725/clouds.png" alt="" />
<canvas></canvas>
</div>
The difference between canvas.width and canvas.style.width is that canvas.width specifies the actual size of the canvas in pixels, while canvas.style.width stretches or compresses the canvas to the width you specify. This will set the canvas width to 300px:
canvas.width = 300;
This will also set the canvas width to 300px, but will stretch it until it reaches 600px:
canvas.width = 300;
canvas.style.width = "600px";
Using canvas.width is better practice.
For the animation to run you must first fix the canvas width. Putting the code that resizes the canvas at the beginning of the animate() function solves the problem:
var canvas = document.querySelector("canvas");
canvas.width = 1000;
canvas.height = 100;
var c = canvas.getContext("2d");
// Constructor Function (object blueprint)
function Circle(x, y, dx, dy, radius, counter) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.counter = counter;
this.draw = function() {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.strokeStyle = "white";
c.stroke();
c.fillStyle = "white";
c.fill();
};
this.update = function() {
if (this.y + this.radius > canvas.height) {
this.y = 0;
}
this.x += this.dx;
this.y += this.dy;
this.draw();
};
}
// Initialize array to store snow objects
var circleArray = [];
// Initialize objects with constructor
for (var i = 0; i < 50; i++) {
var radius = 1 + Math.random() * 5;
var x = Math.random() * canvas.width;
var y = 0 - Math.random() * 50; // start at top, render some circles off screen
var dx = (Math.random() - 0.5) * 2;
var dy = 0.5 + Math.random() * 0.5; // use gravity
circleArray.push(new Circle(x, y, dx, dy, radius, 0));
}
function animate() {
if (window.innerWidth < 1000) {
canvas.width = window.innerWidth;
} else {
canvas.width = 1000;
}
requestAnimationFrame(animate); // recurisvely run
c.clearRect(0, 0, innerWidth, innerHeight); // erases previously drawn content
for (var i = 0; i < circleArray.length; i++) {
circleArray[i].update();
}
}
animate();
body {
background-color: grey;
display: flex;
justify-content: center;
}
.wrapper {
position: relative;
width: 1000px;
height: 110px;
min-height: 110px;
margin-top: 50vh;
}
.wrapper>* {
width: 1000px;
position: absolute;
}
canvas {
position: absolute;
}
img {
width: 100%;
height: 110px;
}
<div class="wrapper">
<img class="stars" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/867725/stars.png
" alt="" />
<img class="tress" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/867725/trees.png
" alt="" />
<img class="clouds" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/867725/clouds.png" alt="" />
<canvas></canvas>
</div>

margin right not working on canvas html5 and trouble with scroll bar removal

I have the following canvas:
Codepen link
What I want: Equal margin on both sides of canvas without any horizontal scroll bars.
Problem: margin-right property does not to work. I have seen some solutions that solve this problem by specifying a fixed width, but I cannot have a fixed width in my case. I want my canvas to adjust its width height according to the size of the window.
The following Javascript takes care of that:
window.addEventListener('resize' , resizeCanvas , false);
function resizeCanvas(){
canvas.width = window.innerWidth;
canvas.height = window.innerHeight/1.2;
}
So is there a different solution?
For the overflow problem, if I put overflow-x: hidden inside the body then only the scrollbar disappears but the problem persists. The canvas still extends past the screen hence the right border of the canvas is No longer visible.
See here
Here is my code:
HTML
<body onload="start()">
<canvas id="myCanvas"></canvas>
</body>
CSS
body{
}
canvas{
border: 1px solid black;
border-radius: 5px;
background-color: #fff;
margin: auto 50px auto 50px; /* works for left margin but not for right */
}
Thanks!
Another thing:
I have not set width: 100% for the canvas because it distorts the content inside it.
As Chris is saying you need to set the width of the canvas lower than the full width of the page:
canvas.width = window.innerWidth - 100;
Note that you need to take the border-width of the canvas and in your codepen the body also has a margin of 8px into account as well:
canvas.width = window.innerWidth - 118;
CSS calc() method is what you need. Just subtract the margins from 100% and you get the desired result. See the demo below. CSS calc() reference
function start() {
var canvas = document.getElementById('myCanvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight / 1.2;
var ctx = canvas.getContext('2d');
function rand(min, max) {
return parseInt(Math.random() * (max - min + 1), 10) + min;
}
function get_random_color() {
var h = rand(1, 360);
var s = rand(30, 100);
var l = rand(30, 70);
return 'hsl(' + h + ',' + s + '%,' + l + '%)';
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var balls = [];
var ballCount = getRandomInt(2, 10);
//document.getElementById('ballCountInfo').innerHTML = ballCount;
//document.getElementById('box').innerHTML = ballCount;
var startpointX = 100;
var startpointY = 50;
for (var i = 0; i < ballCount; i++) {
var randValue = getRandomInt(20, 30);
balls.push({
x: startpointX,
y: startpointY,
vx: getRandomInt(3, 3) * direction(),
vy: getRandomInt(1, 1) * direction(),
radius: randValue,
mass: randValue,
color: get_random_color(),
draw: function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
}
});
startpointX = startpointX + 50;
startpointY = startpointY + 40;
}
function direction() {
var chosenValue = Math.random() < 0.5 ? 1 : -1;
return chosenValue;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < ballCount; i++) {
balls[i].draw();
balls[i].x += balls[i].vx;
balls[i].y += balls[i].vy;
if ((balls[i].y + balls[i].vy + balls[i].radius) > canvas.height || (balls[i].y + balls[i].vy - balls[i].radius) < 0) {
balls[i].vy = -balls[i].vy;
}
if ((balls[i].x + balls[i].vx + balls[i].radius) > canvas.width || (balls[i].x + balls[i].vx - balls[i].radius) < 0) {
balls[i].vx = -balls[i].vx;
}
}
// onBoxTouched();
//collision check
for (var i = 0; i < ballCount; i++) {
for (var j = i + 1; j < ballCount; j++) {
var distance = Math.sqrt(
(balls[i].x - balls[j].x) * (balls[i].x - balls[j].x) +
(balls[i].y - balls[j].y) * (balls[i].y - balls[j].y)
);
if (distance < (balls[i].radius + balls[j].radius)) {
var ax = (balls[i].vx * (balls[i].mass - balls[j].mass) + (2 * balls[j].mass * balls[j].vx)) / (balls[i].mass + balls[j].mass);
var ay = (balls[i].vy * (balls[i].mass - balls[j].mass) + (2 * balls[j].mass * balls[j].vy)) / (balls[i].mass + balls[j].mass);
balls[j].vx = (balls[j].vx * (balls[j].mass - balls[i].mass) + (2 * balls[i].mass * balls[i].vx)) / (balls[i].mass + balls[j].mass);
balls[j].vy = (balls[j].vy * (balls[j].mass - balls[i].mass) + (2 * balls[i].mass * balls[i].vy)) / (balls[i].mass + balls[j].mass);
balls[i].vx = ax;
balls[i].vy = ay;
}
}
}
raf = window.requestAnimationFrame(draw);
}
function onBoxTouched() {
for (var i = 0; i < ballCount; i++) {
if (balls[i].x + balls[i].radius > 600 && balls[i].x + balls[i].radius < 750 &&
balls[i].y + balls[i].radius > 200 && balls[i].y + balls[i].radius < 350) {
//var ele = document.getElementById("box");
ele.style.backgroundColor = balls[i].color;
balls.splice(i, 1);
ballCount = ballCount - 1;
if (ballCount == 0) {
ele.style.fontSize = "x-large";
ele.innerHTML = "Over";
} else {
ele.innerHTML = ballCount;
}
//document.getElementById('ballCountInfo').innerHTML=" "+ballCount;
}
}
}
window.requestAnimationFrame(draw);
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight / 1.2;
}
}
* {}
html,
body {}
canvas {
border: 1px solid black;
border-radius: 5px;
background-color: #fff;
width: calc(100% - 40px);
/*substract the total margin from 100% and will automoatically adjuts accordint to your need*/
margin: auto 20px auto 20px;
/* works for left margin but not for right */
}
#box {
width: 150px;
height: 150px;
background-color: plum;
border-radius: 5px;
position: absolute;
top: 200px;
left: 600px;
font-size: 72px;
font-weight: bold;
color: white;
line-height: 150px;
text-align: center;
}
#info {
float: left;
font-size: 24px;
color: #6D8390;
margin-top: 20px;
}
<body onload="start()">
<canvas id="myCanvas"></canvas>
</body>
Hope it helps :)
instead of messing around with margins, just change the width of your canvas and center it.
CSS
canvas {
border: 1px solid black;
border-radius: 5px;
background-color: #fff;
width: 90%!important;
}
HTML
<body onload="start()">
<center>
<canvas id="myCanvas"></canvas>
</center>
</body>
https://codepen.io/anon/pen/GEmLPL

Categories