欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue3通過canvas實(shí)現(xiàn)圖片圈選功能

 更新時間:2023年12月08日 09:01:32   作者:閃現(xiàn)上空籃  
這篇文章將給大家詳細(xì)介紹了vue3如何通過canvas實(shí)現(xiàn)圖片圈選功能,文中的示例代碼講解詳細(xì),具有一定的參考價值,感興趣的小伙伴快來跟隨小編一起學(xué)習(xí)一下吧

canvas實(shí)現(xiàn)圈選

具體效果

思路

  • 容器里包裹著一張圖片和一個canvas, 讓其同等大小,在圖片加載完成后獲取到圖片大小再設(shè)置canvas大小。
  • 要能拖動, 需要設(shè)置定位,要實(shí)現(xiàn)繪制,所以canvas要置于圖片上層,通過z-index設(shè)置,兩種功能不能同時實(shí)現(xiàn),需要通過按鈕開啟。
  • 實(shí)現(xiàn)交點(diǎn)處按鈕拖拽重繪,此處的點(diǎn)不能使用canvas繪制,canvas繪制不具備DOM元素?zé)o法添加事件,此處可以通過DOM來繪制交點(diǎn)實(shí)心圓。為實(shí)心圓添加移動等等事件,拖動重繪,此處要注意,拖動重繪的時候不要重繪交點(diǎn),不然會拖動一次后移動事件就會失效。
  • 選中刪除, 通過canvas的isPointInPath方法來進(jìn)行判斷,若是選中點(diǎn)存在繪制圖形擇重繪。
  • 通過監(jiān)聽touchStart是否存在兩個觸摸點(diǎn)來實(shí)現(xiàn)圖片的手勢放大縮小。

思路1

頁面加載完之后,設(shè)置canvas大小,如果存在圈選圖則繪制,同時為容器添加touch事件用于雙指縮小放大。

 nextTick(() => {
    let imgRef = img.value
    let map = 'https://z1.ax1x.com/2023/12/07/pigCCPH.png'
    imgRef.setAttribute('src', map)
    imgRef.onload = () => {
      let height = imgRef.offsetHeight
      let width = imgRef.offsetWidth
      imgHeight.value = height
      let canvasRef = canvas.value
      let imgWrapRef = imgWrap.value

      canvasRef.setAttribute('width', width)
      canvasRef.setAttribute('height', height)

      imgWrapRef.style.width = width + 'px'
      imgWrapRef.style.height = height + 'px'
      canvasObj.value = canvasRef.getContext('2d')
      canvasObj.value.lineWidth = 1
      canvasObj.value.strokeStyle = '#687072'

      //繪制已保存的圖
      drawList()
      reset()

      nextTick(() => {
        zoomInOut()
      })
    }
  })

思路2 & 思路3

根據(jù)標(biāo)識判斷是繪制還是拖動圖片, 拖動的情況下判斷是不是點(diǎn)擊了交點(diǎn),如果是交點(diǎn)就拖動交點(diǎn)重繪,如果不是交點(diǎn)就拖動圖片。如果是繪制則每次點(diǎn)的時候都繪制一個實(shí)心圓并添加相應(yīng)拖動事件,繪制情況下到達(dá)設(shè)置點(diǎn)個數(shù)或者交點(diǎn)位置相近則自動閉合圖形。

// 繪制圓點(diǎn)
function drawCircle(left: number, top: number, color: string) {
  let pointDom = document.createElement('div')
  pointDom.setAttribute('class', 'point')
  let style = `background-color:${color};
                 left:${left}px;
                 top:${top}px;
                 width: 30px;
                 height: 30px;
                 border-radius: 50%;
                 position: absolute;
                 touch-action: none;
                 z-index: 2;
                 transform: translate(-50%, -50%);`
  pointDom.setAttribute('style', style)

  const move = (e: any) => {
    let oldLeft = +pointDom.style.left.slice(0, -2)
    let oldTop = +pointDom.style.top.slice(0, -2)
    let left = oldLeft - (movePoint.value.x - e.pageX)
    let top = oldTop - (movePoint.value.y - e.pageY)

    movePoint.value = {
      x: e.pageX,
      y: e.pageY
    }

    pointDom.style.left = `${left}px`
    pointDom.style.top = `${top}px`

    const setPosition = (list: any) => {
      list.some((item: any) => {
        return item.some((it: any) => {
          let isX = ~~it.x <= ~~oldLeft + 3 && ~~it.x >= ~~oldLeft - 3
          let isY = ~~it.y <= ~~oldTop + 3 && ~~it.y >= ~~oldTop - 3
          if (isX && isY) {
            it.x = left
            it.y = top
            return true
          }
          return false
        })
      })
    }

    setPosition(sweepList.value)
    setPosition(delList.value)

    timer && clearTimeout(timer)
    timer = setTimeout(() => {
      drawList({ point: { x: 0, y: 0 }, resetPoint: false })
    }, 5)
    e.preventDefault()
  }

  pointDom.onpointerdown = (e: any) => {
    movePoint.value = {
      x: e.pageX,
      y: e.pageY
    }
    e.stopPropagation()
    if (openDraw.value) {
      if (pointList.value.length > 2) {
        closeFigure()
      }
      return
    }
    pointDom.addEventListener('pointermove', move)
  }

  pointDom.onpointerup = () => {
    if (!openDraw.value) {
      drawList()
    }
    pointDom.removeEventListener('pointermove', move)
  }

  pointDom.onpointerleave = () => {
    pointDom.removeEventListener('pointermove', move)
  }
  imgWrap.value.appendChild(pointDom)
}

