vue基于canvas 涂鸦和文字编辑

editImage 组件

<template>
  <!-- v-show="$store.state.showDoodling" -->
  <div class="editImage">
    <div class="optBtn">
      <!-- style="width: 100vw;height: 100vh;display: flex;justify-content: center;align-items: center;" -->
      <div>
        <canvas
          ref="canvas"
          :id="radom"
          :class="{ canDraw: 'canvas' }"
          @mousedown="canvasDown($event)"
          @mouseup="canvasUp($event)"
          @mousemove="canvasMove($event)"
          @touchstart="canvasDown($event)"
          @touchend="canvasUp($event)"
          @touchmove="canvasMove($event)"
        >
        </canvas>
      </div>
      <div class="tuyaTit">
        <span class="tycancel" @click="goBack">取消</span>
        <span class="tysure" @click="toSaveImage">完成</span>
      </div>
      <div
        class="inpContainer"
        :style="{
          left: moveX < 120 ? 0 : moveX - 120 + 'px',
          top: moveY + 'px',
        }"
        v-if="showTextStatus"
      >
        <input
          type="text"
          name=""
          class="addTextInp"
          placeholder="请输入添加的文字"
          v-model="textValue"
        />
        <div class="addBtn" @click="drawText(moveX > 120 ? moveX - 60 : moveX, moveY - 128)">添加</div>
      </div>
      <div class="colorType">
        <div class="coloSing">
          <span
            class="selColor"
            v-for="(item, index) in colorList"
            :key="index"
            :style="{ backgroundColor: item }"
            @click="changeColor(item, index)"
            :class="{ isChooseColor: isChooseColor == index }"
          ></span>
          <span class="selRevoke" @click="revokeDraw">
            <img src="@/assets/images/revoke.png" width="20px" height="20px" />
          </span>
          <span class="text-icon" @click="showTextContainer">
            <img
              src="@/assets/images/text-icon.png"
              width="20px"
              height="20px"
            />
          </span>
        </div>
      </div>
    </div>
    <!-- 点击完成选择接下的操作 -->
    <div class="doodlingModel" v-if="isComplete">
      <ul>
        <li @click="transmit">发送给朋友</li>
        <!-- <li @click="toSaveImage">保存图片</li> -->
      </ul>
    </div>
    <div class="fog" v-if="isComplete" @click="closeModel"></div>
  </div>
</template>

<script>
import { fileUpload } from '@/utils';

