I've been stuck for days on a problem and I can't seem to find a solution. There is a gap between the cursor and the drawing and I don't know how to fix it.
I've set the height/width of the canvas in the HTML instead of in the CSS file, I've tried different ways to get the cursor position and also adding/removing some pixels to correct the gap on the lineTo line but it doesn't change anything.
For information, my canvas is located next to a map in a container with a width of 40%, I don't know if this is the problem but I don't want to change this.
class Canvas {
constructor(canvasId) {
this.canvas = canvasId;
this.ctx = this.canvas.getContext('2d');
this.signing = false;
}
load() {
this.start();
this.draw();
this.stop();
}
start() {
this.canvas.addEventListener('mousedown', () => {
this.signing = true;
const rectangle = this.canvas.getBoundingClientRect(e);
this.ctx.moveTo(e.clientX - rectangle.left, e.clientY - rectangle.top);
this.beginPath();
});
}
stop() {
this.canvas.addEventListener('mouseup', () => {
this.signing = false;
this.ctx.beginPath();
});
}
draw() {
this.canvas.addEventListener('mousemove', (e) => {
if (this.signing === true) {
this.ctx.lineWidth = '2';
this.ctx.lineCap = "round";
this.ctx.lineTo(e.clientX - this.canvas.offsetLeft - 65, e.clientY - this.canvas.offsetTop - 40);
this.ctx.stroke();
}
});
}
};
const signature = new Canvas(document.getElementById('to-sign'));
signature.load();
canvas {
border: solid;
}
<canvas id="to-sign" class="full-width" width="620" height="160">
Non supporté sur votre navigateur
</canvas>
This issue arises if you have a padding on your canvas. getBoundingClientRect returns the position and size of the element's border box. This includes the padding. See this Introduction to the CSS Box Model and this reference of getBoundingClientRect
Related
I am making a simple drawing board with HTML and JavaScript (Node as server side), but I don't know how I can make a colorpicker where I change the color of the paint. I know how to hard code it, but that is not what I want.
I want something like buttons, if you click a button it will turn into a specific color. Something like 4 buttons.
This is my method:
//Function for when the mouse button is clicked.
canvas.onmousedown = function (e) {
//The mouse button is clicked (true).
mouse.click = true;
context.strokeStyle = "red";
};
As you can see, I have hardcoded the color to red.
This is my full JavaScript code. The HTML document only consist of an element "canvas".
//"DOMContetLoaded tells the browser then the HTML page has finished loading.
document.addEventListener("DOMContentLoaded", function () {
//Add standard mouse functions.
var mouse = {
click: false,
move: false,
pos: { x: 0, y: 0 },
pos_prev: false
};
//Get the CanvasWhiteboard elements by it's ID from the HTML page. We need this to be able to draw.
var canvas = document.getElementById('whiteboard');
//The whiteboard is in 2D with the width and height being the dimensions of the window.
var context = canvas.getContext('2d');
var width = window.innerWidth;
var height = window.innerHeight;
//The client connects to the server.
var socket = io.connect();
//The width and height of the whiteboard equals the window width and height.
canvas.width = width;
canvas.height = height;
// Function for when the mouse button is pushed down.
canvas.onmousedown = function (e) {
// Set mouse click to true.
mouse.click = true;
context.strokeStyle = "red";
};
// Function for when the mouse button is lifted.
canvas.onmouseup = function (e) {
// Set mouse click to false.
mouse.click = false;
};
// Function to check if the mouse is moved.
canvas.onmousemove = function (e) {
//The movement of the mouse at X and Y position is updated everytime the mouse moves.
//The coordinates equals the coordinates relative to the window height and width.
mouse.pos.x = e.clientX / width;
mouse.pos.y = e.clientY / height;
//The mouse is moving (true).
mouse.move = true;
};
//Listen for draws from other clients.
socket.on('draw_data', function (data) {
//The line being drawn equals the data.
var line = data.line;
//Begin from the start of the drawn data.
context.beginPath();
//The thickness of the line.
context.lineWidth = 2;
//Next point to move to from the beginPath.
context.moveTo(line[0].x * width, line[0].y * height);
//End point to move to from the beginPath.
context.lineTo(line[1].x * width, line[1].y * height);
//The data is then drawn on the whiteboard.
context.stroke();
});
//This loop is where our own drawings are sent to the server for the other clients to see.
//It checks every 25ms if the mouse is being clicked and moved.
function mainLoop() {
if (mouse.click && mouse.move && mouse.pos_prev) {
//Send our drawing to the server.
socket.emit('draw_data', { line: [mouse.pos, mouse.pos_prev] });
//The mouse movement is set to false (reset).
mouse.move = false;
}
//The previous position now equals the position we just were at.
mouse.pos_prev = { x: mouse.pos.x, y: mouse.pos.y };
//This is where the 25ms is definend.
setTimeout(mainLoop, 25);
}
//Being called recursively.
mainLoop();
});
You can use CSS to do it too, changing the Canvas to red when click the button
canvas{
background-color:red;
}
Or try this code:
//Function for when the mouse button is clicked.
canvas.onmousedown = function (e) {
//The mouse button is clicked (true).
mouse.click = true;
ctx.fillStyle = 'red';
};
This was my solution:
In HTML I added a drop down box:
<!--Color Picker-->
<select id="colors">
<option value="black">black</option>
<option value="aqua">aqua</option>
<option value="blue">blue</option>
<option value="brown">brown</option>
</select>
In my JavaScript I added this to get the color picker by ID:
//Get the color picker from the HTML page by ID.
var getColorPickerByID = document.getElementById("colors");
And this to get the value of the color picker, i.e. the selected item. This should of course be in a loop that updates every like 10ms so the value gets updated when you pick a new color:
//Get the color picker value, i.e. if the user picks red the value is red.
var getValueOfColorPicker = getColorPickerByID.options[getColorPickerByID.selectedIndex].text;
And at last the strokeStyle itself to set the color of the line being drawn by using the value from the above variable getValueOfColorPicker
//Set the color of the line being drawn by using above variable "getValueOfColorPicker".
drawingArea.strokeStyle = getValueOfColorPicker;
I written sample code you can use it.
function changeColor(color) {
ctx.strokeStyle = color;
};
const c = document.querySelector("#c");
c.height = window.innerHeight / 2;
c.width = window.innerWidth / 2;
const ctx = c.getContext("2d");
let painting = false;
function mousedown(e) {
painting = true;
mousemove(e);
}
function mouseup() {
painting = false;
ctx.beginPath();
}
function mousemove(e) {
if (!painting) return;
ctx.lineWidth = 4;
ctx.lineCap = 'round';
//ctx.strokeStyle = 'black';
ctx.lineTo(e.clientX / 2, e.clientY / 2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(e.clientX / 2, e.clientY / 2);
}
c.addEventListener('mousedown', mousedown);
c.addEventListener('mouseup', mouseup);
c.addEventListener('mousemove', mousemove);
#c {
border: 1px solid black;
}
#container {
text-align: center;
}
.button {
width: 5em;
height: 2em;
text-align: center;
}
<html>
<head>
<meta charset="utf-8" content="Badri,Chorapalli,XML,JavaScript">
<title>Canvas</title>
</head>
<body>
<div id="container">
<canvas id="c" width="400" height="400"></canvas><br>
<button class="button" onclick="changeColor('black')" id="blue">Black</button>
<button class="button" onclick="changeColor('blue')" id="blue">Blue</button>
<button class="button" onclick="changeColor('red')" id="blue">Red</button>
<button class="button" onclick="changeColor('green')" id="blue">Green</button>
</div>
</body>
</html>
I'm trying to use Fabric.js with Fabric Brush This issue that I'm running into is that Fabric Brush only puts the brush strokes onto the Top Canvas and not the lower canvas. (The stock brushes in fabric.js save to the bottom canvas) I think I need to convert "this.canvas.contextTop.canvas" to an object and add that object to the the lower canvas. Any ideas?
I've tried running:
this.canvas.add(this.canvas.contextTop)
in
onMouseUp: function (pointer) {this.canvas.add(this.canvas.contextTop)}
But I'm getting the error
Uncaught TypeError: obj._set is not a function
So the contextTop is CanvasHTMLElement context. You cannot add it.
You can add to the fabricJS canvas just fabric.Object derived classes.
Look like is not possible for now.
They draw as pixel effect and then they allow you to export as an image.
Would be nice to extend fabricJS brush interface to create redrawable objects.
As of now with fabricJS and that particular version of fabric brush, the only thing you can do is:
var canvas = new fabric.Canvas(document.getElementById('c'))
canvas.freeDrawingBrush = new fabric.CrayonBrush(canvas, {
width: 70,
opacity: 0.6,
color: "#ff0000"
});
canvas.isDrawingMode = true
canvas.on('mouse:up', function(opt) {
if (canvas.isDrawingMode) {
var c = fabric.util.copyCanvasElement(canvas.upperCanvasEl);
var img = new fabric.Image(c);
canvas.contextTopDirty = true;
canvas.add(img);
canvas.isDrawingMode = false;
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.1/fabric.min.js"></script>
<script src="https://tennisonchan.github.io/fabric-brush/bower_components/fabric-brush/dist/fabric-brush.min.js"></script>
<button>Enter free drawing</button>
<canvas id="c" width="500" height="500" ></canvas>
That is just creating an image from the contextTop and add as an object.
I have taken the approach suggested by AndreaBogazzi and modified the Fabric Brush so that it does the transfer from upper to lower canvas (as an image) internal to Fabric Brush. I also used some code I found which crops the image to a smaller bounding box so that is smaller than the full size of the canvas. Each of the brushes in Fabric Brush has an onMouseUp function where the code should be placed. Using the case of the SprayBrush, the original code here was:
onMouseUp: function(pointer) {
},
And it is replaced with this code:
onMouseUp: function(pointer){
function trimbrushandcopytocanvas() {
let ctx = this.canvas.contextTop;
let pixels = ctx.getImageData(0, 0, canvas.upperCanvasEl.width, canvas.upperCanvasEl.height),
l = pixels.data.length,
bound = {
top: null,
left: null,
right: null,
bottom: null
},
x, y;
// Iterate over every pixel to find the highest
// and where it ends on every axis ()
for (let i = 0; i < l; i += 4) {
if (pixels.data[i + 3] !== 0) {
x = (i / 4) % canvas.upperCanvasEl.width;
y = ~~((i / 4) / canvas.upperCanvasEl.width);
if (bound.top === null) {
bound.top = y;
}
if (bound.left === null) {
bound.left = x;
} else if (x < bound.left) {
bound.left = x;
}
if (bound.right === null) {
bound.right = x;
} else if (bound.right < x) {
bound.right = x;
}
if (bound.bottom === null) {
bound.bottom = y;
} else if (bound.bottom < y) {
bound.bottom = y;
}
}
}
// Calculate the height and width of the content
var trimHeight = bound.bottom - bound.top,
trimWidth = bound.right - bound.left,
trimmed = ctx.getImageData(bound.left, bound.top, trimWidth, trimHeight);
// generate a second canvas
var renderer = document.createElement('canvas');
renderer.width = trimWidth;
renderer.height = trimHeight;
// render our ImageData on this canvas
renderer.getContext('2d').putImageData(trimmed, 0, 0);
var img = new fabric.Image(renderer,{
scaleY: 1./fabric.devicePixelRatio,
scaleX: 1./fabric.devicePixelRatio,
left: bound.left/fabric.devicePixelRatio,
top:bound.top/fabric.devicePixelRatio
});
this.canvas.clearContext(ctx);
canvas.add(img);
}
setTimeout(trimbrushandcopytocanvas, this._interval); // added delay because last spray was on delay and may not have finished
},
The setTimeout function was used because Fabric Brush could still be drawing to the upper canvas after the mouseup event occurred, and there were occasions where the brush would continue painting the upper canvas after its context was cleared.
I am trying to create a pannable image viewer which also allows magnification. If the zoom factor or the image size is such that the image no longer paints over the entire canvas then I wish to have the area of the canvas which does not contain the image painted with a specified background color.
My current implementation allows for zooming and panning but with the unwanted effect that the image leaves a tiled trail after it during a pan operation (much like the cards in windows Solitaire when you win a game). How do I clean up my canvas such that the image does not leave a trail and my background rectangle properly renders in my canvas?
To recreate the unwanted effect set magnification to some level at which you see the dark gray background show and then pan the image with the mouse (mouse down and drag).
Code snippet added below and Plnkr link for those who wish to muck about there.
http://plnkr.co/edit/Cl4T4d13AgPpaDFzhsq1
<!DOCTYPE html>
<html>
<head>
<style>
canvas{
border:solid 5px #333;
}
</style>
</head>
<body>
<button onclick="changeScale(0.10)">+</button>
<button onclick="changeScale(-0.10)">-</button>
<div id="container">
<canvas width="700" height="500" id ="canvas1"></canvas>
</div>
<script>
var canvas = document.getElementById('canvas1');
var context = canvas.getContext("2d");
var imageDimensions ={width:0,height:0};
var photo = new Image();
var isDown = false;
var startCoords = [];
var last = [0, 0];
var windowWidth = canvas.width;
var windowHeight = canvas.height;
var scale=1;
photo.addEventListener('load', eventPhotoLoaded , false);
photo.src = "http://www.html5rocks.com/static/images/cors_server_flowchart.png";
function eventPhotoLoaded(e) {
imageDimensions.width = photo.width;
imageDimensions.height = photo.height;
drawScreen();
}
function changeScale(delta){
scale += delta;
drawScreen();
}
function drawScreen(){
context.fillRect(0,0, windowWidth, windowHeight);
context.fillStyle="#333333";
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
}
canvas.onmousedown = function(e) {
isDown = true;
startCoords = [
e.offsetX - last[0],
e.offsetY - last[1]
];
};
canvas.onmouseup = function(e) {
isDown = false;
last = [
e.offsetX - startCoords[0], // set last coordinates
e.offsetY - startCoords[1]
];
};
canvas.onmousemove = function(e)
{
if(!isDown) return;
var x = e.offsetX;
var y = e.offsetY;
context.setTransform(1, 0, 0, 1,
x - startCoords[0], y - startCoords[1]);
drawScreen();
}
</script>
</body>
</html>
You need to reset the transform.
Add context.setTransform(1,0,0,1,0,0); just before you clear the canvas and that will fix your problem. It sets the current transform to the default value. Then befor the image is draw set the transform for the image.
UPDATE:
When interacting with user input such as mouse or touch events it should be handled independently of rendering. The rendering will fire only once per frame and make visual changes for any mouse changes that happened during the previous refresh interval. No rendering is done if not needed.
Dont use save and restore if you don't need to.
var canvas = document.getElementById('canvas1');
var ctx = canvas.getContext("2d");
var photo = new Image();
var mouse = {}
mouse.lastY = mouse.lastX = mouse.y = mouse.x = 0;
mouse.down = false;
var changed = true;
var scale = 1;
var imageX = 0;
var imageY = 0;
photo.src = "http://www.html5rocks.com/static/images/cors_server_flowchart.png";
function changeScale(delta){
scale += delta;
changed = true;
}
// Turns mouse button of when moving out to prevent mouse button locking if you have other mouse event handlers.
function mouseEvents(event){ // do it all in one function
if(event.type === "mouseup" || event.type === "mouseout"){
mouse.down = false;
changed = true;
}else
if(event.type === "mousedown"){
mouse.down = true;
}
mouse.x = event.offsetX;
mouse.y = event.offsetY;
if(mouse.down) {
changed = true;
}
}
canvas.addEventListener("mousemove",mouseEvents);
canvas.addEventListener("mouseup",mouseEvents);
canvas.addEventListener("mouseout",mouseEvents);
canvas.addEventListener("mousedown",mouseEvents);
function update(){
requestAnimationFrame(update);
if(photo.complete && changed){
ctx.setTransform(1,0,0,1,0,0);
ctx.fillStyle="#333";
ctx.fillRect(0,0, canvas.width, canvas.height);
if(mouse.down){
imageX += mouse.x - mouse.lastX;
imageY += mouse.y - mouse.lastY;
}
ctx.setTransform(scale, 0, 0, scale, imageX,imageY);
ctx.drawImage(photo,0,0);
changed = false;
}
mouse.lastX = mouse.x
mouse.lastY = mouse.y
}
requestAnimationFrame(update);
canvas{
border:solid 5px #333;
}
<button onclick="changeScale(0.10)">+</button><button onclick="changeScale(-0.10)">-</button>
<canvas width="700" height="500" id ="canvas1"></canvas>
Nice Code ;)
You are seeing the 'tiled' effect in your demonstration because you are painting the scaled image to the canvas on top of itself each time the drawScreen() function is called while dragging. You can rectify this in two simple steps.
First, you need to clear the canvas between calls to drawScreen() and second, you need to use the canvas context.save() and context.restore() methods to cleanly reset the canvas transform matrix between calls to drawScreen().
Given your code as is stands:
Create a function to clear the canvas. e.g.
function clearCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
In the canavs.onmousemove() function, call clearCanvas() and invoke context.save() before redefining the transform matrix...
canvas.onmousemove = function(e) {
if(!isDown) return;
var x = e.offsetX;
var y = e.offsetY;
/* !!! */
clearCanvas();
context.save();
context.setTransform(
1, 0, 0, 1,
x - startCoords[0], y - startCoords[1]
);
drawScreen();
}
... then conditionally invoke context.restore() at the end of drawScreen() ...
function drawScreen() {
context.fillRect(0,0, windowWidth, windowHeight);
context.fillStyle="#333333";
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
/* !!! */
if (isDown) context.restore();
}
Additionally, you may want to call clearCanvas() before rescaling the image, and the canvas background could be styled with CSS rather than .fillRect() (in drawScreen()) - which could give a performance gain on low spec devices.
Edited in light of comments from Blindman67 below
See Also
Canvas.context.save : https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save
Canvas.context.restore : https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/restore
requestAnimationFrame : https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame
Paul Irish, requestAnimationFrame polyfill : http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
Call context.save to save the transformation matrix before you call context.fillRect.
Then whenever you need to draw your image, call context.restore to restore the matrix.
For example:
function drawScreen(){
context.save();
context.fillStyle="#333333";
context.fillRect(0,0, windowWidth, windowHeight);
context.restore();
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
}
Also, to further optimize, you only need to set fillStyle once until you change the size of canvas.
I have this code
<canvas id="Con1" width="500" height="500"></canvas>
<script type="text/javascript">
var Rxt = document.getElementById('Con1').getContext('2d');
Rxt.fillStyle = 'green';
Rxt.fillRect(0, 0, 2000, 2000);
Rxt.rotate(.2);
var Img = document.createElement('img');
Img.src = 'images/009-Invoice1-A4-SET-PAD.png';
Img.onload = function () {
Rxt.drawImage(Img, 50, 0, 200, 200);
}
var down = false;
Rxt.canvas.addEventListener('mousedown', function () {
down = true;
}, false);
Rxt.canvas.addEventListener('mouseup', function () {
down = false;
}, false);
Rxt.canvas.addEventListener('mousemove', function (event) {
if (down) {
Rxt.translate(0, -50);
Rxt.drawImage(Img, event.clientX - this.offsetLeft,
event.clientY - this.offsetTop, 100, 100);
Rxt.translate(0, 50);
}
}, false);
</script>
I've tried this and I'm adding an image on the canvas and dragging it. It works but the image is dragging continuously and it prints duplicate images but the output should be smooth. I also want to add a label to that image and have it moveable.
You need to clear the canvas each time you want to draw something and redraw everything.
I'd also suggest that you look up canvas context .restore() and .save() as they are very useful when dealing with canvas transformations
As for the label you'd probably have to use the fillText or stokeText methods
See if this helps at all
http://pastebin.com/kuaTbuhW
I have a HTML5 canvas of a certain size on the page
<canvas id="canvas" width="200" height="100" style="border:1px solid #000000;">
Right now, the canvas is painted when the mouse is dragged over it (i.e. you click first(not necessarily inside the canvas) then start dragging the mouse over it without releasing) Script below;
$(function () {
var c = document.getElementById("canvas");
var context = c.getContext("2d");
var clickX = new Array();
var clickY = new Array();
var clickDrag = new Array();
var paint;
var $canvas=$("#canvas");
$(this).mousedown(function (e) {
paint = true;
addClick(e.pageX - $canvas[0].offsetLeft, e.pageY - $canvas[0].offsetTop);
redraw();
});
$(this).mousemove(function (e) {
if (paint) {
addClick(e.pageX - $canvas[0].offsetLeft, e.pageY - $canvas[0].offsetTop, true);
redraw();
}
});
$(this).mouseup(function (e) {
paint = false;
});
$(this).mouseleave(function (e) {
paint = false;
});
function addClick(x, y, dragging) {
clickX.push(x);
clickY.push(y);
clickDrag.push(dragging);
}
function redraw() {
context.clearRect(0, 0, context.canvas.width, context.canvas.height); // Clears the canvas
context.strokeStyle = "#000000";
context.lineJoin = "round";
context.lineWidth = 2;
for (var i = 0; i < clickX.length; i++) {
context.beginPath();
if (clickDrag[i] && i) {
context.moveTo(clickX[i - 1], clickY[i - 1]);
} else {
context.moveTo(clickX[i] - 1, clickY[i]);
}
context.lineTo(clickX[i], clickY[i]);
context.closePath();
context.stroke();
}
}
});
What I need now is a way to scale all the mouse coordinates on document during the document's mouse events to the relative coordinates inside the canvas.
so that no matter wherever you drag the mouse on the document it is drawn inside the canvas (in relatively small size of course). Any Idea how to achieve this?
http://jsfiddle.net/umwc5/3/
Why I need this?
It is for a signature application, When a user scribbles the signature using a tablet on a page(without seeing the page!) the entire signature is to be registered in a small canvas.
Update
The final working fiddle
The most important thing you were missing here was to multiply by the canvas/screen ratio.
First calculate the ratio:
var docToCanv = Math.min($canvas[0].width / $('body').width(), $canvas[0].height/$('body').height());
Then use it like this:
addClick(e.pageX*docToCanv, e.pageY*docToCanv);
Depending on the additional behavior you want, you may need to adjust the location a bit, but this should get you past the current issue you are having.
Demo