// 繪制圖形
function drawList(params: listType = { point: { x: 0, y: 0 }, resetPoint: true }) {
  if (params.resetPoint) {
    let pointDoms = Array.from(document.getElementsByClassName('point'))
    pointDoms.forEach((item) => {
      imgWrap.value.removeChild(item)
    })
  }

  canvasObj.value.clearRect(0, 0, img.value.offsetWidth, img.value.offsetHeight)
  try {
    sweepList.value.forEach((item, i) => {
      drawPic(item, 'rgba(29,179,219,0.4)')
      if (
        params.point.x != 0 &&
        params.point.y != 0 &&
        canvasObj.value.isPointInPath(params.point.x, params.point.y)
      ) {
        if (!!delList.value.length) {
          sweepList.value.push(delList.value[0])
        }
        delList.value = sweepList.value.splice(i, 1)
        emits('update:list', sweepList.value)
        throw new Error()
      }

      if (params.resetPoint) {
        item.forEach((subItem: Point) => {
          drawCircle(subItem.x, subItem.y, 'rgb(0,180,226)')
        })
      }
    })

    delList.value.forEach((item) => {
      drawPic(item, 'rgba(233,79,79, 0.5)')
      if (
        params.point.x != 0 &&
        params.point.y != 0 &&
        canvasObj.value.isPointInPath(params.point.x, params.point.y)
      ) {
        let temp = { ...item }
        sweepList.value.push(temp)
        delList.value = []
        emits('update:list', sweepList.value)
        throw new Error()
      }

      if (params.resetPoint) {
        item.forEach((subItem: Point) => {
          drawCircle(subItem.x, subItem.y, 'rgb(233,79,79)')
        })
      }
    })
  } catch (e) {
    drawList()
  }
}

function drawPic(item: any, bgColor: string) {
  canvasObj.value.fillStyle = bgColor
  canvasObj.value.beginPath()
  canvasObj.value.moveTo(item[0].x, item[0].y)
  item.forEach((subItem: Point, index: number) => {
    if (index > 0) {
      canvasObj.value.lineTo(subItem.x, subItem.y)
      canvasObj.value.stroke()
    }
  })
  canvasObj.value.closePath()
  canvasObj.value.stroke()
  canvasObj.value.fill()
}

思路4

每次點(diǎn)擊的時候記錄點(diǎn)下的坐標(biāo)點(diǎn),當(dāng)是拖動模式下并且點(diǎn)下與彈起是的坐標(biāo)點(diǎn)相同,則認(rèn)為是選繪制圖形操作,判斷這個坐標(biāo)點(diǎn)是否存在于canvas繪制的圖形上,存在則選中重繪。

   // 記錄當(dāng)前點(diǎn)擊坐標(biāo)
  let pointDown = {
    x: e.offsetX,
    y: e.offsetY
  }
  if (!openDraw.value) {
    curPoint.value = pointDown
  }
  
   // 記錄當(dāng)前點(diǎn)擊坐標(biāo), 用于判斷是否為選中區(qū)域, 用于處理選中刪除
  if (!openDraw.value) {
    if (e.offsetX == curPoint.value.x && e.offsetY == curPoint.value.y) {
      drawList({ point: { x: e.offsetX, y: e.offsetY }, resetPoint: true })
    }
  }
     
  // 判斷傳入坐標(biāo)是否在canvas上
  canvasObj.value.isPointInPath(params.point.x, params.point.y)

思路5

雙指放大和縮小, 記錄第一次按下兩點(diǎn)間的距離,監(jiān)聽移動事件,記錄新的距離,計算兩個距離之間的倍數(shù)關(guān)系, 通過當(dāng)前倍數(shù)做限制,最后通過scale實(shí)現(xiàn)圖片的放大縮小。

// 雙指放大縮小
let initialDistance = 0
const ctTouchStart = (event: any) => {
  if (event.touches.length == 2) {
    let touch1 = event.touches[0]
    let touch2 = event.touches[1]
    initialDistance = Math.sqrt(
      Math.pow(touch1.pageX - touch2.pageX, 2) + Math.pow(touch1.pageY - touch2.pageY, 2)
    )
  }
}

