一、实现功能

使用 vue + canvas 实现鼠标拖动给图片打马赛克的功能,可以自定义马赛克的程度,并能撤销上一步的操作。

好多教程的撤销操作只是将绘制的马赛克按一小格一小格的撤销,本案例可以实现将鼠标按下到抬起绘制的所有马赛克一次性全部撤回,如果需求相同可作参考。

二、实现效果

第一次录制视频,而且csdn将视频转为gif格式才能上传,将就看一下。

三、代码

<template>
    <el-dialog title="图片编辑" :visible.sync="showCanvas" style="border-radius: 20px;" width="900px">
      <div style="height: 500px;width: 100%;overflow-y: scroll;text-align: center;">
        <canvas id="imgCanvasId" width="800px" @mousedown="mousedown" @mousemove="mousemove"
          @mouseup="mouseup"></canvas>
      </div>
      <div class="btn_box">
        <div style="height: 35px;display: flex;align-items: center;">
          <span>
            <el-tooltip placement="top" content="设置马赛克的程度,数值越大越迷糊">
              <span class="blueFont">
                <svg-icon icon-class="question" style="font-size: 14px;cursor: pointer;"></svg-icon>
              </span>
            </el-tooltip>
            马赛克程度:
          </span>
          <el-input-number v-model="stepNum" controls-position="right" @change="handleChange" :min="1"
            :max="30"></el-input-number>
        </div>
        <div style="height: 100%;flex: 1;display: flex;align-items: flex-end;justify-content: flex-end;">
          <el-button v-if="isPedometer > 0" size="small" type="primary" plain style="width: 90px;margin-right: 15px;"
            @click="undoHandle()">撤销</el-button>
          <el-tooltip class="item" v-if="isPedometer == 0" effect="dark" content="当前没有需要撤销的操作" placement="top-end">
            <el-button size="small" type="primary" plain style="width: 90px;margin-right: 15px;">撤销</el-button>
          </el-tooltip>
          <el-button size="small" type="primary" plain style="width: 90px;margin-right: 15px;"
            @click="cancelHandle()">取消</el-button>
          <el-button size="small" type="primary" style="width: 90px" @click="saveMosaicImg()">确定</el-button>
        </div>
      </div>
    </el-dialog>
</template>
data() {
      return {
        showCanvas: false,
        start: false,
        mosaicHistory: [],
        historyObj: {},
        isPedometer: 0, //撤销操作时使用
        stepNum: 12, //计步器,设置马赛克值
      }
    },
