Add local storage to save canvas drawing - javascript

I do not know how to add local storage to save the drawing made on the canvas so when the page is reloaded the existing drawing is loaded onto the canvas through local storafe. I do not have must experience so would appreciate if someone could edit my code with the local storage addition. Many thanks in advance!
Here is my JS:
var canvas, ctx,
brush = {
x: 0,
y: 0,
color: '#000000',
size: 10,
down: false,
},
strokes = [],
currentStroke = null;
function redraw () {
ctx.clearRect(0, 0, canvas.width(), canvas.height());
ctx.lineCap = 'round';
for (var i = 0; i < strokes.length; i++) {
var s =strokes[i];
ctx.strokeStyle = s.color;
ctx.lineWidth = s.size;
ctx.beginPath();
ctx.moveTo(s.points[0].x, s.points[0].y);
for (var j = 0; j < s.points.length; j++){
var p = s.points[j];
ctx.lineTo(p.x, p.y);
}
ctx.stroke();
}
}
function init () {
canvas = $('#draw');
canvas.attr({
width: window.innerWidth,
height: window.innerHeight,
});
ctx = canvas[0].getContext('2d');
function mouseEvent (e){
brush.x = e.pageX;
brush.y = e.pageY;
currentStroke.points.push({
x: brush.x,
y: brush.y,
});
redraw();
}
canvas.mousedown(function (e){
brush.down = true;
currentStroke = {
color: brush.color,
size: brush.size,
points: [],
};
strokes.push(currentStroke);
mouseEvent(e);
}) .mouseup(function (e) {
brush.down = false;
mouseEvent(e);
currentStroke = null;
}) .mousemove(function (e) {
if (brush.down)
mouseEvent(e);
});
$('#save-btn').click(function () {
window.open(canvas[0].toDataURL());
});
$('#undo-btn').click(function (){
strokes.pop();
redraw();
});
$('#clear-btn').click(function (){
strokes = [];
redraw();
});
$('#color-picker').on('input', function () {
brush.color = this.value;
});
$('#brush-size').on('input', function () {
brush.size = this.value;
});
}
$(init);

Canvas.js will save the canvas as an image to the localStorage which is not helpful in your case as you're storing the mouse events in an array.
If you're looking for a solution which lets continue the drawing on canvas along with restoring old (saved) elements, what you would need is storing of the canvas elements (strokes array in your case) in the localStorage and restoring, redrawing the canvas.
Here's a demo doing that:
JS FIDDLE DEMO
Draw anything on the canvas.
Click on the "Save to local storage" button at the bottom.
Refresh the page.
To clear localStorage, clear the browser cache.
Relevant code changes:
Added a button in HTML to save to local storage
<button id="save-to-local-storage">
Save to local storage
</button>
Saving the strokes array to the localStorage on above button click.
$('#save-to-local-storage').click(function () {
localStorage.setItem('canvas_strokes', JSON.stringify(strokes));
});
On page refresh, check if localStorage has items set and if YES, redraw the canvas:
// check if localstorage has an array of strokes saved
if(localStorage.getItem('canvas_strokes')) {
strokes = JSON.parse(localStorage.getItem('canvas_strokes'));
redraw();
}
Let me know if you have any questions. Hope this helps. :)

