阅读 60

canvas 图片旋转、画图、写字、撤销、文字大小、颜色、保存图片到本地

效果图

image.png

代码

 

<template>
  <div class="canvas_content">
    <div class="canvas_back">
      <div class="edit_content">
        <div class="paint_brush paint_color">
          <input type="color" v-model="colorModel" />
        </div>

        <div class="paint_brush paint_color">
          <img src="../../assets/images/画笔.png" alt="" />
          <img src="../../assets/images/画笔加粗.png" alt="" @click="add" />
          <div
            :style="{
              border: lineWidth + 'px solid #FFFFFF',
            }"
            class="border"
          ></div>
          <img src="../../assets/images/画笔减粗.png" alt="" @click="reduce" />
        </div>
        <div class="paint_brush paint_color">
          <img src="../../assets/images/文字.png" alt="" @click="handleEdit" />
          <a-select v-model="fontValue">
            <a-select-option
              v-for="(item, index) in fontSizeValue"
              :key="index"
              :value="item.name"
            >
              {{ item.name }}
            </a-select-option>
          </a-select>
        </div>

        <div class="paint_brush paint_color">
          <img src="../../assets/images/加粗.png" alt="" @click="handelBold" />
        </div>
        <div class="paint_brush paint_color">
          <button @click="rotateImg">旋转</button>
        </div>
        <div class="paint_brush paint_color">
          <img
            src="../../assets/images/反撤销.png"
            alt=""
            @click="eraserReduce"
          />
          <img src="../../assets/images/撤销.png" alt="" @click="eraserAdd" />
        </div>
      </div>
      <div class="canvas">
        <canvas
          @mousedown="mousedown($event)"
          ref="refCanvas"
          id="paintingId"
          :width="width"
          :height="height"
        ></canvas>
        <canvas
          @mousedown="mousedown($event)"
          ref="imgCanvas"
          id="imgCanvas"
          :width="imgWidth"
          :height="imgHeight"
        ></canvas>
        <div
          class="text-box"
          :class="boldStatus ? 'bold_font' : 'no-bold'"
          v-if="textBoxRect"
          v-focus
          contenteditable
          @input="textValueInput"
          :style="{
            left: textBoxRect.left + 'px',
            top: textBoxRect.top + 'px',
            height: fontHeight + 'px',
            fontSize: fontValue + 'px',
            fontWeight: boldStatus ? '600' : 'normal',
            color: colorModel,
          }"
        ></div>
      </div>
      <div class="tips" v-if="showTipsStatus">
        编辑后的图片将保存于“批阅作业”区域
        <img
          @click="closeShowTips"
          src="../../assets/images/close-circle.png"
          alt=""
        />
      </div>
      <div class="button">
        <a-button type="link" style="color: #aeaeae" @click="$emit('close')">
          取消
        </a-button>
        <a-button type="link" @click="defineImg"> 保存批阅 </a-button>
      </div>
    </div>
  </div>
</template>

<script type="text/ecmascript-6">
import Shadow from '@/components/common/Shadow'
import BoxHeader from '@/components/common/BoxHeader'
import html2canvas from 'html2canvas'
import { getOssSign } from '@/apis/common'
import { formatUploadFile } from '@/assets/js/Common'
import { getRecordEdit } from '@/apis/homework'
import { State, Getter, Action, Mutation, namespace } from 'vuex-class'
import file from '@/mixin/common/file'

