实现类似 LOL小地图/鹰眼地图/电子放大功能 基于vue3

项目中遇到了个类似LOL小地图的需求,电子放大

没找到合适的 DEMO,自己写了个(基于vue3 + ts) ,以供参考

实现功能:

1. 在主视框中滚动缩放内容,右小角小视窗红框选出对应区域

2. 小视窗滚动可以缩放主视框

3. 主视框可以拖动内容,右小角小视窗红框同步移动

4. 拖动右小角小视窗红框,可调整主视框放大内容

效果图:

 

上代码:

<script lang="ts" setup>
import { ref, reactive, onMounted, computed, Ref } from "vue"

interface Position {
  x: number
  y: number
}

const rollBoxContainer: Ref<HTMLElement | null> = ref(null)
const zoomElement: Ref<HTMLElement | null> = ref(null)
const smallWindow: Ref<HTMLElement | null> = ref(null)
const redBox: Ref<HTMLElement | null> = ref(null)

const zoomElementValue = computed(() => zoomElement.value)
const rollBoxContainerValue = computed(() => rollBoxContainer.value)

const mainCurScale = ref(1)
const mousePosition: Position = reactive({ x: 0, y: 0 })
const calcMousePosition: Position = reactive({ x: 0, y: 0 })

const mainRollingStyle = computed(() => ({
  transformOrigin: `${mousePosition.x}px ${mousePosition.y}px`,
  transform: `scale(${mainCurScale.value})`,
  cursor: mainCurScale.value > 1 ? "grab" : ""
}))

const redBoxStyle = computed(() => ({
  transformOrigin: `${calcMousePosition.x}px ${calcMousePosition.y}px`,
  transform: `scale(${1 / mainCurScale.value})`,
  border: `${mainCurScale.value * 2}px solid #ff6969`
}))

/**
 * 工具函数
 * 1. 获取元素位置信息
 * 2. 检查移动范围
 * 3. 修正移动位置
 */
function getRect(el: Ref<HTMLElement | null>): DOMRect {
  return el.value?.getBoundingClientRect()!
}

function checkSafeMove(zoomElement: Ref<HTMLElement | null>, rollBoxContainer: Ref<HTMLElement | null>): boolean {
  if (zoomElement.value) {
    const { transformOrigin } = getComputedStyle(zoomElement.value)
    const { height, width } = getRect(rollBoxContainer)

    const [originX, originY] = transformOrigin.split(" ").map((i) => parseFloat(i))
    const safeTopLeft = originX >= 0 && originY >= 0
    const safeBottomRight = originX <= width && originY <= height

    return safeTopLeft && safeBottomRight
  }
  return false
}

function fixPosition(position: Position, container: Ref<HTMLElement | null>) {
  const { height, width } = getRect(container)
  const { x, y } = position
  position.x = Math.min(Math.max(x, 0), width)
  position.y = Math.min(Math.max(y, 0), height)
}

// 滚动大图
function rollingMainWindow(e: WheelEvent) {
  const { clientX, clientY } = e
  const delta = Math.sign(e.deltaY)
  mainCurScale.value -= delta * 0.1
  mainCurScale.value = Math.max(mainCurScale.value, 1)

  const rollBoxRect = getRect(rollBoxContainer)
  const { left, top, width, height } = getRect(zoomElement)

  mousePosition.x = ((clientX - left) / width) * rollBoxRect.width
  mousePosition.y = ((clientY - top) / height) * rollBoxRect.height
  synchronizeMoveRedBox()
}

function synchronizeMoveRedBox() {
  const rollBoxRect = getRect(rollBoxContainer)
  const { width, height } = getRect(smallWindow)
  calcMousePosition.x = (mousePosition.x / rollBoxRect.width) * width
  calcMousePosition.y = (mousePosition.y / rollBoxRect.height) * height
}

// 处理拖拽大图
const startMove = ref(false)

function handleStartMove() {
  startMove.value = true
}

function handleMovingZoomElement(e: MouseEvent) {
  if (!startMove.value) {
    return
  }
  if (!checkSafeMove(zoomElement, rollBoxContainer)) {
    fixPosition(mousePosition, rollBoxContainer)
    return
  }

  const scale = mainCurScale.value
  const { movementX, movementY } = e

  mousePosition.x -= movementX / scale
  mousePosition.y -= movementY / scale
  synchronizeMoveRedBox()
}

// 小窗口滚动
function rollingSmallWindow(e: WheelEvent) {
  const { clientX, clientY } = e
  const delta = Math.sign(e.deltaY)
  mainCurScale.value += delta * 0.1
  mainCurScale.value = Math.max(mainCurScale.value, 1)
}

// 处理拖动小窗口
const startMoveRedBox = ref(false)

function handleRedBoxStartMove(e: MouseEvent) {
  startMoveRedBox.value = true
}

function handleRedBoxSMoving(e: MouseEvent) {
  if (!startMoveRedBox.value) {
    return
  }
  if (!checkSafeMove(redBox, smallWindow)) {
    fixPosition(calcMousePosition, smallWindow)
    return
  }

  const { movementX, movementY } = e
  const scale = mainCurScale.value

  calcMousePosition.x += movementX * scale
  calcMousePosition.y += movementY * scale
  synchronizeMoveZoomElement()
}

function synchronizeMoveZoomElement() {
  if (rollBoxContainer.value && smallWindow.value) {
    const rollBoxRect = getRect(rollBoxContainer)
    const { width, height } = getRect(smallWindow)

    mousePosition.x = (calcMousePosition.x / width) * rollBoxRect.width
    mousePosition.y = (calcMousePosition.y / height) * rollBoxRect.height
  }
}

onMounted(() => {
  window.addEventListener("mouseup", () => {
    startMove.value = false
    startMoveRedBox.value = false
  })
})
</script>

<style lang='scss' scoped>
@mixin fill {
  height: 100%;
  width: 100%;
}
.app-container {
  user-select: none;
  -webkit-user-drag: none;
  .main-container {
    height: 500px;
    max-width: 900px;
    position: relative;
    border: 1px solid #fff;
    overflow: hidden;
    .zoom-element {
      @include fill();
    }
    .small-window {
      height: 30%;
      width: 30%;
      position: absolute;
      right: -1px;
      bottom: -1px;
      border: 1px solid #fff;
      .red-box-container {
        @include fill();
        background-image: url(http://surl.li/dpeat);
        background-size: 100%;
        .red-box {
          @include fill();
          cursor: grab;
        }
      }
    }
  }
}
</style>

<template>
  <div class="app-container">
    <div
      ref="rollBoxContainer"
      class="main-container"
      @mousewheel.prevent="rollingMainWindow"
      @mousedown="handleStartMove"
      @mousemove="handleMovingZoomElement"
    >
      <img
        src="http://surl.li/dpeat"
        ref="zoomElement"
        :style="mainRollingStyle"
        class="zoom-element"
        :draggable="false"
      />
      <div ref="smallWindow" class="small-window" @mousedown.stop @mousemove.stop @mousewheel.stop="rollingSmallWindow">
        <div class="red-box-container">
          <div
            ref="redBox"
            v-show="mainCurScale > 1"
            :style="redBoxStyle"
            class="red-box"
            @mousedown.stop="handleRedBoxStartMove"
            @mousemove.stop="handleRedBoxSMoving"
          />
        </div>
      </div>
    </div>
  </div>
</template>

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值