Using the Canvas.js helper you can simply do:
const canvas = new Canvas( 'my-canvas' );
canvas.saveToStorage( 'balls' );
where
my-canvas is the canvas id
balls is the key to save it as
To later restore a canvas state:
const canvas = new Canvas( 'my-canvas' );
canvas.restoreFromStorage( 'balls' );
Load Canvas.js:
<script type="text/javascript" src="https://gustavgenberg.github.io/handy-front-end/Canvas.js">
EDIT
After reading the below post (Thank you #Shashank), ive made a jsfiddle with the complete code to achieve continous drawing. It automatically saves the last stroke on mouseup and loads it in on refresh. Check it out!
It uses Canvas.js and Pointer.js:
https://jsfiddle.net/wk5ttqa2/
EDIT 2
Just for fun... this is as simple as it can get really:
https://jsfiddle.net/GustavGenberg/1929f15t/1/
Note that it does not draw complete lines when moving fast (depends on framerate)...

Related

Normalize drawing in FabricJS?

In short, I'm making an app where people can collaborate and draw over an image.
I have a proof-of-concept right now, but I've noticed that the drawings show up with incorrect scale on the other person's browser if the two people collaborating are using different sized windows.
The solution is to normalize the drawing input so that points are expressed as a percentage of the canvas that they span, instead of absolute pixel values. However, I don't know how to do this in FabricJS. Finally, I need to be certain that the solution works when zoomed as well.
Any advice for normalizing drawing input would be appreciated! For reference, here is my code so far.
Be warned: I've never used FabricJS before, so this code sample is a mashup of several blog posts and SO answers. This is not good code and will be refactored entirely if FabricJS is the library I decide to go with
The important lines have been commented
document.addEventListener('DOMContentLoaded', () => {
const room = window.location.href.split('/').pop();
const socket = io.connect();
socket.on('connect', () => {
socket.emit('room', room);
});
var canvas = new fabric.Canvas('map', {
isDrawingMode: true
});
let size = Math.min(window.innerWidth, window.innerHeight);
canvas.setHeight(size);
canvas.setWidth(size);
var img = new Image();
img.onload = function () {
canvas.setBackgroundImage(img.src, canvas.renderAll.bind(canvas), {
width: canvas.width,
height: canvas.height,
});
};
img.src = "/images/map.jpg";
canvas.wrapperEl.addEventListener('wheel', (e) => {
if (e.deltaY <= 0) {
canvas.zoomToPoint({
x: e.offsetX,
y: e.offsetY
}, canvas.getZoom() * 1.1);
} else {
canvas.zoomToPoint({
x: e.offsetX,
y: e.offsetY
}, canvas.getZoom() * 0.9);
}
});
canvas.on('path:created', function (e) {
// This is where I need to normalize the path data
canvas.remove(fabric.Path.fromObject(JSON.stringify(e.path)));
socket.emit('draw_line', {
line: e.path.toJSON(),
room: room
});
});
socket.on('draw_line', function (path) {
// This is where I need to convert the path data from percentages to real size
fabric.util.enlivenObjects([path], function (objects) {
objects.forEach(function (o) {
canvas.add(o);
});
});
});
var panning = false;
canvas.on('mouse:up', function (e) {
panning = false;
});
canvas.on('mouse:out', function (e) {
panning = false;
});
canvas.on('mouse:down', function (e) {
panning = true;
});
canvas.on('mouse:move', function (e) {
//allowing pan only if the image is zoomed.
if (panning && e && e.e && e.e.shiftKey) {
var delta = new fabric.Point(e.e.movementX, e.e.movementY);
canvas.relativePan(delta);
}
});
});
A general working solution is that you give your drawing board a virtual fixed dimension:
15.000 x 15.000 for example.
Then in each client app you make the canvas as big as you want, for example 1000 x 1000 and another will get 800 x 800.
Setting the canvas zoom to each one will make possible to see the drawing as big as needed mantaining the same point values on the 15.000 space coordinates.
The drawing thickness will probably change with zoom level.
canvas.setZoom(1000/15000) and canvas.setZoom(800/15000) should be enough.
If it does not work, there is a bug somewhere, that should be fixed.

Unable to select objects after drawing in FabricJS

I am trying to free draw rectangle on canvas.
Here's my JSFiddle.
Code:
var canvas1 = new fabric.Canvas("canvas2");
var freeDrawing = true;
var divPos = {};
var offset = $("#canvas2").offset();
$(document).mousemove(function(e) {
divPos = {
left : e.pageX - offset.left,
top : e.pageY - offset.top
};
});
$('#2').click(function() {
console.log("Button 2 cilcked");
// Declaring the variables
var isMouseDown = false;
var refRect;
// Setting the mouse events
canvas1.on('mouse:down', function(event) {
// Defining the procedure
isMouseDown = true;
// Getting yhe mouse Co-ordinates
// Creating the rectangle object
if (freeDrawing) {
var rect = new fabric.Rect({
left : divPos.left,
top : divPos.top,
width : 0,
height : 0,
stroke : 'red',
strokeWidth : 3,
fill : ''
});
canvas1.add(rect);
refRect = rect; // **Reference of rectangle object
}
});
canvas1.on('mouse:move', function(event) {
// Defining the procedure
if (!isMouseDown) {
return;
}
// Getting yhe mouse Co-ordinates
if (freeDrawing) {
var posX = divPos.left;
var posY = divPos.top;
refRect.setWidth(Math.abs((posX - refRect.get('left'))));
refRect.setHeight(Math.abs((posY - refRect.get('top'))));
canvas1.renderAll();
}
});
canvas1.on('mouse:up', function() {
// alert("mouse up!");
isMouseDown = false;
// freeDrawing=false; // **Disables line drawing
});
});
The problem that I am facing is after drawing a rectangle I am unable to move, resize or at least select the drawn rectangle.
Mistake is you are not adding the object finally when mouse is up. Just change the mouse:up event function like this:
canvas1.on('mouse:up', function() {
// alert("mouse up!");
canvas1.add(refRect);
isMouseDown = false;
// freeDrawing=false; // **Disables line drawing
});
It will work fine. :)
I am also facing the same issue, thanks for the solution provided. If you noticed in this fiddle, duplicate object is creating when moving the shape.
How to solve the issue.
$(document).ready(function(){
//Getting the canvas
var canvas1 = new fabric.Canvas("canvas2");
var freeDrawing = true;
var divPos = {};
var offset = $("#canvas2").offset();
$(document).mousemove(function(e){
divPos = {
left: e.pageX - offset.left,
top: e.pageY - offset.top
};
});
$('#2').click(function(){
console.log("Button 2 cilcked");
//Declaring the variables
var isMouseDown=false;
var refRect;
//Setting the mouse events
canvas1.on('mouse:down',function(event){
//Defining the procedure
isMouseDown=true;
//Getting yhe mouse Co-ordinates
//Creating the rectangle object
if(freeDrawing) {
var rect=new fabric.Rect({
left:divPos.left,
top:divPos.top,
width:0,
height:0,
stroke:'red',
strokeWidth:3,
fill:''
});
canvas1.add(rect);
refRect=rect; //**Reference of rectangle object
}
});
canvas1.on('mouse:move', function(event){
// Defining the procedure
if(!isMouseDown)
{
return;
}
//Getting yhe mouse Co-ordinates
if(freeDrawing) {
var posX=divPos.left;
var posY=divPos.top;
refRect.setWidth(Math.abs((posX-refRect.get('left'))));
refRect.setHeight(Math.abs((posY-refRect.get('top'))));
canvas1.renderAll();
}
});
canvas1.on('mouse:up',function(){
//alert("mouse up!");
canvas1.add(refRect);
isMouseDown=false;
//freeDrawing=false; // **Disables line drawing
});
});
});
JS Fiddle Link here.
http://jsfiddle.net/PrakashS/8u1cqasa/75/
None of the other answer's implementations worked for me. All seem to be un compatible with latest fabric.js versions or have important issues.
So I implemented my own (check the JS in the HTML sources)
https://cancerberosgx.github.io/demos/misc/fabricRectangleFreeDrawing.html?d=9
supports two modes: one that will create and update a rectangle onmousedown and another that won't and only create the rectangle onmouseup
doesn't depend on any other library other than fabric.js (no jquery, no react no nothing)
Doesn't use offsets or event.clientX, etc to track the coordinates. It just uses fabric event.pointer API to compute it. This is safe and compatible since it doesn't depend on the DOM and just on a fabric.js public API
carefully manages event listeners
TODO:
I probably will also add throttle support for mousemove
Thanks

