基于Vue+Canvas实现的画板绘画以及保存功能,解决保存没有背景问题

基于Vue+Canvas实现的画板绘画以及保存功能

本文内容设计到的画板的js部分内容来源于灵感来源引用地址,然后我在此基础上,根据自己的需求做了修改,增加了其他功能。
在这里插入图片描述

下面展示了完整的前后端代码

这里写目录标题

  • 基于Vue+Canvas实现的画板绘画以及保存功能
    • 1. board-js.js
    • 2. 前端的vue文件
    • 3. 后端Controller

1. board-js.js

这个代码,接收一个容器参数,创建了一个画板类,里面实现了画板会用到的基本方法,保存到单独的js文件,在vue文件中导入,创建一个画板对象。
Canvas直接使用canvas.toDataURL()保存的图片是没有背景的,因为默认的是png,所以需要开始绘画之前先填充背景,下面代码做出了修改,在init()函数中。
代码中用到的Canvas的原生API的作用,可以参考这里Canvas参考手册

export default class BoardCanvas {
    constructor(container) {
        // 容器
        this.container = container
        // canvas画布
        this.canvas = this.createCanvas(container)
        // 绘制工具
        this.ctx = this.canvas.getContext('2d')
        // 起始点位置
        this.startX = 0
        this.stateY = 0
        // 画布历史栈
        this.pathSegmentHistory = []
        this.index = 0

        // 初始化
        this.init()
    }

    // 创建画布
    createCanvas(container) {
        const canvas = document.createElement('canvas')
        canvas.width = container.clientWidth
        canvas.height = container.clientHeight
        canvas.style.display = 'block'
        canvas.style.backgroundColor = 'white'
        container.appendChild(canvas)
        return canvas
    }

    // 初始化
    init() {
        this.addPathSegment()
        this.setContext2DStyle()
        //下面两行,原文件是没有的,如果没有会导致保存的图片没有背景,只有绘画轨迹
        this.ctx.fillStyle = "#ffffff";
        this.ctx.fillRect(0, 0,this.canvas.width, this.canvas.height);
        
        this.canvas.addEventListener('contextmenu', e => e.preventDefault())
        this.canvas.addEventListener('mousedown', this.mousedownEvent.bind(this))
        window.document.addEventListener('keydown', this.keydownEvent.bind(this))
    }

    // 设置画笔样式
    setContext2DStyle() {
        this.ctx.strokeStyle = 'black'
        this.ctx.lineWidth = 3
        this.ctx.lineCap = 'round'
        this.ctx.lineJoin = 'round'
    }

    // 鼠标事件
    mousedownEvent(e) {
        const that = this
        const ctx = this.ctx
        ctx.beginPath()
        ctx.moveTo(e.offsetX, e.offsetY)
        ctx.stroke()

        this.canvas.onmousemove = function (e) {
            ctx.lineTo(e.offsetX, e.offsetY)
            ctx.stroke()
        }
        this.canvas.onmouseup = this.canvas.onmouseout = function () {
            that.addPathSegment()
            this.onmousemove = null
            this.onmouseup = null
            this.onmouseout = null
        }
    }

    // 键盘事件
    keydownEvent(e) {
        if(!e.ctrlKey) return
        switch(e.keyCode) {
            case 90:
                this.undo()
                break
            case 89:
                this.redo()
                break
        }
    }

    // 添加路径片段
    addPathSegment() {
        const data = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height)
        // 删除当前索引后的路径片段,然后追加一个新的路径片段,更新索引
        this.pathSegmentHistory.splice(this.index + 1)
        this.pathSegmentHistory.push(data)
        this.index = this.pathSegmentHistory.length - 1
    }

    // 撤销
    undo() {
        if(this.index <= 0) return
        this.index--
        this.ctx.putImageData(this.pathSegmentHistory[this.index], 0, 0)
    }
    // 恢复
    redo() {
        if(this.index >= this.pathSegmentHistory.length - 1) return
        this.index++
        this.ctx.putImageData(this.pathSegmentHistory[this.index], 0, 0)
    }
	//获取画布内容
    getImage() {
        return this.canvas.toDataURL();
    }
    //清空画板
    cleanboard(){
        this.ctx.fillStyle = "#ffffff";
        this.ctx.fillRect(0, 0,this.canvas.width, this.canvas.height);
    }
}

2. 前端的vue文件

<template>
  <el-container direction="vertical" style="height: 100%;width: 100%">
    <!--头顶布局,用户操作提示语-->
    <el-header height="10%">
      <h2>在空白处进行绘画</h2>
    </el-header>
    <!--中间布局 -->
    <div style=display:flex;justify-content:center;align-items:center;>
      <!--中间画板-->
      <div class="drawing-board"
           style="width:66%;height:600px;border: 1px black solid;margin-left: 10px">
        <div id="container" ref="container" style="width: 100%; height: 100%"></div>
      </div>
    </div>
    <!--底部按钮-->
    <el-footer style="height: 300px;margin-top: 25px">
      <el-button type="primary" round @click="savedrawing">保存</el-button>
    </el-footer>
  </el-container>