const ctTouchMove = (event: any) => {
  if (event.touches.length == 2) {
    let touch1 = event.touches[0]
    let touch2 = event.touches[1]
    let distance = Math.sqrt(
      Math.pow(touch1.pageX - touch2.pageX, 2) + Math.pow(touch1.pageY - touch2.pageY, 2)
    )
    let scale = distance / initialDistance

    if (currentSize.value * scale >= 5) {
      currentSize.value = 5
    } else if (currentSize.value * scale <= 1) {
      currentSize.value = 1
    } else {
      currentSize.value = currentSize.value * scale
    }
    img.value.style.transform = 'scale(' + currentSize.value + ')'
  }
}

const ctTouchEnd = () => {
  initialDistance = 0
}

function zoomInOut() {
  let ctRef = imgWrap.value
  ctRef.addEventListener('touchstart', ctTouchStart)
  ctRef.addEventListener('touchmove', ctTouchMove)
  ctRef.addEventListener('touchend', ctTouchEnd)
}

function removeZoomInOut() {
  let ctRef = imgWrap.value
  ctRef.removeEventListener('touchstart', ctTouchStart)
  ctRef.removeEventListener('touchmove', ctTouchMove)
  ctRef.removeEventListener('touchend', ctTouchEnd)
}

具體代碼如下

<template>
  <div class="area-conatiner">
    <div class="canvas-wrap" ref="canvasWrap">
      <div
        ref="imgWrap"
        class="modal-img-wrap"
        @pointerdown="mousedown($event)"
        @pointerup="mouseup($event)"
        @pointerleave="mouseup($event)"
      >
        <canvas class="canvas" ref="canvas"></canvas>
        <img ref="img" class="modal-img" />
      </div>
      <div class="action-btn">
        <div class="action-item location" @click="drawArea">
          <img :src="enableImg" alt="" />
        </div>
        <div class="action-item location" v-if="!!delList.length" @click="delArea">
          <img :src="getImage(`area/delete`)" alt="" />
        </div>
      </div>

      <div class="action-btn map-set">
        <div class="action-item location" @click="drawAreaSet('1')">
          <img :src="getImage('area/enlarged')" alt="" />
        </div>
        <div class="action-item location" @click="drawAreaSet('2')">
          <img :src="getImage('area/narrow')" alt="" />
        </div>
        <div class="action-item location" @click="drawAreaSet('3')">
          <img :src="getImage('area/reset')" alt="" />
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, nextTick, onMounted, computed, onBeforeUnmount } from 'vue'
import { ElMessage } from 'element-plus'
import {
  checkPointCross,
  checkPointConcave,
  checkPointClose,
  getImage
} from '@/utils/auxiliaryFunc'

interface Point {
  x: number
  y: number
}

interface listType {
  point: Point
  resetPoint: boolean
}

const imgWrap = ref()
const canvas = ref()
const img = ref()
const canvasWrap = ref()
const mousedownEvent = ref()

//畫圖
let openDraw = ref(false)
let rectList = ref([])
let pointList = ref<Array<Point>>([])
let canvasObj = ref<any>()
let maxPointNum = ref(6)
let minPointNum = ref(3)
let sweepList = ref<Array<Array<Point>>>([])
let delList = ref<Array<Array<Point>>>([])
let imgHeight = ref(0) //圖片高度
let openEnable = ref(false)
let currentSize = ref(1)
let curPoint = ref<Point>({ x: 0, y: 0 })
let movePoint = ref({ x: 0, y: 0 })
let timer: NodeJS.Timeout

const emits = defineEmits<{
  (e: 'update:list', val: Array<Array<Point>>): void
}>()

onMounted(() => {
  initArea()
})

onBeforeUnmount(() => {
  removeZoomInOut()
})

const enableImg = computed(() => {
  let imgUrl = openEnable.value ? 'openEnabled' : 'enabled'
  return getImage(`area/${imgUrl}`)
})

//區(qū)域選擇
function initArea() {
  rectList.value = []
  nextTick(() => {
    let imgRef = img.value
    let map = 'https://z1.ax1x.com/2023/12/07/pigCCPH.png'
    imgRef.setAttribute('src', map)
    imgRef.onload = () => {
      let height = imgRef.offsetHeight
      let width = imgRef.offsetWidth
      imgHeight.value = height
      let canvasRef = canvas.value
      let imgWrapRef = imgWrap.value

      canvasRef.setAttribute('width', width)
      canvasRef.setAttribute('height', height)

      imgWrapRef.style.width = width + 'px'
      imgWrapRef.style.height = height + 'px'
      canvasObj.value = canvasRef.getContext('2d')
      canvasObj.value.lineWidth = 1
      canvasObj.value.strokeStyle = '#687072'

      //繪制已保存的圖
      drawList()
      reset()

      nextTick(() => {
        zoomInOut()
      })
    }
  })
}

