I'm making a very simple 'pixel-painting' program using HTML5/Canvas. I'd like to give the user an option to go back in 'history', like the History panel in Photoshop / Adobe programs.
Basically it would be an undo button, but you'd be able to go back to the start of your actions, and there would also be a log showing the details of each action.
Is this possible? How would I even start storing this data?
How much memory is available within the Chrome browser in order to allow this on one page? – (Sorry if that is silly to ask, still quite new to Javascript and working within the browser.)
I have read this undo button Question, which is similar but I'd like to make the info about data being stored visible.
Thank you so so much for any help you can give!
You would need to build a simple undo-redo stack. Then you need to decide if you will store vector data or image data. The latter is more efficient but can also take up much more memory. You may have cases where you want to store both types of data (path on top of images).
The method would be in simple steps:
Note: when creating a undo state it's important to clear any snapshots after the new stack pointer position. This is because if undo has been used, redo can be used if no changes. However, if undo was used and new drawing was added this would invalidate the next states so they have to be removed.
As to browser memory it will depend on the user's system. Some have a few gigabytes, other has a lot. There is no way to know. You would have to chose a UX strategy suitable for your scenario as well as target audience.
This does not implement the logistics for handling the sync of the thumbnails, but has most other parts. I'll leave the rest as an exercise.
var ctx = c.getContext("2d"),
stack = [], // undo-redo stack
sp = 0, // stack pointer
isDown = false; // for drawing (demo)
capture(); // create an initial undo capture (blank)
ctx.lineCap = "round"; // setup line for demo
ctx.lineWidth = 4;
// simple draw mechanism
c.onmousedown = function(e) {
sp++; // on mouse down, move stack pointer to next slot
isDown = true; // NOTE: clear any snapshots after this point (not shown)
var pos = getXY(e); // start drawing some line - how you draw is up to you
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
}
window.onmousemove = function(e) {
if (!isDown) return; // only for drawing
var pos = getXY(e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
}
window.onmouseup = function() {
if (!isDown) return;
isDown = false;
capture(); // capture an undo state
makeThumb(); // create and insert a thumbnail of state
};
function capture() {
stack[sp] = c.toDataURL(); // one way, you could use getImageData,
// or store points instead.. it's up to you
}
// Creates a thumbnail of current canvas and insert into visible undo stack
function makeThumb() {
var canvas = document.createElement("canvas");
canvas.width = canvas.height = 64;
var ctxTmp = canvas.getContext("2d");
ctxTmp.drawImage(c, 0, 0, canvas.width, canvas.height);
undos.appendChild(canvas);
}
// UNDO button clicked
undo.onclick = function() {
var img = new Image; // restore previous state/snapshot
img.onload = function() {
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(this, 0, 0);
}
// move stack pointer back and get previous snapshot
if (sp > 0) img.src = stack[--sp];
};
// REDO button clicked
redo.onclick = function() {
// anything we can redo?
if (sp < stack.length) {
var img = new Image;
img.onload = function() {
ctx.clearRect(0, 0, c.width, c.height);
ctx.drawImage(this, 0, 0);
}
// move stack pointer forward and get next snapshot
img.src = stack[++sp];
}
};
function getXY(e) {
var r = c.getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top}
}
#c {background:#ccc}
<button id=undo>Undo</button>
<button id=redo>Redo</button><br>
<canvas id=c width=500 height=500></canvas>
<div id=undos></div>