watchUrl() {
        this.showCanvas = true;
        this.isPedometer = 0;
        this.historyObj = {};
        this.$nextTick(() => {
          let canvas = document.getElementById("imgCanvasId");
          let ctx = canvas.getContext("2d");
          let img = new Image();
          img.src = require('@/assets/img/logo2.png'); //使用require函数来加载图片
          // img.src = this.imgInfo.imgUrl;
          img.setAttribute('crossOrigin', 'anonymous'); //防止canvas跨域获取不到图片信息
          img.onload = () => {
            let img_width = 800; // canvas画布的宽度固定
            canvas.width = img_width;
            canvas.height = img_width * img.height / img.width; // 通过图片的宽高比例来设置画布的高度,做到自适应
            ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, img_width, img_width * img.height / img.width);
          }
        })
      },
      // 鼠标按下开始绘制马赛克
      mousedown() {
        this.mosaicHistory = [];
        this.isPedometer++;
        this.start = true;
      },
      // 鼠标移动 绘制马赛克
      mousemove(e) {
        if (this.start) {
          let canvas = document.getElementById("imgCanvasId");
          let ctx = canvas.getContext("2d");
          ctx.willReadFrequently = true; // 优化提示,告诉浏览器计划频繁地读取画布上的数据,不然下面一行会送上警告
          let imgData = ctx.getImageData(0, 0, canvas.clientWidth, canvas.clientHeight);
          this.mosaicHistory.push(imgData); // 历史记录
          let num = this.stepNum; // //马赛克的程度,数值越大越模糊,数值越大马赛克高度越高
          let color = this.getXYColor(imgData, e.offsetX, e.offsetY); // 获取鼠标当前所在位置的像素RGBA
          // let color = ['128', '128', '128', '128']; // 也可以自己设定色值,注意:自己设定时每部分的取值都是 0 - 255
          for (let h = 0; h < num; h++) {
            for (let w = 0; w < num; w++) {
              this.setXYColor(imgData, e.offsetX + w, e.offsetY + h,
                color); //设置imgData上坐标为(e.offsetX + w, e.offsetY + h)的颜色
            }
          }
          ctx.putImageData(imgData, 0, 0); //更新canvas数据
        }
      },
      getXYColor(obj, x, y) {
        // obj 也就是 imgData, 是图片的相关宽高以及每个坐标点的rgba色值四个色值,一个点位的色值占四个位置
        // y * canvas.width + x 计算当前像素在像素数组中的索引
        // * 4 是因为每个像素有四个部分(RGBA),每个部分是一个字节(0-255),所以我们需要乘以4来获取R值的索引。
        let w = obj.width;
        let color = [];
        color[0] = obj.data[4 * (y * w + x)];
        color[1] = obj.data[4 * (y * w + x) + 1];
        color[2] = obj.data[4 * (y * w + x) + 2];
        color[3] = obj.data[4 * (y * w + x) + 3];
        return color;
      },
      setXYColor(obj, x, y, color) {
        let w = obj.width;
        obj.data[4 * (y * w + x)] = color[0];
        obj.data[4 * (y * w + x) + 1] = color[1];
        obj.data[4 * (y * w + x) + 2] = color[2];
        obj.data[4 * (y * w + x) + 3] = color[3];

      },
      // 鼠标抬起 马赛克绘制结束
      mouseup() {
        this.$set(this.historyObj, this.isPedometer, this.mosaicHistory);
        this.start = false;
      },
      // 取消
      cancelHandle() {
        this.isPedometer = 0;
        this.historyObj = {};
        this.start = false;
        this.showCanvas = false;
      },

      // 确定
      saveMosaicImg() {
        let canvas = document.getElementById("imgCanvasId");
        let tempSrc = canvas.toDataURL('image/png');
        let formDatas = new FormData();
        this.dataURLtoFile(tempSrc, 'mosaic.jpg') //此方法作用是将base64格式的图片转为文件格式之后方便再次上传到后端(若不需要上传可直接返回tempSrc即可展示)
      },
      dataURLtoFile(dataurl, filename) { //将base64转换为文件,dataurl为base64字符串,filename为文件名(必须带后缀名,如.jpg,.png)
        let arr = dataurl.split(','),
          mime = arr[0].match(/:(.*?);/)[1],
          bstr = atob(arr[1]),
          n = bstr.length,
          u8arr = new Uint8Array(n);
        while (n--) {
          u8arr[n] = bstr.charCodeAt(n);
        }
        let files = new File([u8arr], filename, {
          type: mime
        });

        // 调用组件接口,将打马赛克的图片传递过去
      },

      // 撤销操作
      undoHandle() {
        delete this.historyObj[this.isPedometer];
        this.isPedometer--;
        let canvas = document.getElementById("imgCanvasId");
        let ctx = canvas.getContext("2d");
        ctx.willReadFrequently = true;
        if (Object.keys(this.historyObj).length > 0) {
          let newArr = [];
          Object.keys(this.historyObj).forEach(key => {
            newArr = [...newArr, ...this.historyObj[key]];
          })
          let lastState = newArr.pop();
          ctx.putImageData(lastState, 0, 0);
        } else {
          this.watchUrl(); //如果长度等于0,说明没有绘制的马赛克了,重新加载canvas
        }
      }

GitHub 加速计划 / vu / vue
85
16
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:5 个月前 )
9e887079 [skip ci] 3 个月前
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 7 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