使用Canvas制作画板

news2024/12/28 19:57:14

使用Canvas制作画板

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

准备工作

在开始之前,我们需要一个HTML文件来设置画板的基本结构。请创建一个index.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文件,并将你提供的代码复制到其中。

实现功能

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

画布初始化

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上下文对象,并设置了画布的宽度和高度等于视口的宽度和高度,以确保画布可以铺满整个屏幕。

状态和常量定义

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定义了用于撤销和重做的键盘按键。

初始化和事件绑定

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

绘制函数

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用于清空画布。

事件处理函数

// 省略其他事件处理函数...

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函数用于处理鼠标按下事件,开始绘制操作。

辅助函数

// 省略其他辅助函数...

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——前端小野森森

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/817134.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Html5播放器按钮在移动端变小的问题解决方法

Html5播放器按钮在移动端变小的问题解决方法 用手机浏览器打开酷播云视频&#xff0c;有时会出现播放器按钮太小的情况&#xff0c;此时只需在<head>中加入下面这段代码即可解决&#xff1a; <meta name"viewport" content"widthdevice-width, initia…

矩阵中的路径(JS)

矩阵中的路径 题目 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”单元格是…

CopyTranslator-复制即翻译 文献翻译神器 支持多接口翻译

一、软件起源 科研人员总少不了阅读大量文献&#xff0c;理解文献内容就成了科研生活常态&#xff0c;而我们平时复制PDF内容黏贴到网页翻译的时候可能会出现多余换行而导致翻译乱码&#xff0c;译文与中文阅读习惯不符的情况&#xff0c;翻译结果很差&#xff0c;需要手动删除…

2.4 传统经验光照模型详解

一、光照模型 光照模型&#xff08;illumination model&#xff09;&#xff0c;也称为明暗模型&#xff0c;用于计算物体某点处的光强&#xff08;颜色值&#xff09;。从算法理论基础而言&#xff0c;光照模型分为两类&#xff1a;一种是基于物理理论的&#xff0c;另一种是…

《深入浅出Java虚拟机》AIC松鼠活动第五期

1、JAVA虚拟机 1.1什么是java虚拟机 Java虚拟机&#xff08;Java Virtual Machine&#xff0c;JVM&#xff09;是一种用于执行Java字节码的虚拟机。它可以将Java源代码编译为字节码&#xff0c;然后在不同的操作系统和硬件平台上运行。作为Java语言的核心组成部分&#xff0…

MySQL和Oracle区别

由于SQL Server不常用&#xff0c;所以这里只针对MySQL数据库和Oracle数据库的区别 (1) 对事务的提交 MySQL默认是自动提交&#xff0c;而Oracle默认不自动提交&#xff0c;需要用户手动提交&#xff0c;需要在写commit;指令或者点击commit按钮 (2) 分页查询 MySQL是直接在SQL…

挖了个漏洞,挣了¥12000!

今天给大家分享一个挖漏洞致富的事情。 0x01 前言 本人&#xff0c;一个热爱生活&#xff0c;热爱网络安全的小青年。在此记录自己日常学习以及漏洞挖掘中的奇思妙想&#xff0c;希望能与热爱网络安全的人士一起交流共勉。 0x02 漏洞背景 一个app&#xff0c;开局一个登录框…

模拟面试题

面试题一 C# 1. 装箱和拆箱是什么&#xff1f; 装箱是把栈空间的数据转移到堆空间上去&#xff0c;值类型传引用类型上去 int i 2; object o i; 拆箱是把堆空间的数据转移到栈空间上去&#xff0c;引用类型传到值类型上去 i (int) o 2. 值和引用类型在变量赋值时的区别是…

在OK3588板卡上部署模型实现人工智能OCR应用

一、主机模型转换 我们依旧采用FastDeploy来部署应用深度学习模型到OK3588板卡上 进入主机Ubuntu的虚拟环境 conda activate ok3588 安装rknn-toolkit2&#xff08;该工具不能在OK3588板卡上完成模型转换&#xff09; git clone https://github.com/rockchip-linux/rknn-to…

气象名词解释

