Restructuring for an Art app
I’ve been working on a Pixel Art web app lately, which you can read more about here. This post is going to be more of a journal entry than a tutorial, just to track my progress. I’ve been restructuring the code to keep it scalable, so it doesn’t get overcomplicated as I add tools. It’s not complete, but I’ve refactored the variables into a controller object, which will eventually become the state, similar to how React works. I factored the functions out of the mouse events, so that I can move away from using huge switch statements and more easily add keyboard shortcuts in the future.
Now mouse events look like this:
function handleMouseDown(e) {
state.event = "mousedown";
state.clicked = true;
state.trueRatio = onScreenCVS.offsetWidth / offScreenCVS.width;
state.mouseX = Math.floor(e.offsetX / state.trueRatio);
state.mouseY = Math.floor(e.offsetY / state.trueRatio);
//run selected tool step function
state.tool.fn();
}
state.tool.fn
is the function associated with the selected tool. When a tool button is pressed to select it, a tool is chosen from a library of all the tools, which is an object that stores information about that tool:
const tools = {
pencil: {
name: "pencil",
fn: drawSteps,
brushSize: 1,
options: ["perfect"]
},
replace: {
name: "replace",
fn: replaceSteps,
brushSize: 1,
options: ["perfect"]
},
line: {
name: "line",
fn: lineSteps,
brushSize: 1,
options: []
},
fill: {
name: "fill",
fn: fillSteps,
brushSize: 1,
options: []
}
};
The tool select uses the id to grab the right tool:
function handleTools(e) {
if (e.target.closest(".tool")) {
//reset old button
toolBtn.style.background = "rgb(131, 131, 131)";
//get new button and select it
toolBtn = e.target.closest(".tool");
toolBtn.style.background = "rgb(185, 28, 0)";
state.tool = tools[toolBtn.id];
}
};
The “steps” functions are controller functions that switch the code based on which event is calling them:
function lineSteps() {
switch (state.event) {
case "mousedown":
state.lastX = state.mouseX;
state.lastY = state.mouseY;
break;
case "mousemove":
//draw line from origin point to current point onscreen
//only draw when necessary
if (state.onX !== state.lastOnX || state.onY !== state.lastOnY) {
onScreenCTX.clearRect(0, 0, ocWidth, ocHeight);
drawCanvas();
actionLine(
state.lastX,
state.lastY,
state.mouseX,
state.mouseY,
state.brushColor,
onScreenCTX,
state.mode,
state.ratio
);
state.lastOnX = state.onX;
state.lastOnY = state.onY;
}
break;
case "mouseup":
actionLine(
state.lastX,
state.lastY,
state.mouseX,
state.mouseY,
state.brushColor,
offScreenCTX,
state.mode
);
addToTimeline(state.tool.name, state.mouseX, state.mouseY);
break;
default:
//do nothing
}
}