Creating a scratch card in EaselJS

I've been trying to develop a scratch card in EaselJS.
So far, I've managed to get a Shape instance above a Bitmap one and enabled erasing it with click and drag events, so the image below becomes visible.
I've used the updateCache() with the compositeOperation approach and it was easy enough, but here is my issue:
How can I find out how much the user has already erased from the Shape instance, so I can setup a callback function when, say, 90% of the image below is visible?
Here is a functioning example of what I'm pursuing: http://codecanyon.net/item/html5-scratch-card/full_screen_preview/8721110?ref=jqueryrain&ref=jqueryrain&clickthrough_id=471288428&redirect_back=true
This is my code so far:
function Lottery(stageId) {
this.Stage_constructor(stageId);
var self = this;
var isDrawing = false;
var x, y;
this.autoClear = true;
this.enableMouseOver();
self.on("stagemousedown", startDrawing);
self.on("stagemouseup", stopDrawing);
self.on("stagemousemove", draw);
var rectWidth = self.canvas.width;
var rectHeight = self.canvas.height;
// Image
var background = new createjs.Bitmap("http://www.taxjusticeblog.org/lottery.jpg");
self.addChild(background);
// Layer above image
var overlay = new createjs.Shape();
overlay.graphics
.f("#55BB55")
.r(0, 0, rectWidth, rectHeight);
self.addChild(overlay);
overlay.cache(0, 0, self.canvas.width, self.canvas.height);
// Cursor
self.brush = new createjs.Shape();
self.brush.graphics
.f("#DD1111")
.dc(0, 0, 5);
self.brush.cache(-10, -10, 25, 25);
self.cursor = "none";
self.addChild(self.brush);
function startDrawing(evt) {
x = evt.stageX-0.001;
y = evt.stageY-0.001;
isDrawing = true;
draw(evt);
};
function stopDrawing() {
isDrawing = false;
};
function draw(evt) {
self.brush.x = self.mouseX;
self.brush.y = self.mouseY;
if (!isDrawing) {
self.update();
return;
}
overlay.graphics.clear();
// Eraser line
overlay.graphics
.ss(15, 1)
.s("rgba(30,30,30,1)")
.mt(x, y)
.lt(evt.stageX, evt.stageY);
overlay.updateCache("destination-out");
x = evt.stageX;
y = evt.stageY;
self.update();
$rootScope.$broadcast("LotteryChangeEvent");
};
}
Any ideas?
That's a tricky one, regardless of the language. The naive solution would simply be to track the length of the paths the user "draws" within the active area, and then reveal when they scratch long enough. That's obviously not very accurate, but is fairly simple and might be good enough.
The more accurate approach would be to get the pixel data of the cacheCanvas, then check the alpha value of each pixel to get an idea of how many pixels are transparent (have low alpha). You could optimize this significantly by only checking every N pixel (ex. every 5th pixel in every 5th row would run 25X faster).

