最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 原生JS图片拖动、缩放、边界等问题总结

    正文概述 掘金(浴室熊)   2021-03-12   735

    前言

    看见电商的查看商品图片细节的功能,想着自己能不能写出来,随后一发不可收拾~成功打发了一晚上的时间。咳咳言归正传,功能在脑子里转了一下感觉很简单,无非就是图片拖动图片缩放,但其实写的过程中还是有很多坑的,下面来一一细说:

    1. 图片拖动

    必然少不了事件:mousedown、mousemove、mouseup

    <div class="box">
        <div class="avatar">
            <img src="./avatar1.jpg" draggable="false">
        </div>
    </div>
    

    首先,很简单的结构,唯一需要注意的是img标签的draggable属性,draggable属性在imga标签中默认true,先置成falsedraggable属于浏览器默认拖动,会跟自己写的事件冲突从而导致拖不动、拖动残影等等奇怪的问题。大概就像这样

    原生JS图片拖动、缩放、边界等问题总结 原生JS图片拖动、缩放、边界等问题总结

    当然,还可以取消图片/文字选中进一步增加体验感

    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)
    }
    

    图片相对浏览器(有效区域)的位置减去图片的translateXtranslateY的值(图片无lefttop等样式)赋值给变量xy,关于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获取到,一并更新transformlimitBorder函数通过多层判断给出合理(不超边框等~)的新的translateX和translateY,下面会细说,我们先说鼠标抬起

    鼠标抬起

    鼠标抬起没有什么特别的,无非就是移除监听器,但要注意的一点是,很多人只移除mousemove的回调函数,其实mouseup也要一并移除,否则在多次拖动之后会有多个mouseup事件(addEventListener可以重复添加同种事件)导致拖动延迟

    const mouseUp = () => {
        document.removeEventListener('mousemove', mouseMove)
        document.removeEventListener('mouseup', mouseUp)
    }
    

    没错,鲁迅曾经说过——我杀我自己~

    鼠标滚轮滚动

    根据event.deltaY判断放大 / 缩小,将缩放倍数multiple乘 / 除以某个倍数(这里本熊使用的是DELTA = 1.1倍)然后更新transformscale

    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)

    至于边界值具体怎么算就不做过多解释了、简单的加减法以及缩放倍率的乘法

    那么问题来了:上面的情况是图片小于父盒子,如果图片放大到盒子放不下呢?

    本熊的做法:上面的情况是图片“靠内边”,如果图片宽或高人一边超过盒子,便让他最多“靠外边”,如图所示

    原生JS图片拖动、缩放、边界等问题总结

    当图片超出时,很简单~此时图片相当于盒子,盒子相当于图片,只不过我们拖动的是盒子,啊 恍然大悟

    具体代码就只需把Math.maxMath.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.maxMath.min对换就行~

    再说一下,之所以要封装这个函数是因为缩放的时候也要考虑边界,请看下面对比图:

    原生JS图片拖动、缩放、边界等问题总结 原生JS图片拖动、缩放、边界等问题总结

    原理与拖动相同不再赘述,在鼠标滚轮回调函数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判断一下

    以上~第一次认真写博客,不对的地方请大佬们慷慨指正!感谢!


    起源地下载网 » 原生JS图片拖动、缩放、边界等问题总结

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元