// const homeWorkStore = namespace('homeWork')
export default {
  name: '',
  components: { Shadow, BoxHeader },
  data() {
    return {
      title: '编辑',
      eraser: null,
      canvas: null,
      ctx: null,
      imgCtx: null,
      imgCanvas: null,
      lineWidth: 1,
      // strokeStyle: '#F00505',
      mode: '',
      textBoxRect: null,
      showTipsStatus: true,
      textValue: '',
      width: '800',
      height: '600',
      imgWidth: '800',
      imgHeight: '600',
      colorModel: '#F00505',
      step: -1,
      canvasHistory: [],
      fontValue: '14',
      fontSizeValue: [
        {
          name: '8',
          id: 1,
        },
        {
          name: '10',
          id: 2,
        },
        {
          name: '12',
          id: 3,
        },
        {
          name: '14',
          id: 4,
        },
        {
          name: '16',
          id: 5,
        },
        {
          name: '18',
          id: 6,
        },
        {
          name: '24',
          id: 7,
        },
        {
          name: '36',
          id: 8,
        },
      ],
      boldStatus: false,
      uploadSign: null,
      fontHeight: null,
      ossConfig: {
        path: 'homeworkdetail/',
        host: 'https://img.tifangedu.com',
        bucket: 'tfoneline',
      },
      rotateStatua: null,
    }
  },
  mixins: [file],
  computed: {},
  head() {
    return {}
  },
  props: ['imgPainting', 'imgObject', 'modifyImgObj'],
  watch: {},
  created() {},
  mounted() {
    this.initState()
    // this.getOssSign()
    console.log(JSON.parse(sessionStorage.getItem('showTipsStatus')))
    if (
      sessionStorage.getItem('showTipsStatus') &&
      !JSON.parse(sessionStorage.getItem('showTipsStatus'))
    ) {
      this.showTipsStatus = JSON.parse(sessionStorage.getItem('showTipsStatus'))
    }
    console.log(this.imgObject, '=====')
  },
  watch: {
    fontValue(val) {
      if (val === 18) {
        this.fontHeight = 30
      }
    },
  },
  directives: {
    focus: {
      inserted(el) {
        setTimeout(() => {
          el.focus()
        }, 100)
      },
      componentUpdated(el) {
        setTimeout(() => {
          el.focus()
        }, 100)
      },
    },
  },
  methods: {
    rotateImg() {
      const image = new Image()
      image.onload = () => {
        this.imgCtx.clearRect(0, 0, this.imgWidth, this.imgHeight)
        this.imgCtx.translate(this.imgWidth / 2, this.imgHeight / 2)
        this.imgCtx.rotate(Math.PI / 2)
        this.imgCtx.drawImage(
          image,
          -this.imgWidth / 2,
          -this.imgHeight / 2,
          this.imgWidth,
          this.imgHeight
        )
        this.imgCtx.translate(-this.imgWidth / 2, -this.imgHeight / 2)
      }
      image.setAttribute('crossOrigin', 'anonymous')
      image.src = this.imgPainting
      if (this.rotateStatua >= 3) {
        this.rotateStatua = null
      } else {
        this.rotateStatua += 1
      }
      console.log(this.rotateStatua, ' this.rotateStatua ')
      // if (!this.rotateStatua) {

      // }
      console.log(this.rotateStatua)
    },
    initState() {
      this.imgCanvas = this.$refs.imgCanvas
      this.canvas = this.$refs.refCanvas
      this.imgCtx = this.imgCanvas.getContext('2d')
      this.ctx = this.canvas.getContext('2d')
      const image = new Image()
      image.onload = () => {
        this.imgCtx.drawImage(image, 0, 0, this.width, this.height)
      }
      image.setAttribute('crossOrigin', 'anonymous')
      image.src = this.imgPainting
    },
    closeShowTips() {
      this.showTipsStatus = false
      JSON.stringify(
        sessionStorage.setItem('showTipsStatus', this.showTipsStatus)
      )
      // this.changeIsShowTips(this.showTipsStatus)
    },
    mousedown(e) {
      // 计算鼠标在画布的距离
      if (this.step < 0) {
        this.step++
        this.canvasHistory.push(this.canvas.toDataURL()) // 添加新的绘制到历史记录
      }
      const disX = e.clientX - this.canvas.offsetLeft
      const disY = e.clientY - (this.canvas.offsetTop + 50)
      // 输入框
      if (this.mode === 'text') {
        if (this.textValue) {
          this.drawText()
          this.canvasHistory.push(this.canvas.toDataURL()) // 添加新的绘制到历史记录
        } else {
          this.textBoxRect = {
            left: disX,
            top: disY,
          }
        }
        // return
      }
      // 每次必须重新开始,让它们变成多个。
      this.ctx.beginPath()
      // 设置画线的宽,与颜色
      this.ctx.lineWidth = this.lineWidth
      this.ctx.strokeStyle = this.colorModel
      // 设置画的起始点
      this.ctx.moveTo(disX, disY)
      document.addEventListener('mousemove', this.mousemove)
      document.addEventListener('mouseup', this.mouseup)
    },
    drawText() {
      this.ctx.moveTo(this.textBoxRect.left, this.textBoxRect.top)
      this.ctx.fillStyle = this.colorModel
      this.ctx.font = `${this.boldStatus ? '600' : 'normal'} ${
        this.fontValue
      }px "微软雅黑"`
      this.ctx.textBaseline = 'top'
      this.ctx.textAlign = 'left'
      this.ctx.fillText(
        this.textValue,
        this.textBoxRect.left,
        this.textBoxRect.top
      )
      this.textBoxRect = null
      this.mode = ''
      this.textValue = ''
      this.boldStatus = false
    },
    handelBold() {
      this.boldStatus = !this.boldStatus
      console.log(this.boldStatus)
    },
    mousemove(e) {
      // 鼠标移动画线
      const disX = e.clientX - this.canvas.offsetLeft
      const disY = e.clientY - (this.canvas.offsetTop + 50)
      // 移动时设置画线的结束位置。并且显示
      this.ctx.lineTo(disX, disY) // 鼠标点下去的位置
      this.ctx.stroke()
    },
    mouseup() {
      // 鼠标离开时记录
      this.step++
      if (this.step < (this.canvasHistory && this.canvasHistory.length)) {
        this.canvasHistory.length = this.step // 截断数组
      }
      document.removeEventListener('mousemove', this.mousemove)
      document.removeEventListener('mouseup', this.mouseup)
      this.canvasHistory.push(this.canvas.toDataURL()) // 添加新的绘制到历史记录
    },
    add() {
      if (this.lineWidth >= 6) return
      this.lineWidth++
    },
    reduce() {
      if (this.lineWidth > 1) {
        this.lineWidth--
      }
    },
    eraserAdd() {
      if (this.step <= this.canvasHistory.length - 1) {
        this.step++
        let canvasPic = new Image()
        canvasPic.src = this.canvasHistory[this.step]
        canvasPic.addEventListener('load', () => {
          this.ctx.clearRect(0, 0, this.width, this.height)
          this.ctx.drawImage(canvasPic, 0, 0)
        })
      } else {
        this.$message.info('已经是最新的操作了')
      }
    },
    eraserReduce() {
      if (this.step > 0) {
        this.step--
        this.ctx.clearRect(0, 0, this.width, this.height)
        let canvasPic = new Image()
        canvasPic.src = this.canvasHistory[this.step]
        canvasPic.addEventListener('load', () => {
          this.ctx.drawImage(canvasPic, 0, 0)
        })
      } else {
        this.$message.info('不能再继续撤销了')
      }
    },
    async defineImg() {
      let flag = true
      if (flag) {
        flag = false
        if (this.rotateStatua === 1) {
          this.imgCtx.translate(this.width / 2, this.height / 2)
          this.imgCtx.rotate(-(Math.PI / 2))
          this.imgCtx.drawImage(
            this.canvas,
            -this.width / 2,
            -this.height / 2,
            this.width,
            this.height
          )
          this.imgCtx.translate(-this.width / 2, -this.height / 2)
        } else if (this.rotateStatua === 2) {
          this.imgCtx.translate(this.width / 2, this.height / 2)
          this.imgCtx.rotate((180 * Math.PI) / 180)
          this.imgCtx.drawImage(
            this.canvas,
            -this.width / 2,
            -this.height / 2,
            this.width,
            this.height
          )
          this.imgCtx.translate(-this.width / 2, -this.height / 2)
        } else if (this.rotateStatua === 3) {
          this.imgCtx.translate(this.width / 2, this.height / 2)
          this.imgCtx.rotate((90 * Math.PI) / 180)
          this.imgCtx.drawImage(
            this.canvas,
            -this.width / 2,
            -this.height / 2,
            this.width,
            this.height
          )
          this.imgCtx.translate(-this.width / 2, -this.height / 2)
        }
        if (!this.rotateStatua) {
          this.imgCtx.drawImage(this.canvas, 0, 0)
        }
        let blob = this.getBlob(this.imgCanvas)
        if (
          !this.fileTypes.imgTypes.some(
            (type) => String(this.imgObject.fileName).indexOf(type) !== -1
          )
        ) {
          this.imgObject.fileName =
            this.imgObject.fileName + '.' + this.imgObject.fileType
        }
        let fileOfBlob = new window.File([blob], this.imgObject.fileName, {
          type: this.imgObject.fileType,
        })

        let fileUploadInfo = await this.ossSign(fileOfBlob)
        console.log(
          this.imgObject.fileName,
          this.imgObject,
          'this.imgObject.fileName'
        )

        console.log(this.imgObject.sourcePath)
        let obj = {
          file: {
            name: this.imgObject.fileName,
            size: this.imgObject.fileSize,
            type: this.imgObject.fileType,
          },
          uuid: fileUploadInfo.uuid,
          url: fileUploadInfo.fileList,
          fileUrl:
            'https://tfoneline.oss-cn-hangzhou.aliyuncs.com/' +
            fileUploadInfo.fileList,
        }
        console.log(obj)
        this.$emit('defineEditImg', obj)
        this.getRecordEdit()
        flag = true
      }
    },
    async getRecordEdit() {
      let res = await this.$axios.post(getRecordEdit, this.modifyImgObj)
      if (res.status === 200 && res.data.code === 200) {
        console.log('埋点操作成功')
      }
    },
    async ossSign(list) {
      // 上传oss
      const res = await this.$axios.post(getOssSign, this.ossConfig)
      const promiseArr = []
      // for (let i = 0; i < list.length; i++) {
      const fileName = list.name
      const formatFile = formatUploadFile(list, res.data.data, fileName)
      const resp = this.$axios.post(res.data.data.host, formatFile, {
        headers: { 'Content-Type': 'multipart/form-data' },
        withCredentials: false,
      })
      promiseArr.push(resp)
      // }
      try {
        const fileList = await Promise.all(promiseArr).then((arr) => {
          return `${res.data.data.dir}/${this.imgObject.fileName}`
        })
        console.log(res.data.data.dir, this.imgObject.fileName)
        return { fileList, uuid: res.data.data.dir.split('/')[1] }
      } catch (error) {
        console.log(error)
      }
    },
    getBlob(canvas) {
      //获取blob对象
      var data = canvas.toDataURL('image/png', 1)
      this.picdown = data
      data = data.split(',')[1]
      data = window.atob(data)
      var ia = new Uint8Array(data.length)
      for (var i = 0; i < data.length; i++) {
        ia[i] = data.charCodeAt(i)
      }
      return new Blob([ia], {
        type: 'image/png',
      })
    },
    downloadFile(blob, fileName) {
      let aLink = document.createElement('a')
      let evt = document.createEvent('HTMLEvents')
      evt.initEvent('click', true, true) //initEvent 不加后两个参数在FF下会报错  事件类型,是否冒泡,是否阻止浏览器的默认行为
      aLink.download = fileName
      aLink.href = URL.createObjectURL(blob)
      aLink.click()
    },
    handleEdit() {
      if (this.mode === 'text') {
        this.mode = ''
        this.textBoxRect = null
      } else {
        this.mode = 'text'
      }
    },
    textValueInput(e) {
      this.textValue = e.target.innerText
    },
  },
}
</script>

