Use Canvas to make artboards
In this tech blog, we'll use JavaScript and Canvas technology to create a simple canvas application. This artboard will allow the user to draw lines on a canvas, use the eraser to erase the drawn content, change the color and width of the line, and support undo and redo functions.
Preparation
Before we start, we need an HTML file to set up the basic structure of the artboard. Please create a index.html
file and copy the following content into it:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas 画板</title>
</head>
<body>
<canvas id="can"></canvas>
<input type="color" id="colorInput">
<input type="range" id="lineWidthRange" min="1" max="20" step="1">
<span id="lineWidthValue">1</span>
<button id="clearAllBtn">清空画板</button>
<button id="eraserBtn">使用橡皮擦</button>
<input type="range" id="eraserLineWidthRange" min="1" max="20" step="1">
<span id="eraserLineWidthValue">1</span>
<div id="eraserCircle"></div>
<script src="app.js"></script>
</body>
</html>
Next, we need a app.js
file for writing JavaScript code. Please create a file at the root of your project app.js
and copy the code you provided into it.
Realize function
Now, we'll explain each part of the code in detail and show how they work together to create a complete Artboards app.
Canvas initialization
const oCan = document.getElementById('can');
const ctx = oCan.getContext('2d');
const oColorInput = document.getElementById('colorInput');
const oLineWidthRange = document.getElementById('lineWidthRange');
const oLineWidthValue = document.getElementById('lineWidthValue');
const oClearAllBtn = document.getElementById('clearAllBtn');
const oEraserBtn = document.getElementById('eraserBtn');
const oEraserLineWidthRange = document.getElementById('eraserLineWidthRange');
const oEraserLineWidthValue = document.getElementById('eraserLineWidthValue');
const oEraserCircle = document.getElementById('eraserCircle');
const clientWidth = document.documentElement.clientWidth;
const clientHeight = document.documentElement.clientHeight;
oCan.width = clientWidth;
oCan.height = clientHeight;
The above code snippet gets the HTML element and the Canvas context object, and sets the width and height of the canvas equal to the width and height of the viewport to ensure that the canvas can fill the entire screen.
State and Constant Definitions
const state = {
initPos: null,
eraserStatus: false,
drewData: [],
revokedData: []
}
const DATA_FIELD = {
DREW: 'drewData',
REVOKED: 'revokedData'
}
const DATA_TYPE = {
MOVE_TO: 'moveTo',
LINE_TO: 'lineTo'
}
const CANVAS_VALUES = {
DEFAULT_COLOR: '#000',
DEFAULT_LINE_STYLE: 'round',
DEFAULT_LINE_WIDTH: 1,
ERASER_COLOR: '#fff'
}
const KEYBOARD = {
UNDO: 'z',
REDO: 'b'
}
This part of the code defines the state and constants of the artboard application. state
Objects are used to track user actions and draw data. DATA_FIELD
and DATA_TYPE
are used to identify the different parts of the plotted data. CANVAS_VALUES
Defines some default values for the canvas, including default color, line style, and line width. KEYBOARD
Defines keyboard keys for undo and redo.
Initialization and event binding
const init = () => {
initStyle();
bindEvent();
}
function initStyle() {
ctx.setColor(CANVAS_VALUES.DEFAULT_COLOR);
ctx.setLineStyle(CANVAS_VALUES.DEFAULT_LINE_STYLE);
ctx.setLineWidth(CANVAS_VALUES.DEFAULT_LINE_WIDTH);
}
function bindEvent() {
oCan.addEventListener('mousedown', handleCanvasMouseDown, false);
oColorInput.addEventListener('click', handleColorInput, false);
oColorInput.addEventListener('input', handleColorInput, false);
oLineWidthRange.addEventListener('input', handleLineWidthRangeInput, false);
oClearAllBtn.addEventListener('click', handleClearAllBtnClick, false);
oEraserBtn.addEventListener('click', handleEraserBtnClick, false);
oEraserLineWidthRange.addEventListener('input', handleEraserLineWidthRangeInput, false);
document.addEventListener('keydown', handleKeyDown, false);
}
This part of the code defines initialization and event binding functions. init
The function calls initStyle
and bindEvent
functions to initialize the canvas styles and bind event listeners.
initStyle
The function sets the default color, line style and line width of the canvas.
bindEvent
The function binds handlers for mouse and keyboard events, including mouse click, color selection, line width selection, clearing the artboard, using the eraser, and undo/redo operations.
draw function
function drawPoint(x, y) {
ctx.beginPath();
ctx.arc(x, y, ctx.lineWidth / 2, 0, 2 * Math.PI, false);
ctx.fill();
}
function drawLine({ x1, y1, x2, y2 }) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
function drawBatchLine() {
clearAll();
state[DATA_FIELD.DREW].forEach(item => {
ctx.beginPath();
const { moveTo: [x1, y1], lineTo, info: { color, width } } = item;
ctx.setColor(color);
ctx.setLineWidth(width);
ctx.moveTo(x1, y1);
lineTo.forEach(line => {
ctx.lineTo(...line);
});
ctx.stroke();
})
}
function clearAll() {
ctx.clearRect(0, 0, oCan.offsetWidth, oCan.offsetHeight);
}
The above code snippet contains functions related to drawing. drawPoint
It is used to draw a point, drawLine
to draw a line, drawBatchLine
to draw user's drawing data in batches, and clearAll
to clear the canvas.
event handler
// 省略其他事件处理函数...
function handleKeyDown(e) {
const key = e.key;
console.log(key);
if ((e.metaKey || e.ctrlKey) && (Object.values(KEYBOARD).includes(key))) {
doDrewRecord(key);
drawBatchLine();
}
if (!state[DATA_FIELD.DREW].length || !state[DATA_FIELD.REVOKED].length) {
ctx.setColor(oColorInput.value);
ctx.setLineWidth(oLineWidthRange.value);
}
}
function handleCanvasMouseDown(e) {
// 省略其他事件处理代码...
}
The above code snippet contains functions to handle keyboard events and mouse events. handleKeyDown
The function is used to handle keyboard events, when the user presses the Ctrl key (or Cmd key on macOS) plus the Z or B key, it will trigger undo and redo operations. handleCanvasMouseDown
The function is used to handle the mouse press event and start the drawing operation.
helper function
// 省略其他辅助函数...
function setDrewRecord(type, data) {
switch (type) {
case DATA_TYPE.MOVE_TO:
state[DATA_FIELD.DREW].push({
[DATA_TYPE.MOVE_TO]: [...data],
[DATA_TYPE.LINE_TO]: [],
info: {
color: ctx.getColor(),
width: ctx.getLineWidth()
}
})
break;
case DATA_TYPE.LINE_TO:
const drewData = state[DATA_FIELD.DREW];
drewData[drewData.length - 1][DATA_TYPE.LINE_TO].push([...data]);
break;
default:
break;
}
}
function doDrewRecord(key) {
switch (key) {
case KEYBOARD.UNDO:
state[DATA_FIELD.DREW].length > 0
&&
state[DATA_FIELD.REVOKED].push(state[DATA_FIELD.DREW].pop());
break;
case KEYBOARD.REDO:
state[DATA_FIELD.REVOKED].length > 0
&&
state[DATA_FIELD.DREW].push(state[DATA_FIELD.REVOKED].pop());
break;
default:
break;
}
}
// 定义 EraserCircle 元素的显示与隐藏
oEraserCircle.setVisible = function (visible) {
this.style.display = visible ? 'block' : 'none';
};
// 定义 EraserCircle 元素的大小设置
oEraserCircle.setSize = function (size) {
this.style.width = size + 'px';
this.style.height = size + 'px';
};
// 定义 EraserCircle 元素的位置设置
oEraserCircle.setPosition = function (x, y) {
this.style.left = x - this.offsetWidth / 2 + 'px';
this.style.top = y - this.offsetHeight / 2 + 'px';
};
// 设置画笔颜色
ctx.setColor = function (color) {
this.strokeStyle = color;
this.fillStyle = color;
};
// 获取画笔颜色
ctx.getColor = function () {
return this.strokeStyle;
};
// 设置线条样式(线帽和连接点)
ctx.setLineStyle = function (style) {
this.lineCap = style;
this.lineJoin = style;
};
// 设置线条宽度
ctx.setLineWidth = function (width) {
this.lineWidth = width;
};
// 获取线条宽度
ctx.getLineWidth = function () {
return this.lineWidth;
};
These functions perfect the parts omitted in the code, and are mainly used to set the display and hide of the eraser element, as well as the setting and acquisition of the brush color, line style and line width. These functions play an important role in the sketchpad application, enabling users to freely choose the brush color, line style and line width, and when using the eraser function, the eraser element can be displayed correctly at the mouse position.
Run the Sketchpad app
Save the above code as app.js
a file and create an HTML file to import it. Then open the HTML file in your browser and you can start drawing on the artboard, erasing, changing color and width, and undoing and redoing.
Learning from station B up - front-end Ono Sensen