let initialDistance = 0
const ctTouchStart = (event: any) => {
  if (event.touches.length == 2) {
    let touch1 = event.touches[0]
    let touch2 = event.touches[1]
    initialDistance = Math.sqrt(
      Math.pow(touch1.pageX - touch2.pageX, 2) + Math.pow(touch1.pageY - touch2.pageY, 2)
    )
  }
}

const ctTouchMove = (event: any) => {
  if (event.touches.length == 2) {
    let touch1 = event.touches[0]
    let touch2 = event.touches[1]
    let distance = Math.sqrt(
      Math.pow(touch1.pageX - touch2.pageX, 2) + Math.pow(touch1.pageY - touch2.pageY, 2)
    )
    let scale = distance / initialDistance

    if (currentSize.value * scale >= 5) {
      currentSize.value = 5
    } else if (currentSize.value * scale <= 1) {
      currentSize.value = 1
    } else {
      currentSize.value = currentSize.value * scale
    }
    img.value.style.transform = 'scale(' + currentSize.value + ')'
  }
}

const ctTouchEnd = () => {
  initialDistance = 0
}

// 雙指放大縮小
function zoomInOut() {
  let ctRef = imgWrap.value
  ctRef.addEventListener('touchstart', ctTouchStart)
  ctRef.addEventListener('touchmove', ctTouchMove)
  ctRef.addEventListener('touchend', ctTouchEnd)
}

function removeZoomInOut() {
  let ctRef = imgWrap.value
  ctRef.removeEventListener('touchstart', ctTouchStart)
  ctRef.removeEventListener('touchmove', ctTouchMove)
  ctRef.removeEventListener('touchend', ctTouchEnd)
}

// 繪制圓點(diǎn)
function drawCircle(left: number, top: number, color: string) {
  let pointDom = document.createElement('div')
  pointDom.setAttribute('class', 'point')
  let style = `background-color:${color};
                 left:${left}px;
                 top:${top}px;
                 width: 30px;
                 height: 30px;
                 border-radius: 50%;
                 position: absolute;
                 touch-action: none;
                 z-index: 2;
                 transform: translate(-50%, -50%);`
  pointDom.setAttribute('style', style)

  const move = (e: any) => {
    let oldLeft = +pointDom.style.left.slice(0, -2)
    let oldTop = +pointDom.style.top.slice(0, -2)
    let left = oldLeft - (movePoint.value.x - e.pageX)
    let top = oldTop - (movePoint.value.y - e.pageY)

    movePoint.value = {
      x: e.pageX,
      y: e.pageY
    }

    pointDom.style.left = `${left}px`
    pointDom.style.top = `${top}px`

    const setPosition = (list: any) => {
      list.some((item: any) => {
        return item.some((it: any) => {
          let isX = ~~it.x <= ~~oldLeft + 3 && ~~it.x >= ~~oldLeft - 3
          let isY = ~~it.y <= ~~oldTop + 3 && ~~it.y >= ~~oldTop - 3
          if (isX && isY) {
            it.x = left
            it.y = top
            return true
          }
          return false
        })
      })
    }

    setPosition(sweepList.value)
    setPosition(delList.value)

    timer && clearTimeout(timer)
    timer = setTimeout(() => {
      drawList({ point: { x: 0, y: 0 }, resetPoint: false })
    }, 5)
    e.preventDefault()
  }

  pointDom.onpointerdown = (e: any) => {
    movePoint.value = {
      x: e.pageX,
      y: e.pageY
    }
    e.stopPropagation()
    if (openDraw.value) {
      if (pointList.value.length > 2) {
        closeFigure()
      }
      return
    }
    pointDom.addEventListener('pointermove', move)
  }

  pointDom.onpointerup = () => {
    if (!openDraw.value) {
      drawList()
    }
    pointDom.removeEventListener('pointermove', move)
  }

  pointDom.onpointerleave = () => {
    pointDom.removeEventListener('pointermove', move)
  }
  imgWrap.value.appendChild(pointDom)
}