export default {
  name: 'EditImage',
  data() {
    const data = {
      showModal: true,
      imageurl: '',
      width: 400,
      height: 500,
      canDraw: true,
      url: '',
      info: '',
      lineColor: 'red',
      lineWidth: '2',
      lineType: 'rec',
      colorList: [
        '#FFFFFF',
        '#030102',
        '#E75D58',
        '#F6C543',
        '#FF8500',
        '#4FABF8',
        '#6467E8',
      ],
      // 同一页面多次渲染时,用于区分元素的id
      radom: 'myCanvas',
      // canvas对象
      context: {},
      // 是否处于绘制状态
      canvasMoveUse: false,
      // 绘制矩形和椭圆时用来保存起始点信息
      beginRec: {
        x: '',
        y: '',
        imageData: '',
      },
      moveX: '',
      moveY: '',
      // 储存坐标信息
      drawInfo: [],
      // 背景图片缓存
      img: new Image(),
      canvasHistory: [],
      restore: [],
      repeat: [],
      myCanvas: null,
      hub: 0,
      showImg: false,
      editIamgeUrl: '',
      isComplete: false,
      msgData: {},
      inited: false,
      top: 0,
      left: 0,
      isChooseColor: false,
      textValue: '',
      showTextStatus: false,
    };
    return data;
  },
  props: {
    propsImageurl: {
      type: String,
      default() {
        return '';
      },
    },
  },
  created() {
    const self = this;
    let originLink = '';
    originLink = this.propsImageurl;
    self.url = originLink;
    this.msgData = self.$route.query.msg;
    self.width = document.documentElement.clientWidth; // 可见区域宽度
    self.height = document.documentElement.clientHeight; // 可见区域高度

    if (localStorage.getItem('lineColor')) {
      self.lineColor = localStorage.getItem('lineColor');
    }
  },
  mounted() {
    const self = this;
    self.initDraw();
  },
  methods: {
    // 转发消息
    transmit() {
      const self = this;

      if (
        self.msgData.type === 'custom' &&
        self.msgData.content.type === 'imageurl'
      ) {
        self.msgData.content.imageurl = self.imageurl;
      } else if (self.msgData.type === 'image') {
        self.msgData.originLink = self.imageurl;
      }
      self.$router.push({
        path: '/forwardmsg',
        query: {
          sendMsg: self.msgData,
        },
      });
    },
    goBack() {
      //   this.context.clearRect(0, 0, this.width, this.height);
      this.$emit('hide-edit');
    },
    // canvas转base64
    toSaveImage() {
      this.imageurl = this.myCanvas.toDataURL('image/png');
      const blob = this.base64ToBlob(this.imageurl);
      const file = this.blobToFile(blob, 'newImg.png');
      const type = 'png';
      let obj = '';
      fileUpload(this, file, type, (url) => {
        obj = {
          image: url,
          content: '',
        };
        if (obj.image) {
          this.$emit('getNewImg', obj.image);
        }
      });
    },
    // base64转blob解决ios兼容问题
    base64ToBlob(base64Data) {
      const arr = base64Data.split(',');
      const fileType = arr[0].match(/:(.*?);/)[1];
      const bstr = atob(arr[1]);
      let l = bstr.length;
      const u8Arr = new Uint8Array(l);

      while (l--) {
        u8Arr[l] = bstr.charCodeAt(l);
      }
      return new Blob([u8Arr], {
        type: fileType,
      });
    },
    // blob转file上传
    blobToFile(newBlob, fileName) {
      const Obj = newBlob;
      Obj.lastModifiedDate = new Date();
      Obj.name = fileName;
      return newBlob;
    },
    // 撤销
    revokeDraw() {
      if (this.canvasHistory.length > 1) {
        this.context.putImageData(
          this.canvasHistory[this.canvasHistory.length - 2],
          0,
          0,
        );
        this.canvasHistory.length--;
      }
    },
    // huanse
    changeColor(item, index) {
      this.lineColor = item;
      this.context.strokeStyle = this.lineColor;
      this.isChooseColor = index;
      localStorage.setItem('lineColor', this.lineColor);
    },
    // 关闭窗口
    closeModule() {
      this.showModal = false;
    },
    // 操作按钮 1 涂鸦  2 编辑文字  3、确定
    operation(index) {
      if (index === 1) {
        this.canDraw = true;
      } else if (index === 2) {
        this.canDraw = false;
      } else if (index === 3) {
        this.canDraw = false;
      }
      this.closeModule();
    },
    // 初始化绘制信息
    initDraw() {
      // 初始化画布
      this.myCanvas = document.getElementById(this.radom);
      this.context = this.myCanvas.getContext('2d');
      // 初始化背景图片
      this.img.setAttribute('crossOrigin', 'Anonymous');
      this.img.src = this.url;
      this.img.onerror = () => {
        const timeStamp = +new Date();
        this.img.src = this.url + '?' + timeStamp;
      };

      this.img.onload = () => {
        this.clean();
      };

      // 初始化画笔
      this.context.lineWidth = this.lineWidth;
      this.context.strokeStyle = this.lineColor;
    },
    // 鼠标按下
    canvasDown(e) {
      this.showModal = false;
      if (this.canDraw) {
        this.repeat.length = 0;
        this.canvasMoveUse = true;
        // client是基于整个页面的坐标,offset是cavas距离pictureDetail顶部以及左边的距离

        const canvasX =
          event.changedTouches[0].clientX - e.target.parentNode.offsetLeft;
        const canvasY =
          event.changedTouches[0].clientY - e.target.parentNode.offsetTop;

        // 记录起始点和起始状态
        this.beginRec.x = canvasX;
        this.beginRec.y = canvasY;
        this.beginRec.imageData = this.context.getImageData(
          0,
          0,
          this.width,
          this.height,
        );
        // 存储本次绘制坐标信息
        this.drawInfo.push({
          x: canvasX / this.width,
          y: canvasY / this.height,
          type: this.lineType,
        });

        this.moveX = event.changedTouches[0].clientX;
        this.moveY = event.changedTouches[0].clientY;
        // console.log(this.moveX);
        // console.log(this.moveY);
      }
    },

    // 鼠标移动时绘制
    canvasMove() {
      if (this.canvasMoveUse && this.canDraw) {
        this.context.beginPath(); // 划线改变颜色必须用这个
        this.context.lineCap = 'round';
        this.context.lineJoin = 'round';
        this.context.moveTo(this.moveX - this.left, this.moveY - this.top);
        this.context.lineTo(
          event.changedTouches[0].clientX - this.left,
          event.changedTouches[0].clientY - this.top,
        );
        this.context.closePath();
        this.context.stroke();

        this.moveX = event.changedTouches[0].clientX;
        this.moveY = event.changedTouches[0].clientY;
      }
    },
    showTextContainer() {
      this.showTextStatus = true;
    },
    // 绘制文字
    drawText(x, y) {
      let handldX = x;
      if (x <= 0) {
        handldX = 40;
      }
      if (this.textValue) {
        this.showTextStatus = false;
      }
      this.context.font = 'bold 16px Arial';
      this.context.textAlign = 'center';
      this.context.textBaseline = 'bottom';
      this.context.zIndex = '99';
      this.context.fillStyle = '#ccc';
      this.context.strokeText(this.textValue, handldX, y);
      this.context.closePath();
      this.context.stroke();
      this.textValue = '';
    },
    // 鼠标抬起
    canvasUp() {
      if (this.canDraw) {
        this.canvasMoveUse = false;
        this.showModal = true;
        this.canvasHistory.push(this.context.getImageData(0, 0, this.width, this.height));
      }
    },
    // 保持纵横比缩放图片,使图片的长边能完全显示出来
    containImg(sx, sy, boxW, boxH, sourceW, sourceH) {
      let dx = sx;
      let dy = sy;
      let dWidth = boxW;
      let dHeight = boxH;
      if (sourceW > sourceH || (sourceW === sourceH && boxW < boxH)) {
        dHeight = (sourceH * dWidth) / sourceW;
        dy = sy + ((boxH - dHeight) / 2);
      } else if (sourceW < sourceH || (sourceW === sourceH && boxW > boxH)) {
        dWidth = (sourceW * dHeight) / sourceH;
        dx = sx + ((boxW - dWidth) / 2);
      }
      return {
        dx,
        dy,
        dWidth,
        dHeight,
      };
    },
    // 清空画布
    // img  规定要使用的图像、画布或视频。
    // sx   可选。开始剪切的 x 坐标位置。
    // sy   可选。开始剪切的 y 坐标位置。
    // swidth   可选。被剪切图像的宽度。
    // sheight  可选。被剪切图像的高度。
    // x    在画布上放置图像的 x 坐标位置。
    // y    在画布上放置图像的 y 坐标位置。
    // width    可选。把图像绘制到画布上的宽度。(伸展或缩小图像)
    // height   可选。把图像绘制到画布上的高度。(伸展或缩小图像)
    // ctx.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
    clean() {
      // 计算宽高比
      const self = this;
      const imgRect = this.containImg(
        0,
        0,
        this.width,
        this.height,
        this.img.width,
        this.img.height,
      );
      self.width = imgRect.dWidth;
      self.height = imgRect.dHeight;
      // 给canvas赋值宽高
      self.$refs.canvas.width = self.width;
      self.$refs.canvas.height = self.height;
      // canvas必须有高
      self.$refs.canvas.style.height = self.height;
      // 清空canvas画布
      self.context.clearRect(0, 0, self.width, self.height);
      // 获取水平垂直偏移距离
      self.top = (document.documentElement.clientHeight - self.height) / 2;
      self.left = (document.documentElement.clientWidth - self.width) / 2;
      // this.context.drawImage(this.img, imgRect.dx, imgRect.dy, imgRect.dWidth, imgRect.dHeight);
      // 渲染图片
      self.context.drawImage(self.img, 0, 0, imgRect.dWidth, imgRect.dHeight);
      //  this.context.drawImage(this.img, imgRect.dx, imgRect.dy, imgRect.dWidth, imgRect.dHeight);
      // 画布垂直居中
      self.$refs.canvas.style.marginTop = self.top + 'px';
      // 初始化鼠标在画布的位置
      self.moveY = self.top;
      // 画布水平居中
      self.$refs.canvas.style.left = self.left + 'px';
      // 修改画笔颜色
      for (let i = 0, len = this.colorList.length; i < len; i++) {
        if (self.lineColor === self.colorList[i]) {
          self.changeColor(self.lineColor, i);
          break;
        }
      }
      // 记录第一次canvas图像(防止撤回到最后空白)
      self.canvasHistory.push(self.context.getImageData(0, 0, self.width, self.height));
    },
    // 关闭模态框
    closeModel() {
      self.isComplete = false;
    },
  },
};
</script>

