>웹 프론트엔드 >H5 튜토리얼 >Canvas는 그림 낙서 기능을 구현합니다(코드 포함).

Canvas는 그림 낙서 기능을 구현합니다(코드 포함).

不言
不言앞으로
2018-11-16 17:17:585810검색

이 글의 내용은 캔버스에 이미지 그래피티 기능을 구현하는 내용입니다(코드 포함). 도움이 필요한 친구들이 참고하시면 좋겠습니다.

Requirements

  1. 사진에 주석을 달고 내보내야 합니다.

  2. N개의 여러 장의 사진을 표시하고 최종적으로 동시에 저장해야 합니다.

  3. 은 다각형 영역 데이터(면적, 색상, 이름)에 따라 라벨을 지정해야 합니다.

해당 솔루션

  1. 캔버스를 사용하여 낙서, 원, 직사각형을 그리고 최종적으로 이미지 생성 base64 업로드는 코딩을 사용합니다

  2. 많은 수의 사진을 일괄 업로드하는 것은 시간이 많이 소요됩니다. 사용자 경험을 향상시키기 위해 원만 그리도록 변경되었습니다. 그리고 직사각형을 그리고 최종적으로 좌표로 저장합니다. 다음번에 표시되면 좌표를 기준으로 그려집니다.

  3. 폴리곤 영역의 표시는 좌표점을 기준으로 그려지며, 이름이 표시되는 위치는 폴리곤 중심입니다.

Code

<template>
  <div>
    <canvas
      :id="radom"
      :class="{canDraw: &#39;canvas&#39;}"
      :width="width"
      :height="height"
      :style="{&#39;width&#39;:`${width}px`,&#39;height&#39;:`${height}px`}"
      @mousedown="canvasDown($event)"
      @mouseup="canvasUp($event)"
      @mousemove="canvasMove($event)"
      @touchstart="canvasDown($event)"
      @touchend="canvasUp($event)"
      @touchmove="canvasMove($event)">
    </canvas>
  </div>
</template>
<script>
  // import proxy from './proxy.js'
  const uuid = require('node-uuid')
  export default {
    props: {
      canDraw: { // 图片路径
        type: Boolean,
        default: true
      },
      url: { // 图片路径
        type: String
      },
      info: { // 位置点信息
        type: Array
      },
      width: { // 绘图区域宽度
        type: String
      },
      height: { // 绘图区域高度
        type: String
      },
      lineColor: { // 画笔颜色
        type: String,
        default: 'red'
      },
      lineWidth: { // 画笔宽度
        type: Number,
        default: 2
      },
      lineType: { // 画笔类型
        type: String,
        default: 'circle'
      }
    },
    watch: {
      info (val) {
        if (val) {
          this.initDraw()
        }
      }
    },
    data () {
      return {
        // 同一页面多次渲染时,用于区分元素的id
        radom: uuid.v4(),
        // canvas对象
        context: {},
        // 是否处于绘制状态
        canvasMoveUse: false,
        // 绘制矩形和椭圆时用来保存起始点信息
        beginRec: {
          x: '',
          y: '',
          imageData: ''
        },
        // 储存坐标信息
        drawInfo: [],
        // 背景图片缓存
        img: new Image()
      }
    },
    mounted () {
      this.initDraw()
    },
    methods: {
      // 初始化绘制信息
      initDraw () {
        // 初始化画布
        const canvas = document.getElementById(this.radom)
        this.context = canvas.getContext('2d')
        // 初始化背景图片
        this.img.setAttribute('crossOrigin', 'Anonymous')
        this.img.src = this.url
        this.img.onerror = () => {
          var timeStamp = +new Date()
          this.img.src = this.url + '?' + timeStamp
        }
        this.img.onload = () => {
          this.clean()
        }
        // proxy.getBase64({imgUrl: this.url}).then((res) => {
        //   if (res.code * 1 === 0) {
        //     this.img.src = 'data:image/jpeg;base64,'+res.data
        //     this.img.onload = () => {
        //       this.clean()
        //     }
        //   }
        // })
        // 初始化画笔
        this.context.lineWidth = this.lineWidth
        this.context.strokeStyle = this.lineColor
      },
      // 鼠标按下
      canvasDown (e) {
        if (this.canDraw) {
          this.canvasMoveUse = true
          // client是基于整个页面的坐标,offset是cavas距离pictureDetail顶部以及左边的距离
          const canvasX = e.clientX - e.target.parentNode.offsetLeft
          const canvasY = e.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
          })
        }
      },
      Area (p0,p1,p2) {
        let area = 0.0 ;
        area = p0.x * p1.y + p1.x * p2.y + p2.x * p0.y - p1.x * p0.y - p2.x * p1.y - p0.x * p2.y;
        return area / 2 ;
      },
      // 计算多边形质心
      getPolygonAreaCenter (points) {
        let sum_x = 0;
        let sum_y = 0;
        let sum_area = 0;
        let p1 = points[1];
        for (var i = 2; i < points.length; i++) {
          let p2 = points[i];
          let area = this.Area(points[0],p1,p2) ;
          sum_area += area ;
          sum_x += (points[0].x + p1.x + p2.x) * area;
          sum_y += (points[0].y + p1.y + p2.y) * area;
          p1 = p2 ;
        }
        return {
          x: sum_x / sum_area / 3,
          y: sum_y / sum_area / 3
        }
      },
      // 根据坐标信息绘制图形
      drawWithInfo () {
        this.info.forEach(item => {
          this.context.beginPath()
          if (!item.type) {
            // 设置颜色
            this.context.strokeStyle = item.regionColor
            this.context.fillStyle = item.regionColor
            // 绘制多边形的边
            if (typeof item.region === 'string') {
              item.region = JSON.parse(item.region)
            }
            item.region.forEach(point => {
              this.context.lineTo(point.x * this.width, point.y * this.height)
            })
            this.context.closePath()
            // 在多边形质心标注文字
            let point = this.getPolygonAreaCenter(item.region)
            this.context.fillText(item.areaName, point.x * this.width, point.y * this.height)
          } else if (item.type === 'rec') {
            this.context.rect(item.x * this.width, item.y * this.height, item.w * this.width, item.h * this.height)
          } else if (item.type === 'circle') {
            this.drawEllipse(this.context, (item.x + item.a) * this.width, (item.y + item.b) * this.height, item.a > 0 ? item.a * this.width : -item.a * this.width, item.b > 0 ? item.b * this.height : -item.b * this.height)
          }
          this.context.stroke()
        })
      },
      // 鼠标移动时绘制
      canvasMove (e) {
        if (this.canvasMoveUse && this.canDraw) {
          // client是基于整个页面的坐标,offset是cavas距离pictureDetail顶部以及左边的距离
          let canvasX = e.clientX - e.target.parentNode.offsetLeft
          let canvasY = e.clientY - e.target.parentNode.offsetTop
          if (this.lineType === 'rec') { // 绘制矩形时恢复起始点状态再重新绘制
            this.context.putImageData(this.beginRec.imageData, 0, 0)
            this.context.beginPath()
            this.context.rect(this.beginRec.x, this.beginRec.y, canvasX - this.beginRec.x, canvasY - this.beginRec.y)
            let info = this.drawInfo[this.drawInfo.length - 1]
            info.w = canvasX / this.width - info.x
            info.h = canvasY / this.height - info.y
          } else if (this.lineType === 'circle') { // 绘制椭圆时恢复起始点状态再重新绘制
            this.context.putImageData(this.beginRec.imageData, 0, 0)
            this.context.beginPath()
            let a = (canvasX - this.beginRec.x) / 2
            let b = (canvasY - this.beginRec.y) / 2
            this.drawEllipse(this.context, this.beginRec.x + a, this.beginRec.y + b, a > 0 ? a : -a, b > 0 ? b : -b)
            let info = this.drawInfo[this.drawInfo.length - 1]
            info.a = a / this.width
            info.b = b / this.height
          }
          this.context.stroke()
        }
      },
      // 绘制椭圆
      drawEllipse (context, x, y, a, b) {
        context.save()
        var r = (a > b) ? a : b
        var ratioX = a / r
        var ratioY = b / r
        context.scale(ratioX, ratioY)
        context.beginPath()
        context.arc(x / ratioX, y / ratioY, r, 0, 2 * Math.PI, false)
        context.closePath()
        context.restore()
      },
      // 鼠标抬起
      canvasUp (e) {
        if (this.canDraw) {
          this.canvasMoveUse = false
        }
      },
      // 获取坐标信息
      getInfo () {
        return this.drawInfo
      },
      // 清空画布
      clean () {
        this.context.drawImage(this.img, 0, 0, this.width, this.height)
        this.drawInfo = []
        if (this.info && this.info.length !== 0) this.drawWithInfo()
      }
    }
  }
