小程序之坑 - 可移动/缩放canvas及如何判断canvas点击区域是否在标记的圆圈内

项目:taro3+vue3

描述:一张停车场地图,图上标记空车位,点击空车位能够导航到此位置,可以移动,缩放

拆解:
A、移动
B、缩放 -> 手势缩放和按钮缩放
C、标记空车位
D、点击,计算是否在空车位圆圈内

效果图
小程序之坑 - 可移动/缩放canvas及如何判断canvas点击区域是否在标记的圆圈内_第1张图片
组件结构

<base-move-layout> -移动组件
	<reverse-canvas></reverse-canvas> -画布组件
</base-move-layout>
<reverse-car-scale></reverse-car-scale> - 缩放组件

移动

结合movable-area/movable-view

缩放

手势缩放:movable-view的scale事件

标记空车位

/**
 * 绘制空车位
 * @param ctx
 * @param list 空车位列表
 */
export function useDrawEmptyBerth(ctx, list) {
  if (!list.length) {
    return
  }
  for (let i = 0; i < list.length; i++) {
    const item = list[i]
    const { x, y } = item
    ctx.beginPath()
    ctx.strokeStyle = '#FF028C'
    ctx.lineWidth = 3
    ctx.arc(x, y, EMPTY_CIRCLE_R, 0, 2 * Math.PI)
    ctx.stroke()
  }
}

点击,判断是否在标记点上

点击的点到圆心的距离是否大于半径,是:圈外,否:圈上

const dis = Math.sqrt((x - a) * (x - a) + (y - b) * (y - b))
if (dis <= circleR) {
  // 在圈上
}

遇到的坑

A、拖动(change事件)和手势缩放(scale事件)移动层会触发画布的点击事件touchstart

解决:
移动组件:
设置2个开关isDragging, isScaling
小程序之坑 - 可移动/缩放canvas及如何判断canvas点击区域是否在标记的圆圈内_第2张图片

B、点击缩放按钮时会触发移动层的scale事件

缩放组件中,点击按钮的时候也设置一个开关,移动组件根据这个开关去scale
在这里插入图片描述
注意:
① 缩放组件和移动组件没有层级关系,所以用trigger
② 缩放后,点击复原按钮,需要重置 movable-view 的x, y, 重置时 直接赋值正确的值,并不能复原,需要 x = 值 - 1,再x = 值

C、移动层手势缩放后,点击空车位,计算是否在圆圈内的差值没有缩放

在这里插入图片描述

详细代码

移动组件

<movable-area
 id="movableArea"
 class="movable-area"
 scale-area>
 <movable-view
   :style="`width: ${width}px; height: ${height}px;`"
   class="movable-view"
   :x="movableViewX"
   :y="movableViewY"
   :is-dragging="isDragging"
   :damping="50"
   :out-of-bounds="true"
   direction="all"
   :scale="true"
   :scale-value="scaleValue"
   :scale-min="SCALE_MIN"
   :scale-max="SCALE_MAX"
   @change="onChangeMovable"
   @scale="onScaleMovable">

   <!--用于观察当前实际位置-->
   <view
     class="reverse-car-now"
     :style="nowStyle"></view>

   <slot :info="{isDragging, isScaling, tempScale}"></slot>
 </movable-view>
</movable-area>
events.on(REVERSE_PAGE_EVENTS.CLICK_SCALE_BTN, async type => {
  this.isClickScaleBtn = true
  if (type === SCALE_TYPE.DEFAULT) {
    const x = this.movableViewX.value
    const y = this.movableViewY.value
    this.movableViewX.value = x - 1
    this.movableViewY.value = y - 1
    await sleep(100)
    this.movableViewX.value = x
    this.movableViewY.value = y
  }
  setTimeout(() => {
    this.isClickScaleBtn = false
  }, 1000)
})

/**
 * 拖动
 */
onChangeMovable() {
  console.log('拖动')
  this.isDragging.value = true
  setTimeout(() => {
    this.isDragging.value = false
  }, 1000)
}

/**
 * 手势放大缩小
 * @param e
 */
onScaleMovable(e) {
  if (this.isClickScaleBtn) {
    return
  }
  const { scale } = e.detail
  this.isScaling = true
  this.tempScale.value = scale
  setTimeout(() => {
    this.isScaling = false
  }, 50)
}

canvas组件

<canvas
	type="2d"
	id="myCanvas"
	:style="`width: ${width}px; height: ${height}px`"
	canvas-id="myCanvas"
	@touchstart="onTouchStart"></canvas>
/**
* 点击空车位
 * @param event
 */
onTouchStart(event) {
  const scale = this.scaleValue
  const { x, y } = event.touches[0]
  const circleR = EMPTY_CIRCLE_R  // 半径
  setTimeout(() => {
    if (this.isDrag || this.isScale) {
      return
    }
    ....
    const { x: a, y: b } = it // 标记点
    const dis = Math.sqrt((x / scale - a) * (x / scale - a) + (y / scale - b) * (y / scale - b))
    if (dis <= circleR) {
      this.context.emit('getEmpty', it)
      break
    }
  }, 500)
}
<base-move-layout
  :width="canvasW"
  :height="canvasH"
  :scale="scale"
  :nowStyle="nowStyle">
  <template v-slot="slotProps">
    <base-canvas
      v-bind="$attrs"
      :is-dragging="slotProps.info.isDragging"
      :is-scaling="slotProps.info.isScaling"
      :temp-scale="slotProps.info.tempScale"
      :is-end="isEnd"
      :img="mapImageInfo.mapUrl"
      :width="canvasW"
      :height="canvasH"
      :path-list="pathList"
      :empty-list="emptyBerthList"
      @getEmpty="catchGetEmpty"></base-canvas>
  </template>
</base-move-layout>

缩放组件

<view class="reverse-car-scale f-l-c">
 <viewclass="scale-btn">
   <view class="scale-item scale-item-s" @tap="onScale(SCALE_TYPE.SMALL)"></view>
   <view class="scale-item scale-item-b" @tap="onScale(SCALE_TYPE.BIG)">+</view>
 </view>
 <view class="center-btn" @tap="onScale(SCALE_TYPE.DEFAULT)">
   <image class="img" :src="IconMapLocation"></image>
 </view>
</view>
const onScale = async(type) => {
  const val = props.modelValue
  if (type === SCALE_TYPE.SMALL && val > 1) {
    scaleValue.value = val - 1
  } else if (type === SCALE_TYPE.BIG && val < 3) {
    scaleValue.value = val + 1
  } else {
    scaleValue.value = 1
  }
  context.emit('update:modelValue', scaleValue.value)
  events.trigger(REVERSE_PAGE_EVENTS.CLICK_SCALE_BTN, type)
}

你可能感兴趣的:(#,Taro,小程序,小程序,canvas)