分两层: 操作层与显示层
操作层监听用户各种事件,显示层只管将操作得来的数据进行显示。
等待明天优化拖曳的边界情况。
修改了偏移量的生成,原本只考虑了向左上偏移,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;
}
}