美文网首页
【动手系列】以鼠标为中心对图片进行缩放

【动手系列】以鼠标为中心对图片进行缩放

作者: 曽小晨 | 来源:发表于2019-08-08 14:59 被阅读0次

    在上一家公司开发的时候,看到一个流程图组件,里面有一个拖拽和缩放的功能,缩放很鸡肋,不会以鼠标中心点缩放。所以用户在缩放的时候,还得不停的拖拽。

    以用户体验为第一的原则,我就想着把这个功能的体验弄好一点,在网上找了一些资料:

    最后选择的是 css3 实现,效果图:

    image

    思路

    最开始界面应该是有一个 div(400 * 300),如下:

    初始化div

    然后假设用户进行鼠标放大之后,scale是 1.4:

    图片放大

    这个时候,transform的值应该是translate(-80px, -60px) scale(1.4)

    计算过程:(scale后的高度 - 最开始的高度) * 鼠标在图片高度位置的比例

    1. 图片高度是 300px,假设鼠标在 150px 的位置,得到位置比例是 150 / 300 = 0.5,放大后的高度是 300 * 1.4 = 420px,向上增加的高度应该是 (420 - 300) * 0.5 = 60px。
    2. 不管是缩小还是放大,都把上一次translate对应坐标的值 - 这次得到的值,最后得出 translate 属性上y的值是上一次的值(0) - 60 = -60

    鼠标在图片上的比例,也就是 150px 是如何来的,以 y 轴为例:鼠标的位置(event.y) - 缩放元素的父元素距离屏幕顶部的距离(通过dom.getBoundingClientRect().top可以获取到)

    代码如下:

    /**
     * 元素缩放、拖拽
     * @param {string | HTMLBaseElement} selector 元素选择器或者一个元素
     * @param {number} [scale] 初始化的缩放比
     * @param {object} [option] 其他选项
     * @param {number} [option.interval = 0.1] 每次叠加的间隔数
     * @param {number} [option.minScale = 0.5] 最小缩放
     * @param {number} [option.maxScale = 3] 最大缩放
     * @param {number} [option.disabledZoom = false] 是否禁用缩放,默认 否
     * @param {number} [option.disabledDrag = false] 是否禁用拖拽,默认 否
     * @param {number} [option.slopOver = true] 是否可以超出父容器边界,默认 是
     */
    function zoom (selector, scale = 1, option = {}) {
        // 记录 Translate 的坐标值
        let prevTranslateMap = {
            x: 0,
            y: 0
        }
        let zoomDom = selector,
            mx, // 记录鼠标按下时的 x 坐标
            my, // 记录鼠标按下时的 y 坐标
            tLeft = prevTranslateMap.x, // 最后设置的 translateX 值
            tTop = prevTranslateMap.y, // 最后设置的 translateY 值
            newsetWidth, // 拖动容器最新的宽度
            newsetHeight, // 拖动容器最新的高度
            firstMoveFlag = false // 第一次移动标记,防止用户第一次按下和松开鼠标但并未移动,第二次移动时 dom 出现闪现的情况
        const { interval = 0.1, minScale = 0.5, maxScale = 3, slopOver = true, disabledZoom = false, disabledDrag = false } = option
        if (typeof selector === 'string') {
            zoomDom = document.querySelector(selector)
        }
        zoomDom.style.transformOrigin = '0 0';
        // 获取最初始的宽高
        const { width: initWidth, height: initHeight } = zoomDom.getBoundingClientRect()
        const pDom = zoomDom.parentElement;
        // 滚动事件兼容文章(https://www.zhangxinxu.com/wordpress/2013/04/js-mousewheel-dommousescroll-event/)
        !disabledZoom && zoomDom.addEventListener('mousewheel', ev => {
            const isZoomOut = ev.deltaY < 0; // 缩小
            // 鼠标坐标
            const { x: mouseX, y: mouseY } = ev;
            // 元素当前宽高
            const { height, width } = zoomDom.getBoundingClientRect();
            const { top: pTop, left: pLeft } = pDom.getBoundingClientRect()
            if (isZoomOut) {
                // 缩小
                scale -= interval;
                if (minScale && scale < minScale) {
                    scale = minScale
                }
            } else {
                // 放大
                scale += interval;
                if (maxScale && scale > maxScale) {
                    scale = maxScale
                }
            }
            // 获取比例
            let yScale = (mouseY - pTop - prevTranslateMap.y) / height;
            let xScale = (mouseX - pLeft - prevTranslateMap.x) / width;
            // 放大后的宽高
            const ampWidth = initWidth * scale
            const ampHeight = initHeight * scale
            // 需要重新运算的 translate 坐标
            const y = yScale * (ampHeight - height)
            const x = xScale * (ampWidth - width)
            // 更新
            const translateY = prevTranslateMap.y - y
            const translateX = prevTranslateMap.x - x
            zoomDom.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
            // 记录这次的值
            prevTranslateMap = {
                x: translateX,
                y: translateY
            }
            ev.preventDefault()
        })
        // 鼠标按下去
        !disabledDrag && zoomDom.addEventListener('mousedown', mousedown);
        
        function mousedown(ev) {
            mx = ev.x;
            my = ev.y;
            const clientRect = zoomDom.getBoundingClientRect()
            newsetWidth = clientRect.width
            newsetHeight = clientRect.height
            // 鼠标移动
            document.addEventListener('mousemove', mousemove);
            // 鼠标松开
            document.addEventListener('mouseup', mouseup);
        }
        function mousemove(ev) {
            firstMoveFlag = true
            tTop = prevTranslateMap.y + (ev.y - my)
            tLeft = prevTranslateMap.x + (ev.x - mx)
            if (!slopOver) {
                if (tTop < 0) tTop = 0
                if (tLeft < 0) tLeft = 0
                const rightBoundary = pDom.offsetWidth - newsetWidth // 右边边界
                const bottomBoundary = pDom.offsetHeight - newsetHeight // 下边边界
                if (tTop > bottomBoundary) tTop = bottomBoundary
                if (tLeft > rightBoundary) tLeft = rightBoundary
            }
            // 设置样式
            zoomDom.style.cssText += `transform: translate(${tLeft}px, ${tTop}px) scale(${scale})`;
        }
        function mouseup() {
            if (firstMoveFlag) {
              prevTranslateMap = {
                x: tLeft,
                y: tTop
              }
            }
            document.removeEventListener('mousemove', mousemove);
            document.removeEventListener('mouseup', mouseup);
        }
    }
    
    zoom('#drag'); // <div><div id='drag'></div></div>
    

    需要注意的几行代码,少了这几行,缩放就达不到想要的效果:

    • zoomDom.style.transformOrigin = '0 0';要给缩放元素设置该属性。
    • const { top: pTop, left: pLeft } = pDom.getBoundingClientRect();每次进行缩放时获取父元素的 topleft 值,用来获取鼠标坐标在图片比例最重要的一步。

    代码还有很多缺陷,总会一步步完善的,努力吧。

    相关文章

      网友评论

          本文标题:【动手系列】以鼠标为中心对图片进行缩放

          本文链接:https://www.haomeiwen.com/subject/rwexjctx.html