<style lang="scss" scoped>
.editImage {
  position: fixed;
  z-index: 999;
  width: 100%;
  height: 100vh;
  background-color: #fff;
  .fog {
    position: fixed;
    top: 0px;
    left: 0px;
    background-color: #000;
    opacity: 0.5;
    width: 100%;
    height: 100%;
    z-index: 100;
  }
  .doodlingModel {
    width: 85%;
    position: absolute;
    left: 7.5%;
    top: 35%;
    background-color: #ffffff;
    border-radius: 5px;
    z-index: 1000;
  }
  .doodlingModel > ul {
    width: 100%;
  }
  .doodlingModel > ul > li {
    width: 90%;
    color: #000000;
    font-size: 16px;
    height: 50px;
    line-height: 50px;
    border-bottom: 1px solid #dedede;
    margin: 0 auto;
  }
  .doodlingModel > ul > li:last-of-type {
    border-bottom: none;
  }
}

.optBtn {
  width: 100%;
  height: 100vh;
  position: relative;
  bottom: 0;
  left: 0;
  display: flex;
  justify-content: space-around;
  background-color: rgba(0, 0, 0, 0.5);
  z-index: 1;
  .inpContainer {
    position: absolute;
    display: flex;
    .addTextInp {
      width: 120px;
    }
    .addBtn {
      height: 20px;
      text-align: center;
      line-height: 20px;
      font-size: 14px;
      border: 2px solid #ccc;
      color: rgb(246, 197, 67);
    }
  }
  .colorType {
    bottom: 0px;
    position: absolute;
    padding: 0 1.8125rem;
    height: 3.8125rem;
    width: 100%;
    padding: 0 10px;
    background-image: url(../../assets/images/doodlingbg.png);
    .huabi {
      width: 1rem;
      height: 1rem;
      margin-top: 1.172rem;

      img {
        width: 100%;
        height: 100%;
      }
    }

    .coloSing {
      width: 100%;
      display: flex;
      justify-content: space-around;
      margin-top: 50px;
      .selColor {
        width: 20px;
        height: 20px;
        border-radius: 50%;
        border: 1px solid #fff;
      }
      .isChooseColor {
        border: 3px solid #fff;
      }
    }
  }

  .tuyaTit {
    /* width: calc(100% - 1.25rem); */
    width: 90%;
    display: flex;
    justify-content: space-between;
    align-items: center;
    /* margin: 3rem 0.625rem 0  0.625rem; */
    color: #fff;
    /* font-size: .5rem; */
    font-size: 15px;
    font-weight: 500;
    position: absolute;
    top: 20px;
    /* left: 0.625rem; */
    left: 5%;
  }

  .tysure {
    padding: 5px 15px;
    background-color: #ff8500;
    border-radius: 3px;
  }
}
</style>

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

给钱,谢谢!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值