function mousedown(e: any) {
  if (e.button === 2) {
    return false
  }

  mousedownEvent.value = e
  // 圖片拖拽
  let imgWrapRef = imgWrap.value
  let pointDown = {
    x: e.offsetX,
    y: e.offsetY
  }

  // 記錄當(dāng)前點(diǎn)擊坐標(biāo)
  if (!openDraw.value) {
    curPoint.value = pointDown
  }

  let x = e.pageX - imgWrapRef.offsetLeft
  let y = e.pageY - imgWrapRef.offsetTop

  let move = (e: any) => {
    let imgWidth = imgWrapRef.offsetWidth * currentSize.value
    let imgHeight = imgWrapRef.offsetHeight * currentSize.value
    let leftWidth = e.pageX - x,
      topWidth = e.pageY - y

    imgWrapRef.style.left = leftWidth + 'px'
    imgWrapRef.style.top = topWidth + 'px'

    // 解決邊界拖出問題
    let canvasWrapWidth = canvasWrap.value.offsetWidth
    let canvasWrapHeight = canvasWrap.value.offsetHeight

    if (imgWidth >= canvasWrapWidth) {
      if (leftWidth >= 0) {
        imgWrapRef.style.left = '0px'
      } else if (leftWidth + imgWidth <= canvasWrapWidth) {
        imgWrapRef.style.left = canvasWrapWidth - imgWidth + 1 + 'px'
      }
    }

    if (imgHeight >= canvasWrapHeight) {
      if (topWidth >= 0) {
        imgWrapRef.style.top = '0px'
      } else if (topWidth + imgHeight <= canvasWrapHeight) {
        imgWrapRef.style.top = canvasWrapHeight - imgHeight + 'px'
      }
    }
  }

  if (openDraw.value) {
    let pointColor = 'rgba(0,180,226)'
    if (pointList.value.length === 0) {
      drawCircle(pointDown.x, pointDown.y, pointColor)
      canvasObj.value.beginPath()
      canvasObj.value.moveTo(pointDown.x, pointDown.y)
    } else {
      const check = checkPointClose(pointDown, pointList.value, minPointNum.value)
      if (check == 'closeFirst') {
        closeFigure()
        return
      }
      if (!check) {
        return
      }

      drawCircle(pointDown.x, pointDown.y, pointColor)
      // 已經(jīng)有點(diǎn)了,連成線
      canvasObj.value.beginPath()
      let lastPoint = pointList.value.slice(-1)[0]
      canvasObj.value.moveTo(lastPoint.x, lastPoint.y)
      canvasObj.value.lineTo(pointDown.x, pointDown.y)
      canvasObj.value.stroke()
    }
    pointList.value.push({
      ...pointDown
    })
    // 如果已經(jīng)到達(dá)最大數(shù)量,則直接閉合圖形
    if (pointList.value.length >= maxPointNum.value) {
      closeFigure()
      return
    }
    e.preventDefault()
  } else {
    //圖片拖拽
    e.preventDefault()
    // 添加指針移動事件
    imgWrapRef.addEventListener('pointermove', move)

    // 添加指針抬起事件,鼠標(biāo)抬起,將事件移除
    imgWrapRef.addEventListener('pointerup', () => {
      imgWrapRef.removeEventListener('pointermove', move)
    })
    // 指針離開父級元素,把事件移除
    imgWrapRef.addEventListener('pointerleave', () => {
      imgWrapRef.removeEventListener('pointermove', move)
    })
  }
}

function mouseup(e: any) {
  // 記錄當(dāng)前點(diǎn)擊坐標(biāo), 用于判斷是否為選中區(qū)域, 用于處理選中刪除
  if (!openDraw.value) {
    if (e.offsetX == curPoint.value.x && e.offsetY == curPoint.value.y) {
      drawList({ point: { x: e.offsetX, y: e.offsetY }, resetPoint: true })
    }
  }
}

// 閉合圖型
function closeFigure() {
  // 檢查部分
  if (!checkPointCross(pointList.value[0], pointList.value)) {
    ElMessage.error('閉合圖形時發(fā)生橫穿線,請重新繪制!')
    clear()
    return
  }
  if (!checkPointConcave(pointList.value[0], pointList.value, true)) {
    ElMessage.error('閉合圖形時出現(xiàn)凹多邊形,請重新繪制!')
    clear()
    return
  }
  if (pointList.value.length >= minPointNum.value) {
    // 符合要求
    canvasObj.value.fillStyle = 'rgba(29,179,219,0.4)'
    for (let i = 0; i < pointList.value.length - 2; i++) {
      canvasObj.value.lineTo(pointList.value[i].x, pointList.value[i].y)
    }
    canvasObj.value.closePath()
    canvasObj.value.stroke()
    canvasObj.value.fill()

    sweepList.value.push(pointList.value)
    emits('update:list', sweepList.value)
    openEnable.value = false

    pointList.value = []
    openDraw.value = false
    canvas.value.style.cursor = 'move'
  } else {
    ElMessage.error('最低繪制3個點(diǎn)!')
  }
}

function clear() {
  drawList()
  openEnable.value = false
  pointList.value = []
  openDraw.value = false
  canvas.value.style.cursor = 'move'
}

function drawArea() {
  if (sweepList.value.length === 5) {
    ElMessage.error('最多選擇5個區(qū)域')
    return false
  }

  if (openEnable.value && pointList.value.length < 3) {
    pointList.value = []
  }
  if (pointList.value.length > 2) {
    closeFigure()
  }
  openEnable.value = !openEnable.value

  if (openEnable.value) {
    openDraw.value = true
    canvas.value.style.cursor = 'crosshair'
  } else {
    openDraw.value = false
    canvas.value.style.cursor = 'move'
    clear()
  }
}

