当背景图片缩放遇上点位拖拽
0、写在前面
在我之前实现点位拖拽这篇文章中提到过,本文将是那一篇文章的需求升级版。如果只是希望阅读普通点位拖拽的同学可以移步那篇文章。
回归正题,本文要实现的是精确标点
:背景图片缩放并拖拽+点位拖拽。
文字描述可能不太清楚,大家可以结合第二部分的gif加以理解。
1、成品效果
如上gif图所示,在一个宽高确定的区域中,有一张图片,我们可以在图片上标注点位(即粉红色框)。考虑到标点的准确性,我们需要将图片放大,并调整图片位置,将点位拖拽至精确位置。将图片恢复至原大小后,点位相对于图片的位置不变。
借助背景图片的缩放,实现精确标点。
2、整体布局
<style>
.content {
width: 600px;
height: 400px;
margin: auto;
border: 1px solid pink;
overflow: hidden;
}
.pic {
position: relative;
width: 100%;
height: 100%;
background: url('https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg');
background-size: 100% 100%;
transform-origin: 0 0;
}
.point {
position: absolute;
width: 50px;
height: 50px;
background-color: pink;
}
</style>
<div class="content">
<div class="pic">
<div class="point"></div>
</div>
</div>
如上html所示,content为宽高固定的区域,pic就是里面要展示的图片,然后需要拖拽的点位就是point。
考虑到图片放大之后会溢出content,所以content中需要overflow:hidden。
3、实现普通点位拖拽
关于point的拖拽,在之前的文章中已经描述过,这里就不再赘述了。
后面实现其他功能时,会对这部分代码稍作修改,将会在下文中展开。
4、实现背景图片缩放
4.1 缩放方案
实现图片的缩放,我们选用的方案是使用转换:transform: scale()
。它在MDN上的描述为:
CSS 函数
scale()
用于修改元素的大小。可以通过向量形式定义的缩放值来放大或缩小元素。
按我自己的理解就是,用scale( )进行缩放的元素,他们的宽高在数值上是不变(即100px的宽高,放大后还是100px),只是它们长度的比例不一样了,经放大后的1px要长于普通的1px。这样来实现放大的效果。
scale()的默认值为1,即正常比例,设置成2,即2倍比例,放大成2倍,以此类推。
值得注意的是,转换的中心点默认是元素的中心,需要设置css样式transform-origin: 0 0;
来使元素以左上角为源点进行缩放(本文直接写在了样式表中)。
4.2 缩放控制
缩放当然是使用鼠标滚轮来控制,对应的事件是wheel
。
其中事件对象中的deltaY
属性就是判断滚轮滚动方向的依据:大于0是向下滚动,小于0是向上滚动。
4.3 代码
// 缩放大小
var scale = 1
pic.addEventListener('wheel', function(e){
const { deltaY } = e
if(deltaY > 0){
// 向下滚 缩小
scale -= 0.2
// 缩小最小值为1(可根据需求更改)
if(scale < 1) scale = 1;
pic.style.transform = `scale(${scale})`
} else if(deltaY < 0){
// 向上滚 放大
scale += 0.2
// 放大最大值为2(可根据需求更改)
if(scale > 2) scale = 2;
pic.style.transform = `scale(${scale})`
}
// 阻止默认行为,防止缩放图片时使页面滚动
e.preventDefault()
})
5、图片放大后拖拽pic
我们再看一眼布局:
<div class="content">
<div class="pic">
<div class="point"></div>
</div>
</div>
默认情况下,pic的大小是与content相同的,但是当我们使用scale进行放大时,pic就会溢出content,导致content出现滚动条(即使pic的px值不变)。
我们希望实现对放大后的pic进行拖拽,其实可以操作content的滚动条,来实现拖拽了pic的假象。
这里我们操作滚动条需要用到的属性和方法有:
事件对象属性:e.clientX,e.clientY:鼠标相对于浏览器可视区域的距离 元素对象属性:el.scrollLeft,el.scrollTop:元素向左(向下)滚动的距离
这里控制content滚动的方法其实就是之前文章中的思路一:
- 记录mousedown时,鼠标的原始位置
- 鼠标移动时,计算鼠标移动的距离,然后让content也滚动相同的距离。
// pic滚动的开关
var isPicDragging = false
var content = document.getElementsByClassName('content')[0]
// mousedown时鼠标点位
var cursorPosition = {x: 0, y: 0}
// mousedown时,x轴滚动条滚动的距离
var originScrollLeft= 0
var originScrollTop= 0
pic.addEventListener('mousedown', function(e){
isPicDragging = true
// 记录鼠标相对于浏览器可视区域的x,y距离
cursorPosition.x = e.clientX
cursorPosition.y = e.clientY
// 记录mousedown时,content已经滚动的距离
originScrollLeft = content.scrollLeft
originScrollTop = content.scrollTop
})
// 为了防止鼠标移动时,移动到pic的范围之外,
// 需要将事件绑定在document上
document.addEventListener('mousemove', function(e){
if(isPicDragging){
// 计算鼠标相对于原始位置的偏移量
var changeX = e.clientX - cursorPosition.x
var changeY = e.clientY - cursorPosition.y
// 让content也滚动相同的偏移量
// 值得注意的是,鼠标移动的方向数值与scrollLeft移动方向的数值是相反的,
// 所以不是加上changX,而是减去changeY。scrollTop同理。
content.scrollLeft = originScrollLeft - changeX
content.scrollTop = originScrollTop - changeY
}
})
document.addEventListener('mouseup', function (e) {
isPicDragging = false
})
6、图片放大时拖拽点位
点位拖拽在之前的文章中已经实现了,但是这次需要在pic放大的情况下进行拖拽,情况就变得不一样了。
回顾一下之前拖拽点位的实现:
cursorPosition是鼠标在mousedown时的原始位置,pointPosition是点位在mousedown时的style.left和style.top 计算鼠标移动的距离,然后使point的left和top也移动相同的距离。
document.addEventListener('mousemove', function (e) {
if (isDragging) {
var changeX = e.clientX - cursorPosition.x
var changeY = e.clientY - cursorPosition.y
point.style.left = pointPosition.x + changeX + 'px'
point.style.top = pointPosition.y + changeY + 'px'
}
})
但是当图片使用scale放大之后,就不能直接相加了。
上述代码中的changeX和changeY是鼠标在document上正常比例的移动距离。但是假设我们使用transform: scale(2)对pic进行了放大,那么point的位移应该移动的位移应该是changeX/2和changeY/2。
所以point进行拖拽时应该注意pic的缩放比例。
完善后的代码如下:
var scale = 1 //scale为pic的缩放比例
document.addEventListener('mousemove', function (e) {
if (isDragging) {
var changeX = e.clientX - cursorPosition.x
var changeY = e.clientY - cursorPosition.y
// 需要注意scale的缩放比例
point.style.left = pointPosition.x + changeX/scale + 'px'
point.style.top = pointPosition.y + changeY/scale + 'px'
}
})
7、完整代码
完整功能的代码实现如下:
<style>
.content {
width: 600px;
height: 400px;
margin: auto;
border: 1px solid pink;
overflow: hidden;
}
.pic {
position: relative;
width: 100%;
height: 100%;
background: url('https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg');
background-size: 100% 100%;
transform-origin: 0 0;
}
.point {
position: absolute;
width: 50px;
height: 50px;
background-color: pink;
}
</style>
<div class="content">
<div class="pic">
<div class="point"></div>
</div>
</div>
<script type="text/javascript">
var point = document.getElementsByClassName('point')[0]
var pic = document.getElementsByClassName('pic')[0]
// point是否正在拖拽
var isDragging = false
// 鼠标点位
var cursorPosition = { x: 0, y: 0 }
// point的left和top
var pointPosition = { x: 0, y: 0 }
// 开始拖拽point
point.addEventListener('mousedown', function (e) {
// 阻止事件传播,否则pic绑定的事件处理函数也会执行
e.stopPropagation()
isDragging = true
cursorPosition.x = e.clientX
cursorPosition.y = e.clientY
pointPosition.x = parseFloat(point.style.left || 0)
pointPosition.y = parseFloat(point.style.top || 0)
})
// 图片是否正在拖拽
var isPicDragging = false
var content = document.getElementsByClassName('content')[0]
// mousedown时,content的原始scrollLeft
var originScrollLeft= 0
var originScrollTop= 0
// pic开始拖拽
pic.addEventListener('mousedown', function(e){
isPicDragging = true
// 记录鼠标相对于浏览器可视区域的x,y距离
cursorPosition.x = e.clientX
cursorPosition.y = e.clientY
// 记录mousedown时,content已经滚动的距离
originScrollLeft = content.scrollLeft
originScrollTop = content.scrollTop
})
// 鼠标移动,判断是否正在拖拽,是哪个元素正在拖拽
// 为了防止鼠标移动时,移动到元素的范围之外,
// 需要将事件绑定在document上
document.addEventListener('mousemove', function (e) {
// point正在拖拽
if (isDragging) {
// 计算鼠标相对于原始位置的偏移量
var changeX = e.clientX - cursorPosition.x
var changeY = e.clientY - cursorPosition.y
// 这里注意scale缩放比例
point.style.left = pointPosition.x + changeX / scale + 'px'
point.style.top = pointPosition.y + changeY /scale + 'px'
// 下面的操作是防止拖拽的div越过外层盒子
if (point.offsetLeft < 0) point.style.left = 0;
if (point.offsetTop < 0) point.style.top = 0;
if (point.offsetLeft > (pic.offsetWidth - point.offsetWidth)) {
point.style.left = pic.offsetWidth - point.offsetWidth + 'px';
}
if (point.offsetTop > (pic.offsetHeight - point.offsetHeight)) {
point.style.top = pic.offsetHeight - point.offsetHeight + 'px';
}
}
// pic正在拖拽
if(isPicDragging){
// 计算鼠标相对于原始位置的偏移量
var changeX = e.clientX - cursorPosition.x
var changeY = e.clientY - cursorPosition.y
// 让content也滚动相同的偏移量
// 值得注意的是,鼠标移动的方向数值与scrollLeft移动方向的数值是相反的,
// 所以不是加上changX,而是减去changeY。scrollTop同理。
content.scrollLeft = originScrollLeft - changeX
content.scrollTop = originScrollTop - changeY
}
})
// 鼠标抬起,停止拖拽
document.addEventListener('mouseup', function (e) {
isDragging = false
isPicDragging = false
})
// 背景图片缩放比例
var scale = 1
pic.addEventListener('wheel', function(e){
const { deltaY } = e
if(deltaY > 0){
// 向下滚 缩小
scale -= 0.2
// 缩小最小值为1(可根据需求更改)
if(scale < 1) scale = 1;
pic.style.transform = `scale(${scale})`
} else if(deltaY < 0){
// 向上滚 放大
scale += 0.2
// 放大最大值为2(可根据需求更改)
if(scale > 2) scale = 2;
pic.style.transform = `scale(${scale})`
}
// 阻止默认行为,防止缩放图片时使页面滚动
e.preventDefault()
})
</script>
转载自:https://juejin.cn/post/7249758081293533239