HTML5 Canvas Drawing History

I'm curious to know how applications such as Adobe Photoshop implement their drawing history with the ability to go back or undo strokes on rasterized graphics without having to redraw each stroke from the beginning...
I'm wanting to implement a similar history function on an HTML5 drawing application I'm working on but duplicating the canvas after every stoke seems like it'd use too much memory to be a practical approach, especially on larger canvas'...
Any suggestions on how this might be implemented in a practical and efficient manner?
I may have a solution.....
var ctx = document.getElementById("canvasId").getContext("2d");
var DrawnSaves = new Array();
var Undo = new Array();
var FigureNumber = 0;
var deletingTimer;
function drawLine(startX, startY, destX, destY) {
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(destX, destY);
ctx.stroke();
var Para = new Array();
Para["type"] = "line";
Para["fromX"] = startX;
Para["fromY"] = startY;
Para["toX"] = destX;
Para["toY"] = destY;
DrawnSaves.push(Para);
FigureNumber++;
}
function undo() {
ctx.beginPath();
ctx.clearRect(0, 0, 500, 500);
Undo[FigureNumber] = DrawnSaves[FigureNumber];
DrawnSaves[FigureNumber] = "deleted";
FigureNumber--;
drawEverything();
startTimeoutOfDeleting();
}
function undoTheUndo() {
FigureNumber++;
DrawnSaves[FigureNumber] = Undo[FigureNumber];
drawEverything();
clearTimeout(deletingTimer);
}
function drawEverything() {
for (i = 0; i < DrawnSaves.length; i++) {
if (DrawnSaves[i].type == "line") {
ctx.beginPath();
ctx.moveTo(DrawnSaves[i].fromX, DrawnSaves[i].fromY);
ctx.lineTo(DrawnSaves[i].toX, DrawnSaves[i].toY);
ctx.stroke();
}
}
}
function startTimeoutOfDeleting() {
setTimeout(function() {Undo[FigureNumber] = "deleted";}, 5000);
}
This is really simple, first I draw a line when the function is called and save all his parameters in an array. Then , in the undo function I just start a timer do delete the figure drawn i 2000 miliseconds, clears the whole canvas and makes it can't be redrawn. in the undoTheUndo function, it stops the timer to delete the figure and makes that the figure can be redrawn. In the drawEverything function, it draws everything in the array based on it's type ("line here"). That's it... :-)
Here is an example working : This, after 2sec UNDOs then after 1sec UNDOTHEUNDO