<style scoped lang="scss">
.canvas_content {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 999;
}
.canvas_back {
  width: 800px;
  background: rgba(51, 51, 51, 1);
}
.edit_content {
  height: 51px;
  background: #333333;
  box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.5);
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  .paint_brush {
    display: flex;
    align-items: center;
    img {
      cursor: pointer;
      margin-right: 10px;
    }
    .ant-select-selection {
      width: 60px;
    }
  }
  .paint_color {
    margin-right: 24px;
  }
}
.canvas {
  position: relative;
  #paintingId {
    position: relative;
    z-index: 1;
  }
  .text-box {
    border: 1px solid #333;
    position: absolute;
    outline: none;
    background-color: transparent;
  }
  #imgCanvas {
    position: absolute;
    top: 0px;
    left: 0px;
    z-index: 0;
  }
}
.tips {
  position: relative;
  background: rgba(253, 204, 119, 0.52);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ffffff;
  img {
    cursor: pointer;
    position: absolute;
    right: 10px;
    top: 2px;
  }
}
.button {
  display: flex;
  align-items: center;
  justify-content: center;
}
.border {
  width: 15px;
  color: #ffffff;
  border: 1px solid #ffffff;
  margin-right: 10px;
}
// .feedback-content {
//   position: relative;
//   .canvas_content {
//     // position: relative;
//     .text-mode {
//       cursor: text;
//     }
// .text-box {
//   // min-width: 100px;
//   // min-height: 20px;
//   border: 1px solid #333;
//   position: absolute;
//   outline: none;
//   background-color: transparent;
// }
//     .bold_font {
//       font-weight: 600;
//     }
//     .no-bold {
//       font-weight: normal;
//     }
//   }
//   .operation_button {
//     height: 100px;
//     input {
//       width: 70px;
//       height: 50px;
//       font-size: 20px;
//     }
//   }
// }
</style>

   
复制代码
文章分类
前端
文章标签