使用canvas绘制图片蒙版/获取绘制区域坐标/转成base64

vue2/canvas所有代码(已验证,可正常运行)

数据来源于GPT4:GPT4o

javascript 复制代码
<template>
  <div class="canvas-container">
    <div class="modal-content">
      <div class="modal_header">
        <div>请点击鼠标选择重绘区域</div>
        <div class="modal_header_close">
          <span class="close" @click="closeModal">&times;</span>
        </div>
      </div>
      <canvas
        ref="canvas"
        @mousedown="startDrawing"
        @mousemove="draw"
        @mouseup="finishDrawing"
        @touchstart="startDrawing"
        @touchmove="draw"
        @touchend="finishDrawing"
      ></canvas>
      <button @click="resetDrawing">重置</button>
      <button @click="sendMuskAction(muskItem)">开始重绘</button>
      <div>
        startX: {{  Math.floor(startX) }}, startY: {{ Math.floor(startY) }}, endX: {{ Math.floor(endX) }}, endY: {{ Math.floor(endY) }}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "CanvasDemo",
  data() {
    return {
      imageUrl:
        "https://ts1.cn.mm.bing.net/th/id/R-C.6b9130073d73ef5ef0c45012a0774147?rik=7vPGVRRTb6MDcw&riu=http%3a%2f%2fchina-ybxgg.com%2fuploadfile%2fimage%2f%e6%b3%95%e5%85%b0%2f20200330110537873.jpg&ehk=lSqw%2bi0Cu2aykuT7KdHvQwVenva%2baGZ7oB2G6uBDxLs%3d&risl=&pid=ImgRaw&r=0",
      muskItem: null, // 蒙版操作的mj的item
      showModal: false, //是否显示蒙版
      isDrawing: false, //是否正在绘图
      startX: 0, //蒙版开始x坐标
      startY: 0, //蒙版开始y坐标
      endX: 0, //蒙版结束y坐标
      endY: 0, //蒙版结束Fy坐标
      rectangles: [], // 存储多个矩形的数组
      context: null, //canvas context
      image: new Image(), //需要蒙版操作的图片
      // imageUrl: 'http://ipv4.sharemoon.club:29000/images/2024/01/02/1a9cf051c1996eebdd2af2a86e95f1f6.png',//需要蒙版操作的图片url
      originalWidth: 0, //图片原始宽度
      originalHeight: 0, //图片原始高度
      scale: 1, //缩放比例
      muskBase64: null, //绘制的蒙版
    };
  },
  methods: {
 //======================================蒙版begin======================================
        loadImage(imageUrl) {
            this.image.onload = () => this.drawImage();
            this.image.src = imageUrl;
        },
        drawImage() {
            const canvas = this.$refs.canvas;
            this.originalWidth = this.image.width;
            this.originalHeight = this.image.height;

            // 根据模态框大小调整图片显示大小 offsetWidth 父级宽度
            this.scale = Math.min(
                canvas.parentElement.offsetWidth / this.originalWidth,
                canvas.parentElement.offsetHeight / this.originalHeight
            ) * 3;
            canvas.width = this.originalWidth * this.scale;
            canvas.height = this.originalHeight * this.scale;

            this.context = canvas.getContext('2d');
            this.context.drawImage(this.image, 0, 0, canvas.width, canvas.height);
        },
        startDrawing(event) {
            console.log("start draw")
            const rect = this.$refs.canvas.getBoundingClientRect();
            const scaleX = this.$refs.canvas.width / rect.width;
            const scaleY = this.$refs.canvas.height / rect.height;

            // 检查是否为触摸事件
            if(event.touches) {
                // 对于触摸事件,使用touches数组中的第一个触摸点
                this.startX = (event.touches[0].clientX - rect.left) * scaleX;
                this.startY = (event.touches[0].clientY - rect.top) * scaleY;
            } else {
                // 对于鼠标事件,使用clientX和clientY
                this.startX = (event.clientX - rect.left) * scaleX;
                this.startY = (event.clientY - rect.top) * scaleY;
            }

            this.isDrawing = true;
        },
        draw(event) {
            if (!this.isDrawing) return;
            const rect = this.$refs.canvas.getBoundingClientRect();
            const scaleX = this.$refs.canvas.width / rect.width;
            const scaleY = this.$refs.canvas.height / rect.height;
            let x = null
            let y = null
            if(event.touches) {
                // 对于触摸事件,使用touches数组中的第一个触摸点
                x = (event.touches[0].clientX - rect.left) * scaleX;
                y = (event.touches[0].clientY - rect.top) * scaleY;
            } else {
                // 对于鼠标事件,使用clientX和clientY
                x = (event.clientX - rect.left) * scaleX;
                y = (event.clientY - rect.top) * scaleY;
            }

            this.context.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            this.context.drawImage(this.image, 0, 0, this.$refs.canvas.width, this.$refs.canvas.height);

            // 重新绘制已存在的矩形
            this.rectangles.forEach(rect => {
                this.context.beginPath();
                this.context.rect(rect.startX, rect.startY, rect.endX - rect.startX, rect.endY - rect.startY);
                this.context.fillStyle = 'rgba(255, 255, 255, 0.5)';
                this.context.fill();
                this.context.strokeStyle = 'red';
                this.context.stroke();
            });

            this.context.beginPath();
            this.context.rect(this.startX, this.startY, x - this.startX, y - this.startY);
            this.context.fillStyle = 'rgba(255, 255, 255, 0.5)';
            this.context.fill();
            this.context.strokeStyle = 'red';
            this.context.stroke();
        },
        finishDrawing(event) {
            this.isDrawing = false;
            const rect = this.$refs.canvas.getBoundingClientRect();
            const scaleX = this.$refs.canvas.width / rect.width;
            const scaleY = this.$refs.canvas.height / rect.height;

            if(event.changedTouches && event.changedTouches.length > 0) {
                // 对于触摸事件,使用touches数组中的第一个触摸点
                this.endX = (event.changedTouches[0].clientX - rect.left) * scaleX;
                this.endY = (event.changedTouches[0].clientY - rect.top) * scaleY;
            } else {
                // 对于鼠标事件,使用clientX和clientY
                this.endX = (event.clientX - rect.left) * scaleX;
                this.endY = (event.clientY - rect.top) * scaleY;
            }
            // 创建蒙版
            // 将当前矩形添加到数组中
            this.rectangles.push({
                startX: this.startX,
                startY: this.startY,
                endX: this.endX,
                endY: this.endY
            });
            console.log(this.rectangles);
            this.createMask();
        },
        resetDrawing() {
            this.context.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            this.context.drawImage(this.image, 0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
            this.rectangles = []; // 清空矩形数组
        },
        createMask() {
            const canvas = document.createElement('canvas');
            canvas.width = this.originalWidth; // 设置画布宽度
            canvas.height = this.originalHeight; // 设置画布高度
            const ctx = canvas.getContext('2d'); // 获取画布上下文

            // 填充黑色背景
            ctx.fillStyle = 'black';
            ctx.fillRect(0, 0, canvas.width, canvas.height);// 填充黑色背景

            // 填充白色矩形
            ctx.fillStyle = 'white';
            this.rectangles.forEach(rect => { // 遍历矩形数组
                ctx.fillRect(rect.startX / this.scale, rect.startY / this.scale,// 除以缩放比例
                    (rect.endX - rect.startX) / this.scale,// 除以缩放比例
                    (rect.endY - rect.startY) / this.scale
                );
            });

            // 输出Base64
            const base64Image = canvas.toDataURL();// 输出Base64
            console.log(base64Image,"base64Image");
            this.muskBase64 = base64Image
        },
        closeModal() {
            this.showModal = false;
            this.resetDrawing();
        },
        //======================================蒙版end======================================
  },
  mounted() {
    this.loadImage(this.imageUrl);
    // Code to run when the component is mounted goes here
  },
};
</script>