文章目录 SAMPSAAMO SAM SAM(Southern Annualr Mode) 南半球环状模&#xff0c;是南半球大气环流和气候变异的一种重要现象。具有如下特点&#xff1a; 主要特点&#xff1a; 赤道附近环流&#xff1a;在 SAM 正相位期间&#xff0c;赤道附近的环流增强&#xff0c;称为正 SA…

Java源码规则引擎:jvs-rules决策流的自定义权限控制

规则引擎用于管理和执行业务规则。它提供了一个中央化的机制来定义、管理和执行业务规则&#xff0c;以便根据特定条件自动化决策和行为。规则引擎的核心概念是规则。规则由条件和动作组成。条件定义了规则适用的特定情况或规则触发的条件&#xff0c;而动作定义了规则满足时要…

IO进程线程day3(2023.7.31)

一、Xmind整理&#xff1a; 文件描述符概念&#xff1a; 二、课上练习&#xff1a; 练习1&#xff1a;用fread和fwrite实现文件拷贝 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <head.h> int main(int argc, const char…

12-3_Qt 5.9 C++开发指南_创建和使用静态链接库

第12章中的静态链接库和动态链接库介绍&#xff0c;都是以UI操作的方式进行&#xff0c;真正在实践中&#xff0c;可以参考UI操作产生的代码来实现同样的功能。 文章目录 1. 创建静态链接库1.1 创建静态链接库过程1.2 静态链接库代码1.2.1 静态链接库可视化UI设计框架1.2.2 qw…

八大排序算法--快速排序(动图理解)

快速排序 概念 快速排序是对冒泡排序的一种改进。其基本原理是通过选取一个基准元素&#xff0c;将数组划分为两个子数组&#xff0c;分别对子数组进行排序&#xff0c;最终实现整个数组的有序排列。快速排序的时间复杂度最好为O(nlogn)&#xff0c;最坏为O(n^2)&#xff0c;…

5个值得收藏的AI绘画网站,还有国产!

随着科技的发展&#xff0c;设计领域也迎来了科技创新&#xff0c;AI绘画网站便是其中的一个代表&#xff0c;本文精选了4个好用的AI绘画网站与大家分享&#xff0c;一起来看看吧&#xff01; 1、即时灵感 作为一个国产的AI绘画网站&#xff0c;即时灵感支持设计师使用中文语…

Spring | Bean 作用域和生命周期

一、通过一个案例来看 Bean 作用域的问题 Spring 是用来读取和存储 Bean&#xff0c;因此在 Spring 中 Bean 是最核心的操作资源&#xff0c;所以接下来我们深入学习⼀下 Bean 对象 假设现在有⼀个公共的 Bean&#xff0c;提供给 A 用户和 B 用户使用&#xff0c;然而在使用的…

IO流简述

IO流IO流使用场景 什么是IO流常用的IO流字节流字符流缓冲流 BIO、NIO、AIO的区别 IO流 IO流使用场景 如果操作的是纯文本文件&#xff0c;优先使用字符流如果操作的是图片、视频、音频等二进制文件。优先使用字节流如果不确定文件类型&#xff0c;优先使用字节流。字节流是万能…

【LLM系列之指令微调】长话短说大模型指令微调的“Prompt”

1 指令微调数据集形式“花样”太多 大家有没有分析过 prompt对模型训练或者推理的影响&#xff1f;之前推理的时候&#xff0c;发现不加训练的时候prompt&#xff0c;直接输入模型性能会变差的&#xff0c;这个倒是可以理解。假如不加prompt直接训练&#xff0c;是不是测试的时…

linux网络编程--select多路IO转接模型

目录 1.TCP状态转换图 2.端口复用 3.半关闭状态 4.心跳包 5.高并发服务器模型--select 6.提纲总结 学习目录 熟练掌握TCP状态转换图熟练掌握端口复用的方法了解半关闭的概念和实现方式了解多路IO转接模型熟练掌握select函数的使用熟练使用fd_set相关函数的使用能够编写s…

winform学习(3)-----Windows窗体应用和Windows窗体应用(.Net Framework)有啥区别?

1.模板选择 在学习winform的时候总是会对这两个应用不知道选择哪个&#xff1f;而且在学习的时候也没有具体的说明 首先说一下我是在添加控件的时候出现了以下问题 对于使用了Windows窗体应用这个模板的文件在工具箱中死活不见控件。 在转换使用了Windows窗体应用(.NET Fram…