// 繪制單個圖形
function drawPic(item: any, bgColor: string) {
  canvasObj.value.fillStyle = bgColor
  canvasObj.value.beginPath()
  canvasObj.value.moveTo(item[0].x, item[0].y)
  item.forEach((subItem: Point, index: number) => {
    if (index > 0) {
      canvasObj.value.lineTo(subItem.x, subItem.y)
      canvasObj.value.stroke()
    }
  })
  canvasObj.value.closePath()
  canvasObj.value.stroke()
  canvasObj.value.fill()
}

//重新繪制成功的區(qū)域圖
function drawList(params: listType = { point: { x: 0, y: 0 }, resetPoint: true }) {
  if (params.resetPoint) {
    let pointDoms = Array.from(document.getElementsByClassName('point'))
    pointDoms.forEach((item) => {
      imgWrap.value.removeChild(item)
    })
  }

  canvasObj.value.clearRect(0, 0, img.value.offsetWidth, img.value.offsetHeight)
  try {
    sweepList.value.forEach((item, i) => {
      drawPic(item, 'rgba(29,179,219,0.4)')
      if (
        params.point.x != 0 &&
        params.point.y != 0 &&
        canvasObj.value.isPointInPath(params.point.x, params.point.y)
      ) {
        if (!!delList.value.length) {
          sweepList.value.push(delList.value[0])
        }
        delList.value = sweepList.value.splice(i, 1)
        emits('update:list', sweepList.value)
        throw new Error()
      }

      if (params.resetPoint) {
        item.forEach((subItem: Point) => {
          drawCircle(subItem.x, subItem.y, 'rgb(0,180,226)')
        })
      }
    })

    delList.value.forEach((item) => {
      drawPic(item, 'rgba(233,79,79, 0.5)')
      if (
        params.point.x != 0 &&
        params.point.y != 0 &&
        canvasObj.value.isPointInPath(params.point.x, params.point.y)
      ) {
        let temp = { ...item }
        sweepList.value.push(temp)
        delList.value = []
        emits('update:list', sweepList.value)
        throw new Error()
      }

      if (params.resetPoint) {
        item.forEach((subItem: Point) => {
          drawCircle(subItem.x, subItem.y, 'rgb(233,79,79)')
        })
      }
    })
  } catch (e) {
    drawList()
  }
}

// 放大縮小重置
function drawAreaSet(type: string) {
  let imgWrapRef = imgWrap.value
  let left = imgWrapRef.style.left.slice(0, -2) / currentSize.value
  let top = imgWrapRef.style.top.slice(0, -2) / currentSize.value
  if (['1', '2'].includes(type)) {
    if (type == '1') {
      if (currentSize.value == 5) {
        return
      }
      currentSize.value += 0.5
    } else if (type == '2') {
      if (currentSize.value == 1) {
        return
      }
      currentSize.value -= 0.5
    }
    imgWrapRef.style.transformOrigin = `0% 0%`
  } else {
    currentSize.value = 1
  }

  imgWrapRef.style.transform = `scale(${currentSize.value})`
  if (type == '3') {
    reset()
  } else {
    reset(left, top)
  }
}

// 復(fù)位居中
function reset(left: number = 1, top: number = 1) {
  let imgWrapRef = imgWrap.value
  let imgWidth = imgWrapRef.offsetWidth
  let imgHeight = imgWrapRef.offsetHeight
  let canvasWrapWidth = canvasWrap.value.offsetWidth
  let canvasWrapHeight = canvasWrap.value.offsetHeight

  if (left == 1 && top == 1) {
    // 居中
    imgWrapRef.style.left = Math.ceil((canvasWrapWidth - imgWidth) / 2) + 'px'
    imgWrapRef.style.top = Math.ceil((canvasWrapHeight - imgHeight) / 2) + 'px'
  } else {
    // 基于當(dāng)前位置放大縮小
    imgWrapRef.style.left = (left as number) * currentSize.value + 'px'
    imgWrapRef.style.top = (top as number) * currentSize.value + 'px'
  }
}

// 刪除選擇的繪制圖形
function delArea() {
  delList.value = []
  drawList()
}

// 重置畫板
function init() {
  sweepList.value = []
  delList.value = []
  clear()
}

defineExpose({
  init
})
</script>

<style scoped lang="scss">
.area-conatiner {
  padding: 20px;
  .canvas-wrap {
    touch-action: none;
    position: relative;
    width: 900px;
    height: 455px;
    overflow: hidden;
    background-color: #e6ecef;
    .modal-img-wrap {
      touch-action: none;
      position: relative;
      left: 0;
      top: 0;
      .modal-img {
        position: absolute;
        touch-action: none;
        top: 0;
        left: 0;
      }
      .canvas {
        z-index: 2;
        position: absolute;
        touch-action: none;

        top: 0;
        left: 0;
        cursor: move;
      }
    }
    .radio {
      position: absolute;
      bottom: 14px;
      left: 14px;
      display: flex;
      flex-direction: column;
      z-index: 3;
      label {
        margin-top: 12px;
      }
    }
    .action-btn {
      position: absolute;
      z-index: 3;
      left: 10px;
      top: 10px;
      padding: 0 4px;
      .action-item {
        display: flex;
        align-items: center;
        margin-top: 6px;
        padding-bottom: 6px;
        cursor: pointer;
        img {
          height: 40px;
        }
      }
      &.map-set {
        top: auto;
        left: auto;
        right: 10px;
        bottom: 10px;
      }
    }
  }
}
</style>
interface Point {
  x: number
  y: number
}

