自定义地图demo

分两层: 操作层与显示层
操作层监听用户各种事件,显示层只管将操作得来的数据进行显示。 等待明天优化拖曳的边界情况。

修改了偏移量的生成,原本只考虑了向左上偏移,top\left都是负数,方便计算就都给正数了,现改成按照top、left真实值来计算。 添加了拖曳功能,支持放大以后再拖曳。

vue文件

<template>
  <div class="map-image">
    <div class="map-image-container">
      <div class="map-image-box" :style="imageStyle">
        <img
          class="marker"
          src="https://3gimg.qq.com/lightmap/api_v2/2/4/122/theme/default/imgs/marker.png"
          :style="markerStyle"
        />
      </div>
    </div>
    <div
      class="map-image-wheel"
      :style="wheelBoxStyle"
      @wheel="setZoom"
      @mousedown="handlerStart"
      @mousemove="handlerMouseMove"
      @mouseup="handlerEnd"
      @mouseleave="handlerMouseleave"
    ></div>
    <!-- 由于 drop事件 导致操作出错 所以遇到就强行停止位移-->

    <!-- @click="setImageXY"  @drop="handlerEnd" -->
  </div>
</template>

<script>
import { getBaseUrl } from "@/libs/util"

export default {
  name: "map_image",
  components: {},
  props: {
    image_x: 0,
    image_y: 0,
    disabled: false
  },
  data() {
    return {
      baseUrl: getBaseUrl(),
      imageWidth: 60, // 图片宽度
      imageHeight: 60, // 图片宽度
      imageTop: 0, // 图片top偏移
      imageLeft: 0, // 图片left偏移
      startZoom: 10, // 保存初始放大比例
      maxZoom: 30, // 最大放大比例
      zoom: 10, // 图片放大比例 用10 而不是1 是为了避免浮点数精度丢失
      mouseWheelX: 0, // 滚轮触发事件时 鼠标在框内的位置 x
      mouseWheelY: 0, // 滚轮触发事件时 鼠标在框内的位置 y
      mouseType: "+", // +  - 本次操作是放大还是缩小
      isClick: false, // 是否是点击事件 触发的mousemove
      mousemove: {
        x: null,
        y: null
      }, // 每次拖曳记住上一次的值
      clickTimeStamp: 0 // 本次点击的开始时间 如果时间小于.5s则判定为点击 ,大于则是拖拽地图
    }
  },
  watch: {},
  computed: {
    // 图片的偏移量
    imageStyle() {
      return {
        height: `${this.nowImageWidth}px`,
        width: `${this.nowImageHeigth}px`,
        top: `${this.imageTop}px`,
        left: `${this.imageLeft}px`,
        backgroundImage: `url('${this.baseUrl}/applets_images/map/small_map.png')`
      }
    },
    // marker的偏移量
    markerStyle() {
      // setImageXY函数反推可知
      // this.image_y * nowImageHeigth === this.imageLeft + mouseClickX
      return {
        top: `${this.image_y * this.nowImageHeigth}px`,
        left: `${this.image_x * this.nowImageWidth}px`
      }
    },
    wheelBoxStyle() {
      return {
        height: `${this.startImageAndWheelWidth}px`,
        width: `${this.startImageAndWheelHeigth}px`
      }
    },
    nowImageWidth() {
      return this.imageWidth * this.zoom
    },
    nowImageHeigth() {
      return this.imageHeight * this.zoom
    },
    startImageAndWheelWidth() {
      return this.imageWidth * this.startZoom
    },
    startImageAndWheelHeigth() {
      return this.imageHeight * this.startZoom
    }
  },
  methods: {
    // 鼠标点击事件开始
    handlerStart(e) {
      // console.log(e)
      this.isClick = true
      this.clickTimestamp = e.timeStamp
    },
    // 鼠标移动事件
    handlerMouseMove(e) {
      if (this.isClick) {
        // console.log(e)
        const x = e.offsetX || e.layerX
        const y = e.offsetY || e.layerY

        // 上一次mousemove的点存在
        if (
          (this.mousemove.x || this.mousemove.x === 0) &&
          (this.mousemove.y || this.mousemove.y === 0)
        ) {
          const mousemoveX = x - this.mousemove.x
          const mousemoveY = y - this.mousemove.y
          const imageLeft = this.imageLeft + mousemoveX
          const imageTop = this.imageTop + mousemoveY

          // 边界情况处理
          if (mousemoveX > 0 && imageLeft <= 0) {
            // 向右划
            this.imageLeft = imageLeft
          } else if (
            mousemoveX < 0 &&
            this.nowImageWidth + imageLeft >= this.startImageAndWheelWidth
          ) {
            // 向左划
            this.imageLeft = imageLeft
          }

          if (mousemoveY > 0 && imageTop <= 0) {
            // 向下滑
            this.imageTop = imageTop
          } else if (
            mousemoveY < 0 &&
            this.nowImageHeigth + imageTop >= this.startImageAndWheelHeigth
          ) {
            // 向上划
            this.imageTop = imageTop
          }
        }

        this.$nextTick(() => {
          this.mousemove = {
            x,
            y
          }
        })
      }
    },
    // 鼠标点击事件结束
    handlerEnd(e) {
      this.isClick = false
      this.mousemove = {
        x: null,
        y: null
      }
      if (e.timeStamp - this.clickTimestamp < 300) {
        this.setImageXY(e)
      }
      // 鼠标可能会在移动中选中文本 导致下一次进去移动会变成拖拽东西 报错
      window.getSelection().removeAllRanges()
    },
    // 鼠标移出触发区域会触发该函数
    handlerMouseleave(e) {
      this.isClick = false
      this.mousemove = {
        x: null,
        y: null
      }
      // 鼠标可能会在移动中选中文本 导致下一次进去移动会变成拖拽东西 报错
      window.getSelection().removeAllRanges()
    },

    // 下面三个计算偏移量的原理是

    // wheel 事件
    // 例如 top
    // 当前鼠标相对与当前放大后图片顶部的y = (本次鼠标的y - 上一次的偏移量  )/(上一次的地图的放大倍数) * 本次放大倍数
    // 当前偏移量 = 本次鼠标的y - 鼠标相对与放大后图片顶部的y

    // click
    // 这个事件提交的数据要存储在数据库所以要用百分数,不管地图大小都能找到位置
    // 例如top
    // 当前鼠标相对与当前放大后图片顶部的y = 当前的偏移量 + 本次鼠标的y

    // 点击标点事件
    setImageXY(e) {
      if (this.disabled) return
      // console.log(e)
      const mouseClickX = e.offsetX || e.layerX
      const mouseClickY = e.offsetY || e.layerY
      // const image_x = (( mouseClickX - this.imageLeft ) / this.zoom) * 10
      // const image_y = (( mouseClickY - this.imageTop ) / this.zoom) * 10
      const image_x = (mouseClickX - this.imageLeft) / this.zoom
      const image_y = (mouseClickY - this.imageTop) / this.zoom
      // 最终要存的数据是百分数 这里把 *10  /10 约分,注释是为了加强阅读 这个10 实际上是指没有放大时的zoom 所以这个参数不因该放到通用的代码里
      this.$emit("clickImage", {
        value: {
          // image_x: image_x / (this.imageWidth * 10),
          // image_y: image_y / (this.imageHeight * 10)
          image_x: image_x / this.imageWidth,
          image_y: image_y / this.imageHeight
        }
      })
    },
    // 设置背景图片的偏移量 top
    setImageTop() {
      if (this.mouseType === "+") {
        this.imageTop =
          this.mouseWheelY -
          ((this.mouseWheelY - this.imageTop) / (this.zoom - 2)) * this.zoom
      } else {
        if (this.zoom === 10) {
          this.imageTop = 0
        } else {
          this.imageTop =
            this.mouseWheelY -
            ((this.mouseWheelY - this.imageTop) / (this.zoom + 2)) * this.zoom
        }
      }
    },
    // 设置背景图片的偏移量 left
    setImageLeft() {
      if (this.mouseType === "+") {
        this.imageLeft =
          this.mouseWheelX -
          ((this.mouseWheelX - this.imageLeft) / (this.zoom - 2)) * this.zoom
      } else {
        if (this.zoom === 10) {
          this.imageLeft = 0
        } else {
          this.imageLeft =
            this.mouseWheelX -
            ((this.mouseWheelX - this.imageLeft) / (this.zoom + 2)) * this.zoom
        }
      }
    },
    // 设置放大缩小zoom
    setZoom(e) {
      // console.log(e)
      e.preventDefault()
      if (e.wheelDelta < 0) {
        // 缩小
        if (this.zoom > this.startZoom) {
          this.mouseType = "-"
          this.zoom -= 2
          this.$nextTick(() => {
            // wheel 事件  鼠标所在地X
            this.mouseWheelX = e.offsetX || e.layerX
            // wheel 事件  鼠标所在地Y
            this.mouseWheelY = e.offsetY || e.layerY
            this.setImageTop()
            this.setImageLeft()
          })
        }
      } else if (e.wheelDelta > 0) {
        // 放大
        if (this.zoom < this.maxZoom) {
          this.mouseType = "+"
          this.zoom += 2
          this.$nextTick(() => {
            // wheel 事件  鼠标所在地X
            this.mouseWheelX = e.offsetX || e.layerX
            // wheel 事件  鼠标所在地Y
            this.mouseWheelY = e.offsetY || e.layerY
            this.setImageTop()
            this.setImageLeft()
          })
        }
      }
    }
  },
  created() {},
  mounted() {}
}
</script>
<style lang="scss">
</style>

css代码

.map-image{
  position: relative;
  .map-image-container{
    height: 600px;
    width: 600px;
    position: absolute;
    overflow: hidden;
    z-index: 1;
    .map-image-box{
      position: absolute;
      @include bg-img-contain;
      z-index: 1;
      .marker{
        position: absolute;
        top: 0;
        left: 0;
        height: 39px;
        transform: translate(-11px,-34px);
        z-index: 10;
      }
    }
  }
  .map-image-wheel{
    position: relative;
    top:0;
    left: 0;
    overflow: hidden;
    z-index: 100;
  }
}