前言
看见电商的查看商品图片细节的功能,想着自己能不能写出来,随后一发不可收拾~成功打发了一晚上的时间。咳咳言归正传,功能在脑子里转了一下感觉很简单,无非就是图片拖动、图片缩放,但其实写的过程中还是有很多坑的,下面来一一细说:
1. 图片拖动
必然少不了事件:mousedown、mousemove、mouseup
<div class="box">
<div class="avatar">
<img src="./avatar1.jpg" draggable="false">
</div>
</div>
首先,很简单的结构,唯一需要注意的是img
标签的draggable
属性,draggable
属性在img
、a
标签中默认true
,先置成false
,draggable
属于浏览器默认拖动,会跟自己写的事件冲突从而导致拖不动、拖动残影等等奇怪的问题。大概就像这样
当然,还可以取消图片/文字选中进一步增加体验感
document.addEventListener('selectstart', e => { e.preventDefault() })
鼠标按下
获取图片的位置并添加监听鼠标移动、抬起事件,这里本熊使用event.clientX和event.clientY计算
const mouseDown = e => {
let transf = getTransform(oDiv)
x = e.clientX - transf.transX // 图片初始位置
y = e.clientY - transf.transY // 图片初始位置
document.addEventListener('mousemove', mouseMove)
document.addEventListener('mouseup', mouseUp)
}
图片相对浏览器(有效区域)的位置减去图片的translateX
和translateY
的值(图片无left
、top
等样式)赋值给变量x
, y
,关于transform
怎么获取下面会说到
鼠标拖动
鼠标当前相对浏览器(有效区域)位置减去鼠标按下时的位置 计算出图片移动的距离,通过 DOM.style.transform
更新图片位置
const mouseMove = e => {
let multiple = getTransform(oDiv).multiple
let moveX = e.clientX - x // x向移动距离
let moveY = e.clientY - y // y向移动距离
let newTransf = limitBorder(oDiv, oBox, moveX, moveY, multiple)
oDiv.style.transform = `matrix(${multiple}, 0, 0, ${multiple}, ${newTransf.transX}, ${newTransf.transY})`
}
因为要写图片缩放的原因,这里一并把图片的scale
倍数值multiple
获取到,一并更新transform
,limitBorder
函数通过多层判断给出合理(不超边框等~)的新的translateX和translateY
,下面会细说,我们先说鼠标抬起
鼠标抬起
鼠标抬起没有什么特别的,无非就是移除监听器,但要注意的一点是,很多人只移除mousemove
的回调函数,其实mouseup
也要一并移除,否则在多次拖动之后会有多个mouseup
事件(addEventListener可以重复添加同种事件)导致拖动延迟
const mouseUp = () => {
document.removeEventListener('mousemove', mouseMove)
document.removeEventListener('mouseup', mouseUp)
}
没错,鲁迅曾经说过——我杀我自己~
鼠标滚轮滚动
根据event.deltaY
判断放大 / 缩小,将缩放倍数multiple
乘 / 除以某个倍数(这里本熊使用的是DELTA = 1.1
倍)然后更新transform
的scale
const zoom = e => {
let transf = getTransform(oDiv)
if (e.deltaY < 0) {
transf.multiple *= DELTA // 放大DELTA倍
} else {
transf.multiple /= DELTA // 缩小DELTA倍
}
let newTransf = limitBorder(oDiv, oBox, transf.transX, transf.transY, transf.multiple)
oDiv.style.transform = `matrix(${transf.multiple}, 0, 0, ${transf.multiple}, ${newTransf.transX}, ${newTransf.transY})`
}
2. 边界问题
半年前本熊在写边界问题的时候,用了多个if
判断上下左右四个边界、如果超出就等于边界值。咳咳,可现在的我不一样了,要想不出界,不就是一个数学的闭区间问题吗x方向移动距离∈[左边界, 右边界]
,y方向移动距离∈[上边界, 下边界]
,利用Math
函数的方法即可
transX = Math.max(Math.min(moveX, right), left)
transY = Math.max(Math.min(moveY, bottom), top)
至于边界值具体怎么算就不做过多解释了、简单的加减法以及缩放倍率的乘法
那么问题来了:上面的情况是图片小于父盒子,如果图片放大到盒子放不下呢?
本熊的做法:上面的情况是图片“靠内边”,如果图片宽或高人一边超过盒子,便让他最多“靠外边”,如图所示
当图片超出时,很简单~此时图片相当于盒子,盒子相当于图片
,只不过我们拖动的是盒子,啊 恍然大悟
具体代码就只需把Math.max
和Math.min
调换一下即可,下面贴一下本熊的代码
const limitBorder = (innerDOM, outerDOM, moveX, moveY, multiple) => {
let { clientWidth: innerWidth, clientHeight: innerHeight, offsetLeft: innerLeft, offsetTop: innerTop } = innerDOM
let { clientWidth: outerWidth, clientHeight: outerHeight } = outerDOM
let transX
let transY
// 放大的图片超出box时 图片最多拖动到与边框对齐
if (innerWidth * multiple > outerWidth || innerHeight * multiple > outerHeight) {
if (innerWidth * multiple > outerWidth && innerWidth * multiple > outerHeight) {
transX = Math.min(Math.max(moveX, outerWidth - innerWidth * (multiple + 1) / 2 - innerLeft), -innerLeft + innerWidth * (multiple - 1) / 2)
transY = Math.min(Math.max(moveY, outerHeight - innerHeight * (multiple + 1) / 2 - innerTop), -innerTop + innerHeight * (multiple - 1) / 2)
} else if (innerWidth * multiple > outerWidth && !(innerWidth * multiple > outerHeight)) {
transX = Math.min(Math.max(moveX, outerWidth - innerWidth * (multiple + 1) / 2 - innerLeft), -innerLeft + innerWidth * (multiple - 1) / 2)
transY = Math.max(Math.min(moveY, outerHeight - innerHeight * (multiple + 1) / 2 - innerTop), -innerTop + innerHeight * (multiple - 1) / 2)
} else if (!(innerWidth * multiple > outerWidth) && innerWidth * multiple > outerHeight) {
transX = Math.max(Math.min(moveX, outerWidth - innerWidth * (multiple + 1) / 2 - innerLeft), -innerLeft + innerWidth * (multiple - 1) / 2)
transY = Math.min(Math.max(moveY, outerHeight - innerHeight * (multiple + 1) / 2 - innerTop), -innerTop + innerHeight * (multiple - 1) / 2)
}
}
// 图片小于box大小时 图片不能拖出边框
else {
transX = Math.max(Math.min(moveX, outerWidth - innerWidth * (multiple + 1) / 2 - innerLeft), -innerLeft + innerWidth * (multiple - 1) / 2)
transY = Math.max(Math.min(moveY, outerHeight - innerHeight * (multiple + 1) / 2 - innerTop), -innerTop + innerHeight * (multiple - 1) / 2)
}
return { transX, transY }
}
本熊这里之所以写了辣么辣么多的if-else
因为上面我们分析的情况只是图片和盒子都是正方形啦,实际还得考虑单边超出的情况,也很简单啦,超出的一边就把Math.max
和Math.min
对换就行~
再说一下,之所以要封装这个函数是因为缩放的时候也要考虑边界,请看下面对比图:
原理与拖动相同不再赘述,在鼠标滚轮回调函数zoom
中更新transform
前调用limitBorder
方法即可
3. 获取transform
半年前刚毕业还是新手的我,曾一度使用DOM.style.transform
来获取tranform
但这只是获取的是同css
代码一样的字符串 translate(100px, 100px)
这种的话,如何获取x
,y
方向的偏移量呢?可能你会觉得使用split
就可以,但如果我的属性是translate(100px, 100px) scale(2)
又或者是 translate(100px, 100px) scale(2) rotate(30deg)
好像不是很好获取呢~
getComputedStyle
以及 matrix
矩阵
MDN官方的解释比较难懂,其实简单来说 getComputedStyle(DOM).transform
会得到一个矩阵 matrix(1,0,0,1,100,100)
,在没有旋转rotate
、拉伸skew
的情况下,我们只需要关注括号里的1、4、5、6位,分别代表x
向缩放倍数、y
向缩放倍数、x
向偏移量和y
向偏移量。这种统一形式的字符串,我们完全可以用split
分隔为数组再依次取到
const getTransform = DOM => {
let arr = getComputedStyle(DOM).transform.split(',')
return {
transX: isNaN(+arr[arr.length - 2]) ? 0 : +arr[arr.length - 2], // 获取translateX
transY: isNaN(+arr[arr.length - 1].split(')')[0]) ? 0 : +arr[arr.length - 1].split(')')[0], // 获取translateX
multiple: +arr[3] // 获取图片缩放比例
}
}
需要注意的是,当该DOM
没有transform
属性时,getComputedStyle(DOM).transform
返回的是字符串 'none'
,因此我们需要使用isNaN
判断一下
以上~第一次认真写博客,不对的地方请大佬们慷慨指正!感谢!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!