/**
 * 獲取動態(tài)圖片地址
 * @param {url} string
 * @returns {string}
 */
export const getImage = (url: string) => {
  let path: string = `../assets/images/${url}.png`
  const modules: any = import.meta.globEager('../assets/images/**/**.png')
  return modules[path].default
}

/**
 * 檢查圖形有沒有橫穿
 * @param point
 * @param pointList
 * @returns
 */
export function checkPointCross(point: Point, pointList: Array<Point>) {
  if (pointList.length < 3) {
    return true
  }
  for (let i = 0; i < pointList.length - 2; ++i) {
    const re = isPointCross(pointList[i], pointList[i + 1], pointList[pointList.length - 1], point)
    if (re) {
      return false
    }
  }
  return true
}

/**
 * 檢查是否是凹圖形
 * @param point
 * @param pointList
 * @param isEnd
 * @returns
 */
export function checkPointConcave(point: Point, pointList: Array<Point>, isEnd: boolean) {
  if (pointList.length < 3) {
    return true
  }
  if (
    isPointConcave(
      pointList[pointList.length - 3],
      pointList[pointList.length - 2],
      pointList[pointList.length - 1],
      point
    )
  )
    return false

  // 如果是閉合時,point為起始點(diǎn),需要再判斷最后兩條線與第一條線是否形成凹圖形
  if (isEnd) {
    if (
      isPointConcave(
        pointList[pointList.length - 2],
        pointList[pointList.length - 1],
        pointList[0],
        pointList[1]
      )
    )
      return false
    if (isPointConcave(pointList[pointList.length - 1], pointList[0], pointList[1], pointList[2]))
      return false
  }
  return true
}

/**
 * 檢查點(diǎn)有沒有與當(dāng)前點(diǎn)位置太近,如果太近就不認(rèn)為是一個點(diǎn)
 * @param point
 * @param pointList
 * @param minPointNum
 * @returns
 */
export function checkPointClose(point: Point, pointList: Array<Point>, minPointNum: number) {
  for (let i = 0; i < pointList.length; ++i) {
    const distance = Math.sqrt(
      Math.abs(pointList[i].x - point.x) + Math.abs(pointList[i].y - point.y)
    )
    if (distance > 6) {
      continue
    }
    // 如果是在第一個點(diǎn)附近點(diǎn)的,那就認(rèn)為是在嘗試閉合圖形
    if (pointList.length >= minPointNum && i === 0) {
      return 'closeFirst'
    }
    return false
  }
  return true
}

/**
 * 輔助函數(shù) 檢查兩個線是否交叉
 * @param line1P1
 * @param line1P2
 * @param line2P1
 * @param line2P2
 * @returns
 */
export function isPointCross(line1P1: Point, line1P2: Point, line2P1: Point, line2P2: Point) {
  const euqal =
    isEuqalPoint(line1P1, line2P1) ||
    isEuqalPoint(line1P1, line2P2) ||
    isEuqalPoint(line1P2, line2P1) ||
    isEuqalPoint(line1P2, line2P2)
  const re1 = isDirection(line1P1, line1P2, line2P1)
  const re2 = isDirection(line1P1, line1P2, line2P2)
  const re3 = isDirection(line2P1, line2P2, line1P1)
  const re4 = isDirection(line2P1, line2P2, line1P2)
  const re11 = re1 * re2
  const re22 = re3 * re4
  if (re11 < 0 && re22 < 0) return true
  if (euqal) {
    if (re1 === 0 && re2 === 0 && re3 === 0 && re4 === 0) return true
  } else {
    if (re11 * re22 === 0) return true
  }
  return false
}

/**
 * 輔助函數(shù) 檢查三個線是否凹凸
 * @param point1
 * @param point2
 * @param point3
 * @param point4
 * @returns
 */
export function isPointConcave(point1: Point, point2: Point, point3: Point, point4: Point) {
  const re1 = isDirection(point1, point2, point3)
  const re2 = isDirection(point2, point3, point4)
  if (re1 * re2 <= 0) return true
  return false
}

/**
 * 輔助函數(shù) 判斷兩個點(diǎn)是否是同一個
 * @param point1
 * @param point2
 * @returns
 */
export function isEuqalPoint(point1: Point, point2: Point) {
  if (point1.x == point2.x && point1.y == point2.y) {
    return true
  }
}