<style scoped>
.canvas-container {
    width: 600px;
    height: 600px;
    border: 1px solid #1764e8;
    display: flex;
    justify-content: center;
    align-items: center;
}
.modal-content {
  max-width: 90%;
  background-color: #fefefe;
  padding: 20px;
  border: 1px solid #888;
  display: flex;
  flex-direction: column;
  justify-content: flex-start; /* 从顶部开始布局元素 */
  align-items: center;
  overflow: auto; /* 如果内容过多,允许滚动 */
  position: relative;
}
.shangchuan /deep/.el-upload--picture-card {
  margin-left: -155px !important;
}
canvas {
  width: 80%; /* 使用100%宽度确保与父元素宽度一致 */
  height: auto; /* 高度自动调整 */
}
</style>
相关推荐
abc800211703430 分钟前
前端Bug 修复手册
前端·bug
Best_Liu~32 分钟前
el-table实现固定列,及解决固定列导致部分滚动条无法拖动的问题
前端·javascript·vue.js
_斯洛伐克1 小时前
下降npm版本
前端·vue.js
苏十八3 小时前
前端进阶:Vue.js
前端·javascript·vue.js·前端框架·npm·node.js·ecmascript
st紫月3 小时前
用MySQL+node+vue做一个学生信息管理系统(四):制作增加、删除、修改的组件和对应的路由
前端·vue.js·mysql
乐容4 小时前
vue3使用pinia中的actions,需要调用接口的话
前端·javascript·vue.js
似水明俊德4 小时前
ASP.NET Core Blazor 5:Blazor表单和数据
java·前端·javascript·html·asp.net
至天5 小时前
UniApp 中 Web/H5 正确使用反向代理解决跨域问题
前端·uni-app·vue3·vue2·vite·反向代理
与墨学长5 小时前
Rust破界:前端革新与Vite重构的深度透视(中)
开发语言·前端·rust·前端框架·wasm
H-J-L6 小时前
Web基础与HTTP协议
前端·http·php