</template>

//这里使用的vue的setup语法糖,所以data和method不需要封装,直接用
<script setup>
import { ref, onMounted } from 'vue'
import Board from '@/js/drawing-board.js'
import axios from "axios";

const container = ref(null)
let drawboard = null;
onMounted(() => {
  // 新建一个画板
  drawboard=new Board(container.value)
})
function savedrawing() {
  const drawdata = drawboard.getImage();				
  let formdata = new FormData();
  const timestamp = (new Date()).valueOf();
  let filename = timestamp;
  formdata.append("drawPictureId",filename);
  formdata.append("drawPictureData",drawdata.substring(22));
  axios({
    method:"post",
    url:"/savedrawdata",
    baseURL:"http://localhost:9999",
    data:formdata,
    contentType:false,
    processData:false
  }).then(response=>{
    if(response.status===200){
      alert("保存成功!")
    }
  }).catch(error=>{
    console.log(error);
  })
}
</script>

3. 后端Controller

Controller文件中,前端使用FormData格式传递参数,就相当于是个map键值对,所以在参数这里,使用 @RequestParam() ,取出表单中的值,括号中的字符串,是在前端传入的,表示将该key对应的value,赋值给后面的String 参数。
其次,这里注意Canvas得到的图片是经过base64编码过的,所以先解码成字节数组

    @RequestMapping("/savedrawdata")
    public ResponseEntity<?> savedrawdata(@RequestParam("drawPictureId")String drawPictureId,
                                          @RequestParam("drawPictureData")String drawPictureData){
        try {
            // 解码前端传过来的base64编码
            byte[] imageBytes = Base64.decodeBase64(drawPictureData);

            // 将字节流转为图片缓冲流
            BufferedImage bufferedImage = ImageIO.read(new ByteArrayInputStream(imageBytes));

            // 保存为png
            File output = new File("F:\\image\\" + drawPictureId + ".png");
            ImageIO.write(bufferedImage, "png", output);

        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(rawPictureId);
        System.out.println(drawPictureId);
        return ResponseEntity.ok(HttpStatus.OK);
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OpenAI GPT应用商城正式上线!超300万个GPT应用供选择

原创 | 文 BFT机器人 千呼万唤始出来&#xff0c;终于在北京时间1月11日凌晨&#xff0c;OpenAI在官网发布了令人振奋的消息&#xff1a;备受瞩目的GPT store正式上线&#xff01; 这个商店旨在让团体和企业用户轻松找到那些既实用又热门的GPT应用。在这里&#xff0c;用户可以…

python基础知识

python基础语法 python基础精讲 http://t.csdnimg.cn/HdKdi 本专栏主要针对python基础语法&#xff0c;帮助学习者快速接触并掌握python大部分最重要的语法特征。 1、基本数据类型和变量 2、分支结构与循环结构 3、函数与异常处理 4、类与模块 5、文件读写 通过本专栏可以快…

Unity 编辑器篇|(十)Handles (全面总结 | 建议收藏)

目录 1. 前言2 参数总览3 Handles两种使用方式3.1 基于Editor类的OnSceneGUI3.2 基于EditorWindow 4 Handles绘制4.1 Draw&#xff1a;绘制元几何体(点、线、面)4.1.1 抗锯齿&#xff1a; DrawAAPolyLine 、 DrawAAConvexPolygon4.1.2 绘制实线: DrawLine 、 DrawLines 、DrawP…

(2)(2.1) Andruav Android Cellular(一)

文章目录 前言 1 Andruav 是什么&#xff1f; 2 Andruav入门 3 Andruav FPV 4 Andruav GCS App​​​​​​​ 前言 Andruav 是一个基于安卓的互联系统&#xff0c;它将安卓手机作为公司计算机&#xff0c;为你的无人机和遥控车增添先进功能。 1 Andruav 是什么&#xff…

门禁监控如何提升安全系数?这个技术,学习一下!

随着社会的不断发展和科技的快速进步&#xff0c;安全管理成为各个领域至关重要的议题。在这一背景下&#xff0c;门禁监控系统逐渐崭露头角&#xff0c;成为保障建筑物和场所安全的关键工具。 门禁监控系统不仅在提高安全性方面发挥着积极作用&#xff0c;而且通过智能化的技术…

《模拟龙生》|500行Go代码写一个随机冒险游戏|巨龙修为挑战开启

一、前言 新年就要到了&#xff0c;祝大家新的一年&#xff1a;&#x1f432; 龙行龘龘&#xff0c;&#x1f525; 前程朤朤&#xff01; 白泽花了点时间&#xff0c;用 500行 Go 代码写了一个控制台的小游戏&#xff1a;《模拟龙生》&#xff0c;在游戏中你将模拟一条新生的…

Linux系统三剑客之grep和正则表达式的介绍(一)

1.正则表达式 目录 1.正则表达式 1.什么是正则表达式 &#xff1f; 2.正则表达式的使用场景 3.正则表达式字符表示 4.它们之间的区别 2.grep命令 作用&#xff1a; 语法&#xff1a; 说明&#xff1a; 选项&#xff1a;options 重点 实例 3.后面的下次再更新。 …

C语言位域定义与使用

参考文章&#xff1a; 【C语言】详解位域定义与使用_c 语言定义位-CSDN博客 代码有修改&#xff0c;主要是变量初始化&#xff0c;原程序可能相应内存不能写。且第二个字节F不好区分各位。 #include <stdio.h>typedef struct {unsigned short b1 : 1;unsigned short b…

情人节专属--HTML制作情人节告白爱心

💕效果展示 💕html展示 <!DOCTYPE html> <html lang="en" > <head>

ros2仿真学习04 -turtlebot3实现cartographer算法建图演示

安装看这里 https://blog.csdn.net/hai411741962/article/details/135619608?spm1001.2014.3001.5502 虚拟机配置&#xff1a; 内存16g cpu 4 核 磁盘40G,20G 不够 启动仿真 ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py启动成功如下 启动建图 重新开一个…

浅谈情绪的分类合集

什么是情绪分类 情绪分类&#xff0c;是指区分或者对比一种情绪与另一种情绪的方法&#xff0c;目前在情绪研究&#xff08;emotion research&#xff09;与情感科学&#xff08;affective science&#xff09;是具有争议的问题。有两个讨论情绪分类的基本观点&#xff1a; 情…

【计算机组成与体系结构Ⅱ】Tomasulo 算法模拟和分析(实验)

实验5&#xff1a;Tomasulo 算法模拟和分析 一、实验目的 1&#xff1a;加深对指令级并行性及开发的理解。 2&#xff1a;加深对 Tomasulo 算法的理解。 3&#xff1a;掌握 Tomasulo 算法在指令流出、执行、写结果各阶段对浮点操作指令以及 load 和 store 指令进行了什么处…

CC工具箱使用指南:【用地用海指标汇总】

一、简介 在湘源上画的规划用地图&#xff0c;想导出规划指标表是很容易的。 但是现在很多用地图最终是要在ArcGIS中处理的&#xff0c;想再导出指标表就比较麻烦。 这个工具的主要功能就是在ArcGIS中汇总统计&#xff0c;生成类似湘源的Excel格式用地指标表。 二、工具参数…

20240117在本地机器识别OCR法语电影的字幕效果PK

20240117在本地机器识别OCR法语电影的字幕效果PK 2024/1/17 11:18 1959 - Jirai Cracher Sur Vos Tombes [Gast, Vian].avi https://www.pianbar.net//drama/52892.html 1959[我唾弃你的坟墓]Jirai cracher sur vos tombes[BT下载/迅雷下载] magnet:?xturn:btih:7c9c99d9d048…

【备战蓝桥杯】图论重点 敲黑板啦!

蓝桥杯备赛 | 洛谷做题打卡day11 文章目录 蓝桥杯备赛 | 洛谷做题打卡day11杂务题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 题解代码我的一些话 杂务 题目描述 John 的农场在给奶牛挤奶前有很多杂务要完成&#xff0c;每一项杂务都需要一定的时间来完成它。比如&a…

canvas绘制不同样式的六角星(示例源代码)

查看专栏目录 canvas实例应用100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

Python如何便捷的执行JavaScript代码,调用JS函数

文章目录 📖 介绍 📖🏡 环境 🏡📒 实现方法 📒📝 安装📝 使用⚓️ 相关链接 ⚓️📖 介绍 📖 Python在写一些爬虫代码的时候经常需要接触到JS逆向,其中如何让Python便捷的执行JS代码就成了一个很关键的问题,本文分享一种便捷的方式实现这个需求。 🏡 环…

司铭宇老师:二手房地产中介销售培训:二手房经纪人必备的七种销售能力

二手房地产中介销售培训&#xff1a;二手房经纪人必备的七种销售能力 在房地产行业中&#xff0c;二手房经纪人扮演着至关重要的角色。他们需要具备一系列的销售能力&#xff0c;以便更好地为客户服务&#xff0c;实现业绩增长。本文将详细介绍二手房经纪人必备的七种销售能力…

带你解析Git的基础功能(三)

文章目录 前言一.远程仓库的概念二.远程仓库的操作2.1新建远程仓库2.2 克隆远程仓库2.3 向远程仓库推送2.4 拉取远程仓库2.5 忽略特殊⽂件2.6 标签管理 三.Git实战场景3.1 Git多人实战场景一准备工作由开发者1和开发者2新增加文件内容。将dev的文件合并到master上总结 3.2 Git多…

20240118-最小下降路径总和

昨天的爬楼梯以前写过&#xff0c;是一道基础的动态规划&#xff0c;就不重新写了。 题目要求 给定一个n*n的矩阵数组&#xff0c;返回通过矩阵的任何下降路径的最小和。 下降路径从第一行中的任何元素开始&#xff0c;并选择下一行中正下方或左右对角线的元素。具体来说&am…