/**
 * 輔助函數(shù) 檢查第二條線的方向在第一條線的左還是右
 * @param point1
 * @param point2
 * @param point3
 * @returns
 */
export function isDirection(point1: Point, point2: Point, point3: Point) {
  // 假設(shè)point1是原點(diǎn)
  const p1 = getPointLine(point1, point2)
  const p2 = getPointLine(point1, point3)
  return crossLine(p1, p2)
}

/**
 * 輔助函數(shù) 獲取以point1作為原點(diǎn)的線
 * @param point1
 * @param point2
 * @returns
 */
export function getPointLine(point1: Point, point2: Point) {
  const p1 = {
    x: point2.x - point1.x,
    y: point2.y - point1.y
  }
  return p1
}

/**
 * 輔助函數(shù) 兩線叉乘 兩線的起點(diǎn)必須一致
 * @param point1
 * @param point2
 * @returns
 */
export function crossLine(point1: Point, point2: Point) {
  return point1.x * point2.y - point2.x * point1.y
}

以上就是vue3通過canvas實(shí)現(xiàn)圖片圈選功能的詳細(xì)內(nèi)容,更多關(guān)于vue3 canvas圖片圈選的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue3.x中emits的基本用法實(shí)例

    vue3.x中emits的基本用法實(shí)例

    emits是Vue3新增的選項(xiàng),emits主要作用在子組件中,用于接收在父組件中綁定的方法,這篇文章主要給大家介紹了關(guān)于vue3.x中emits的基本用法,需要的朋友可以參考下
    2022-07-07
  • vue使用codemirror的兩種用法

    vue使用codemirror的兩種用法

    這篇文章主要介紹了在vue里使用codemirror的兩種用法,每種方法通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-08-08
  • Vue中ElementUI分頁組件Pagination的使用方法

    Vue中ElementUI分頁組件Pagination的使用方法

    這篇文章主要為大家詳細(xì)介紹了Vue中ElementUI分頁組件Pagination的使用,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • Vue3實(shí)現(xiàn)vueFLow流程組件的詳細(xì)指南

    Vue3實(shí)現(xiàn)vueFLow流程組件的詳細(xì)指南

    VueFlow是一個專門為Vue.js框架設(shè)計的交互式可視化庫,它允許開發(fā)者輕松創(chuàng)建和管理復(fù)雜的圖形模型,如流程圖、狀態(tài)機(jī)、組織結(jié)構(gòu)圖等,本文給大家介紹了Vue3實(shí)現(xiàn)vueFLow流程組件的詳細(xì)指南,需要的朋友可以參考下
    2024-11-11
  • 關(guān)于Vue.nextTick()的正確使用方法淺析

    關(guān)于Vue.nextTick()的正確使用方法淺析

    最近在項(xiàng)目中遇到了一個需求,我們通過Vue.nextTick()來解決這一需求,但發(fā)現(xiàn)網(wǎng)上這方面的資料較少,所以自己來總結(jié)下,下面這篇文章主要給大家介紹了關(guān)于Vue.nextTick()正確使用方法的相關(guān)資料,需要的朋友可以參考下。
    2017-08-08
  • vue3使用富文本編輯器Editor.js的簡單方法

    vue3使用富文本編輯器Editor.js的簡單方法

    Editor.js是一個用于構(gòu)建具有完全可定制化塊結(jié)構(gòu)的現(xiàn)代編輯器的開源庫,它提供了一個簡潔、可擴(kuò)展和易于使用的接口,使開發(fā)人員能夠創(chuàng)建擁有豐富內(nèi)容和互動性的編輯器,這篇文章主要給大家介紹了關(guān)于vue3使用富文本編輯器Editor.js的簡單方法,需要的朋友可以參考下
    2024-04-04
  • 如何啟動一個Vue.js項(xiàng)目

    如何啟動一個Vue.js項(xiàng)目

    這篇文章主要介紹了如何啟動一個Vue.js項(xiàng)目,對Vue.js感興趣的同學(xué),可以參考下
    2021-04-04
  • vue 如何使用vue-cropper裁剪圖片你知道嗎

    vue 如何使用vue-cropper裁剪圖片你知道嗎

    這篇文章主要為大家介紹了vue 使用vue-cropper裁剪圖片,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-11-11
  • VUE 文字轉(zhuǎn)語音播放的實(shí)現(xiàn)示例

    VUE 文字轉(zhuǎn)語音播放的實(shí)現(xiàn)示例

    本文主要介紹了VUE 文字轉(zhuǎn)語音播放的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • vue項(xiàng)目打包后部署到服務(wù)器的詳細(xì)步驟

    vue項(xiàng)目打包后部署到服務(wù)器的詳細(xì)步驟

    這篇文章主要介紹了vue項(xiàng)目打包后部署到服務(wù)器,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-09-09

最新評論