js实现类似百度地图的缩放拖动功能
使用的前端框架为vue
采用vue的框架,通过canvas的缩放绘制和外层div的滚动条控制实现功能
滚轮事件代码
handleScroll(e) {
console.log(e.deltaY)
let _this = this
if (e.deltaY > 0 && _this.zoomtimes > 1) {
_this.zoomtimes = (_this.zoomtimes * 10 - 2) / 10
} else if (e.deltaY < 0) {
_this.zoomtimes = (_this.zoomtimes * 10 + 2) / 10
}
this.down_x = e.clientX
this.down_y = e.clientY
if (e.deltaY > 0) {
_this.direction = 1
} else {
_this.direction = 0
}
e.preventDefault()
},
wheelxy() {
let _this = this
if (_this.direction == 0) {
_this.$store.state.scroll_x = _this.$refs.imageWrapper.scrollLeft =
(((_this.$refs.imageWrapper.clientWidth - 10) * _this.zoomtimes) /
((_this.$refs.imageWrapper.clientWidth - 10) *
(_this.zoomtimes - 0.2)) -
1) *
(_this.down_x - 67 + _this.$refs.imageWrapper.scrollLeft) +
_this.$refs.imageWrapper.scrollLeft
_this.$store.state.scroll_y = _this.$refs.imageWrapper.scrollTop =
(((_this.$refs.imageWrapper.clientHeight - 10) * _this.zoomtimes) /
((_this.$refs.imageWrapper.clientHeight - 10) *
(_this.zoomtimes - 0.2)) -
1) *
(_this.down_y - 193 + _this.$refs.imageWrapper.scrollTop) +
_this.$refs.imageWrapper.scrollTop
} else {
_this.$store.state.scroll_x = _this.$refs.imageWrapper.scrollLeft =
(1 -
((_this.$refs.imageWrapper.clientWidth - 10) *
(_this.zoomtimes + 0.2)) /
((_this.$refs.imageWrapper.clientWidth - 10) * _this.zoomtimes)) *
(_this.down_x - 67 + _this.$refs.imageWrapper.scrollLeft) +
_this.$refs.imageWrapper.scrollLeft
_this.$store.state.scroll_y = _this.$refs.imageWrapper.scrollTop =
(1 -
((_this.$refs.imageWrapper.clientHeight - 10) *
(_this.zoomtimes + 0.2)) /
((_this.$refs.imageWrapper.clientHeight - 10) *
_this.zoomtimes)) *
(_this.down_y - 193 + _this.$refs.imageWrapper.scrollTop) +
_this.$refs.imageWrapper.scrollTop
}
},
通过监听zoomtimes字段获取缩放倍数,调用canvas绘制,canvas绘制完成后调用wheelxy()函数实现定位滑动条位置为鼠标指针中心.
拖动事件代码
mouse_down(e) {
console.log(this.$store.state.scroll_x)
this.down_x = e.clientX
this.down_y = e.clientY
this.$refs.imageWrapper.scrollLeft = this.$store.state.scroll_x
this.$refs.imageWrapper.scrollTop = this.$store.state.scroll_y
this.mouseevent = true
},
mouse_move(e) {
if (this.mouseevent) {
let x = e.clientX - this.down_x
let y = e.clientY - this.down_y
this.$refs.imageWrapper.scrollLeft = this.$store.state.scroll_x - x
this.$refs.imageWrapper.scrollTop = this.$store.state.scroll_y - y
}
},
mouse_up(e) {
this.$store.state.scroll_x = this.$refs.imageWrapper.scrollLeft
this.$store.state.scroll_y = this.$refs.imageWrapper.scrollTop
this.mouseevent = false
},
拖动时注册三个事件函数,用vuex记录每次放开鼠标或缩放完成后的滚动条位置
实现方法逻辑
通过计算canvas缩放时的倍数计算出x轴和y轴的增量△x和△y,其中判断鼠标滚轮方向以使用不同的计算公式,放大时△x = ( canvas.width' / canvas.width - 1) x, 缩小时 △x = ( 1 - canvas.width / canvas.width' ) x',△y同理,而x,y轴的偏移量就是滚动条应该滚动的大小,每次叠加到上一次的数据中.
拖动时,以每次vuex记录的的滚动条位置为起点进行拖动
后续更新
通过实际测试发现,通过增量△x与△y操作的缩放拖动功能虽然能实现需求功能,但是在实际运用时发现会产生不流畅的迟钝感.
个人原本认为是因为每次移动或缩放产生的大量计算导致浏览器的canvas性能出现问题,但经过查询资料和实际测试发现,这种程度的计算远远没有达到浏览器的性能瓶颈,所以说明产生钝感的原因的核心还是算法的问题,因为算法的不细致导致了动画效果的不流畅.
在几次查询资料之后,我修改了代码的算法逻辑,新的算法和之前相比更加的丝滑,代码如下:
新滚轮事件代码
handleScroll(e) {
let _this = this
_this.down_xy = _this.windowToCanvas(e.clientX, e.clientY)
let newDown_xy = {
x: (
(_this.down_xy.x - _this.imgX - _this.b) /
_this.zoomtimes /
_this.ts
).toFixed(2),
y: (
(_this.down_xy.y - _this.imgY - _this.a) /
_this.zoomtimes /
_this.ts
).toFixed(2)
}
if (e.deltaY > 0 && _this.zoomtimes > 1 && _this.zoomtimes < 1.1) {
_this.zoomtimes = 1
_this.imgX =
(1 - _this.zoomtimes * _this.ts) * newDown_xy.x +
(_this.down_xy.x - newDown_xy.x) -
_this.b
_this.imgY =
(1 - _this.zoomtimes * _this.ts) * newDown_xy.y +
(_this.down_xy.y - newDown_xy.y) -
_this.a
} else if (e.deltaY > 0 && _this.zoomtimes > 1) {
_this.zoomtimes = (_this.zoomtimes * 10 - 1) / 10
_this.imgX =
(1 - _this.zoomtimes * _this.ts) * newDown_xy.x +
(_this.down_xy.x - newDown_xy.x) -
_this.b
_this.imgY =
(1 - _this.zoomtimes * _this.ts) * newDown_xy.y +
(_this.down_xy.y - newDown_xy.y) -
_this.a
} else if (e.deltaY < 0) {
_this.zoomtimes = (_this.zoomtimes * 10 + 1) / 10
_this.imgX =
(1 - _this.zoomtimes * _this.ts) * newDown_xy.x +
(_this.down_xy.x - newDown_xy.x) -
_this.b
// (1 - _this.zoomtimes) * _this.down_xy.x - _this.b
_this.imgY =
(1 - _this.zoomtimes * _this.ts) * newDown_xy.y +
(_this.down_xy.y - newDown_xy.y) -
_this.a
// (1 - _this.zoomtimes ) * _this.down_xy.y - _this.a
}
this.$store.state.move_item = ''
e.preventDefault()
console.info(_this.imgX, _this.imgY, 'sdsdsdsdsd')
},
windowToCanvas(x, y) {
let mycanvas = document.getElementById('canvas_img')
let box = mycanvas.getBoundingClientRect() //这个方法返回一个矩形对象,包含四个属性:left、top、right和bottom。分别表示元素各边与页面上边和左边的距离
return {
x: x - box.left - (box.width - mycanvas.width) / 2,
y: y - box.top - (box.height - mycanvas.height) / 2
}
},
新拖动事件
mouse_down(e) {
console.log(this.$store.state.scroll_x)
this.mouseevent = true
this.down_xy = this.windowToCanvas(e.clientX, e.clientY)
},
mouse_move(e) {
if (this.mouseevent) {
this.down_xyl = this.windowToCanvas(e.clientX, e.clientY)
let x = this.down_xyl.x - this.down_xy.x,
y = this.down_xyl.y - this.down_xy.y
this.imgX += x
this.imgY += y
this.down_xy = JSON.parse(JSON.stringify(this.down_xyl))
}
},
mouse_up(e) {
if (this.mouseevent) {
this.$store.state.move_item = ''
}
this.mouseevent = false
},
其中 _this.a, _this.b, _this.ts都是这个项目中canvas的效果需求特殊定义的,比如ts变量指原图放到canvas时的比例,a,b为图片初始位置对canvas边框的距离,如果要测试的话可以先去掉不用,根据自己的情况添加