</script>
<style scoped>
  .canvas{
    cursor: crosshair;
  }
</style>

매개변수로 전달해야 함

    #🎜🎜 #
  • 그림 경로

url: string
  • 도면 영역 너비

width: string
#🎜 🎜#
    도면 영역 높이
  • height: string
전달된 매개변수 선택

#🎜🎜 #

그릴 수 있는지 여부는 기본값이 true입니다
  • canDraw: boolean
좌표점 정보는 표시되지 않습니다. 전달되지 않으면 그려집니다
  • info: string
드로어블인지 여부에 관계없이 기본값은 true입니다
  • canDraw: boolean
    #🎜 🎜#
  • 그리기 색상, 기본 빨간색
  • lineColor: string

  • 그리기 펜 너비 , 기본 2
  • lineWidth: number

  • 드로잉 펜 유형, Rec, Circle, 기본 Rec
  • lineType: string

    메소드 호출 가능

캔버스 지우기

  • clean()
    #🎜 🎜#

    좌표 정보 반환
    getInfo()
  • 특별 지침

  • # 🎜🎜#

    canvas 객체를 얻을 수 없습니다. 좌표는 상위 요소의 좌표를 통해 얻어지므로 구성 요소의 상위 요소 위 수준에서 너무 많은 위치 지정이나 중첩이 있어서는 안 됩니다. 그렇지 않으면 그리기 좌표가 오프셋.

      도메인 이름이 다른 이미지에는 도메인 간 문제가 있을 수 있습니다. 많은 정보를 읽었지만 최종 프로젝트에서는 다음을 사용했습니다. 이미지를 base64로 변환하는 노드 서비스입니다. 인터페이스는 캔버스에 그려서 해결됩니다. 다른 프로젝트에는 적용되지 않을 수 있습니다. 더 나은 솔루션이 있으면 공유해 주세요.
    • 좌표점 데이터 내보내기는 일반 패턴의 좌표점만 내보낼 수 있습니다. 무작위로 낙서하면 너무 많은 좌표점이 무너지기 때문입니다(어느 정도 시도하지는 않았지만). 발생합니다) 충돌), 고성능 구현이 있으면 공유해 주세요.
    • 그래피티를 저장한 후 이미지 url을 요청했는데도 요청이 안되는 경우는 CDN 캐싱 문제로, 임의의 철자를 써서 해결할 수 있습니다. 이미지 경로 뒤의 코드.

    위 내용은 Canvas는 그림 낙서 기능을 구현합니다(코드 포함).의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    성명:
    이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제