clear Rect doesn't work [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
I would like to add to a drawing program buttons with different functions. But I got problems with the first one, of course. I'm trying to have a button to clear the entire canvas. But somehow it doesn't work.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var cb_canvas = null;
var cb_ctx = null;
var cb_lastPoints = null;
var cb_easing = 0.4;
// Setup event handlers
window.onload = init;
function init(e) {
cb_canvas = document.getElementById("cbook");
cb_lastPoints = Array();
if (cb_canvas.getContext) {
cb_ctx = cb_canvas.getContext('2d');
cb_ctx.lineWidth = 2;
cb_ctx.strokeStyle = "rgb(0, 0, 0)";
cb_ctx.beginPath();
cb_canvas.onmousedown = startDraw;
cb_canvas.onmouseup = stopDraw;
cb_canvas.ontouchstart = startDraw;
cb_canvas.ontouchstop = stopDraw;
cb_canvas.ontouchmove = drawMouse;
}
}
function startDraw(e) {
if (e.touches) {
// Touch event
for (var i = 1; i <= e.touches.length; i++) {
cb_lastPoints[i] = getCoords(e.touches[i - 1]); // Get info for
finger #1
}
}
else {
// Mouse event
cb_lastPoints[0] = getCoords(e);
cb_canvas.onmousemove = drawMouse;
}
return false;
}
// Called whenever cursor position changes after drawing has started
function stopDraw(e) {
e.preventDefault();
cb_canvas.onmousemove = null;
}
function drawMouse(e) {
if (e.touches) {
// Touch Enabled
for (var i = 1; i <= e.touches.length; i++) {
var p = getCoords(e.touches[i - 1]); // Get info for finger i
cb_lastPoints[i] = drawLine(cb_lastPoints[i].x, cb_lastPoints[i].y,
p.x, p.y);
}
}
else {
// Not touch enabled
var p = getCoords(e);
cb_lastPoints[0] = drawLine(cb_lastPoints[0].x, cb_lastPoints[0].y, p.x,
p.y);
}
cb_ctx.stroke();
cb_ctx.closePath();
cb_ctx.beginPath();
return false;
}
// Draw a line on the canvas from (s)tart to (e)nd
function drawLine(sX, sY, eX, eY) {
cb_ctx.moveTo(sX, sY);
cb_ctx.lineTo(eX, eY);
return { x: eX, y: eY };
}
// Get the coordinates for a mouse or touch event
function getCoords(e) {
if (e.offsetX) {
return { x: e.offsetX, y: e.offsetY };
}
else if (e.layerX) {
return { x: e.layerX, y: e.layerY };
}
else {
return { x: e.pageX - cb_canvas.offsetLeft, y: e.pageY -
cb_canvas.offsetTop };
}
}
$("clear").onclick=function(){clearAll()};
function clearAll() {
var canvas = $("#cbook");
var ctx = canvas.get(0).getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
});
</script>
</head>
<body>
<canvas id="cbook" width="500" height="333"></canvas>
<button id="clear">Clean Up</button>
</body>
</html>
You are pretty much right, there are a couple of problems...
$("clear").onclick=function(){clearAll()};
Should probably read
$("#clear").click(clearAll);
You needed to mark the selector as an id and also you were trying to put a native element event on a jQuery object.
The other problem is similar you are using the jQuery object containing a canvas instead of the native element, instead change...
var canvas = $("#cbook");
var ctx = canvas.get(0).getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
To...
var canvas = $("#cbook");
var ctx = canvas.get(0).getContext("2d");
ctx.clearRect(0, 0, canvas.get(0).width, canvas.get(0).height);
Or more simply...
var canvas = $("#cbook").get(0);
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
A fiddle...
http://jsfiddle.net/zZhfE/
Oh and totally forgot, the window.onload handler is unnecessary as you are running it within the document ready function of jQuery everything should be ready to go anyway. I just moved everything out of the function and removed the window.onload handler.
I can see three main issues with your code. First, you're mixing and matching Javascript and jQuery, and it's causing issues (when working with Canvas, try and stick to vanilla Javascript).
You're not getting the context in the clearAll() function because you're trying to use jQuery:
var canvas = $("#cbook");
When you should be using plain ol' JS, like in your drawing function:
var canvas = document.getElementById("cbook");
Secondly, you're calling the clear function like this:
$("clear").onclick=function(){clearAll()};
When it should be this:
$("#clear").on('click', function(){clearAll()});
(note the # before clear - you need to use this to refer to the ID of an element.)
Finally, you're looking for the first instance of a canvas inside a canvas, which won't work:
var ctx = canvas.get(0).getContext("2d");
All you need is:
var ctx = canvas.getContext("2d");
Here's a working JSFiddle:
http://jsfiddle.net/hGjDR/

Categories