使用Canvas制作画板

使用Canvas制作画板

在本篇技术博客中,我们将使用JavaScript和Canvas技术来创建一个简单的画板应用程序。这个画板将允许用户在一个画布上绘制线条,使用橡皮擦擦除绘制的内容,更改线条的颜色和宽度,并支持撤销和重做功能。

准备工作

在开始之前,我们需要一个HTML文件来设置画板的基本结构。请创建一个index.html文件,并将以下内容复制到其中:

html 复制代码
<!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>

接下来,我们需要一个app.js文件,用于编写JavaScript代码。请在项目根目录下创建一个app.js文件,并将你提供的代码复制到其中。

实现功能

现在,我们将详细解释一下代码中的每个部分,并说明它们如何协同工作来创建一个完整的画板应用。

画布初始化

javascript 复制代码
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;

以上代码片段获取了HTML元素和Canvas上下文对象,并设置了画布的宽度和高度等于视口的宽度和高度,以确保画布可以铺满整个屏幕。

状态和常量定义

javascript 复制代码
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'
}

这部分代码定义了画板应用的状态和常量。state对象用于跟踪用户的操作和绘制数据。DATA_FIELDDATA_TYPE用于标识绘制数据的不同部分。CANVAS_VALUES定义了画布的一些默认值,包括默认颜色、线条样式和线条宽度。KEYBOARD定义了用于撤销和重做的键盘按键。

初始化和事件绑定

javascript 复制代码
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);
}

这部分代码定义了初始化和事件绑定函数。init函数调用了initStylebindEvent函数来初始化画布样式并绑定事件监听器。

initStyle函数设置了画布的默认颜色、线条样式和线条宽度。

bindEvent函数绑定了鼠标和键盘事件的处理函数,这些事件包括鼠标点击、颜色选择、线条宽度选择、清空画板、使用橡皮擦和撤销/重做操作。

绘制函数

javascript 复制代码
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);
}

以上代码片段包含了绘制相关的函数。drawPoint用于绘制一个点,drawLine用于绘制直线,drawBatchLine用于批量绘制用户的绘制数据,clearAll用于清空画布。

事件处理函数

javascript 复制代码
// 省略其他事件处理函数...

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) {
  // 省略其他事件处理代码...
}

以上代码片段包含了处理键盘事件和鼠标事件的函数。handleKeyDown函数用于处理键盘事件,当用户按下Ctrl键(或Cmd键在macOS上)加上Z或B键时,会触发撤销和重做操作。handleCanvasMouseDown函数用于处理鼠标按下事件,开始绘制操作。

辅助函数

javascript 复制代码
// 省略其他辅助函数...

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;
};

这些函数是对代码中省略的部分进行了完善,主要用于设置橡皮擦元素的显示与隐藏,以及对画笔颜色、线条样式和线条宽度的设置与获取。这些函数在画板应用中起着重要的作用,使用户能够自由选择画笔颜色、线条样式和线条宽度,同时在使用橡皮擦功能时,橡皮擦元素能够正确显示在鼠标位置。

运行画板应用

将上述代码保存为app.js文件,并创建一个HTML文件将其引入。然后在浏览器中打开HTML文件,你就可以在画板上开始绘制、擦除、更改颜色和宽度,以及进行撤销和重做操作了。

学习自B站up------前端小野森森

相关推荐
悦涵仙子1 小时前
CSS中的变量应用——:root,Sass变量,JavaScript中使用Sass变量
javascript·css·sass
兔老大的胡萝卜1 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
一点媛艺2 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风2 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生3 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功3 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨3 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程3 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk4 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*4 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go