js实现以鼠标为中心缩放图片

最终实现效果如下图

第一版

  • 最开始接到这个需求的时候,想的很简单,感觉不是一个很复杂的东西

  • 开始想的是用 transform-origintransform:scale 实现

  • 根据鼠标位置来改变缩放中心,滚轮滚动来改变scale大小

  • 代码如下

js 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            background:
                -webkit-linear-gradient(top, transparent 99px, #ccc 99px),
                -webkit-linear-gradient(left, transparent 99px, #ccc 99px);
            background-size: 100px 100px;
        }

        .container {
            position: relative;
            top: 0;
            left: 0;
            width: 100px;
            height: 100px;
        }

        img {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            object-fit: cover;
            transform-origin: 0% 0%;
        }
    </style>
</head>

<body>
    <div class="container">
        <img id="img" src="https://t7.baidu.com/it/u=1653814446,2847580380&fm=193&f=GIF" alt="">
    </div>
    <script>
        let s = 1
        let minL = 1
        let maxL = 20

        const img = document.getElementById('img')

        img.addEventListener('mousewheel', (e) => {
            const { x, y, deltaY } = e

            s += deltaY * -0.01
            s = Math.min(Math.max(minL, s), maxL)

            img.style.transformOrigin = `${x}px ${y}px`
            img.style.transform = `scale(${s})`

        })
    </script>
</body>

</html>
  • 乍一看,貌似这个功能就这样实现了

  • 但是,当我们在一个位置缩放后,鼠标换个位置再次缩放,图片就会瞬移,如果缩放位置在最初的盒子的位置之外,图片还可能缩放到视图之外,

  • 想不通瞬移,百度,查,查到一个老哥遇到了和我相同的问题这是他的帖子,在他文章回复里看到了瞬移的原因

  • 还有解决方案
  • 继续改代码,计算scale,translate......,

  • 算不对,老是算不对,还是算不对,不算了,百度

  • 百度出来怎么做的都有,就是有点看不懂.....,虽然有些看不懂,但还是吸收了一点经验

第二版

  • 既然做不出来,那就问问水友群的大佬吧,
  • 第二版就这样出来了,用transform:scale来控制大小,top,left控制图片位置

  • 我加了一点自己思考,让这个问题变得更加容易理解

1.每次缩放的时候可以获取鼠标位置(x,y)

2.获取鼠标位置相对于图片缩放为1(transform:scale(1))时的位置(x2,y2)

3.图片从scale=1 缩放到scale=s后,鼠标的位置(x3,y3)

4.再从缩放后的位置(x2,y2),回到(x,y)的位置,计算出图片的偏移(x4,y4)

5.设置图片位置和缩放比例

js 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            background:
                -webkit-linear-gradient(top, transparent 99px, #ccc 99px),
                -webkit-linear-gradient(left, transparent 99px, #ccc 99px);
            background-size: 100px 100px;
        }


        .container {
            position: absolute;
            top: 0;
            left: 0;
            width: 200px;
            height: 200px;
        }

        img {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            object-fit: cover;
            transform-origin: 0% 0%;
        }
    </style>
</head>

<body>
    <div class="container">
        <img id="img" src="https://t7.baidu.com/it/u=1653814446,2847580380&fm=193&f=GIF" alt="">
    </div>
    <script>
        let s = 1//缩放倍率
        let lasts = 1
        let minL = 1
        let maxL = 20

        const img = document.getElementById('img')

        // 缩放前,鼠标位置相对图片缩放为1时的位置
        const getSourePosition = (el, s, x, y) => {
            let sourceTop = +el.style.top.slice(0, -2)
            let sourceLeft = +el.style.left.slice(0, -2)

            let x2 = (x - sourceLeft) / s
            let y2 = (y - sourceTop) / s

            return { x2, y2 }
        }

        // 缩放后,图片需要的位移
        getXY = (s, x, y, x2, y2) => {
            // 缩放后的位置
            let x3 = x2 * s
            let y3 = y2 * s

            // 缩放后的位置移动到鼠标位置,需要的位移
            let x4 = x - x3
            let y4 = y - y3

            return { x4, y4 }
        }

        // 设置图片位置
        const setPosition = (el, x4, y4, s) => {
            el.style.left = `${x4}px`
            el.style.top = `${y4}px`
            el.style.transform = `scale(${s})`
        }

        img.addEventListener('mousewheel', (e) => {
            const { x, y, deltaY } = e

            s += deltaY * -0.01
            s = Math.min(Math.max(minL, s), maxL)

            // 放大
            if (deltaY < 0) {
                let { x2, y2 } = getSourePosition(img, s - 1, x, y)

                let { x4, y4 } = getXY(s, x, y, x2, y2)

                if (lasts < maxL) {
                    lasts = s
                    setPosition(img, x4, y4, s)
                }
            }
            // 缩小
            if (deltaY > 0) {
                let { x2, y2 } = getSourePosition(img, s + 1, x, y)

                let { x4, y4 } = getXY(s, x, y, x2, y2)

                if (lasts > minL) {
                    lasts = s
                    setPosition(img, x4, y4, s)
                }
            }
        })
    </script>
</body>

</html>

第三版

  • 第二版其实已经实现了我们所需要的功能

  • 但是每次缩放我们都需要去获取,设置图片的lefttop,

  • lefttop 属性的改变都会触发了布局操作,开销高。更好的解决方案是在元素上使用 translate,因为它不会触发重新布局。

js 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            background:
                -webkit-linear-gradient(top, transparent 99px, #ccc 99px),
                -webkit-linear-gradient(left, transparent 99px, #ccc 99px);
            background-size: 100px 100px;
        }


        .container {
            position: absolute;
            top: 0;
            left: 0;
            width: 200px;
            height: 200px;
        }

        img {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            object-fit: cover;
            transform-origin: 0% 0%;
            transform: translate(0px, 0px) scale(1);
        }
    </style>
</head>

<body>
    <div class="container">
        <img id="img" src="https://t7.baidu.com/it/u=1653814446,2847580380&fm=193&f=GIF" alt="">
    </div>
    <script>
        let s = 1//缩放倍率
        let lasts = 1
        let minL = 1
        let maxL = 20

        const img = document.getElementById('img')

        const getTransLate = (transform) => {

            let tx = 0, ty = 0
            if (transform === '') {
                return { tx, ty }
            } else {
                tx = transform.split(' ')[0].replaceAll('translate(', '').replaceAll('px,', '')
                ty = transform.split(' ')[1].replaceAll('px)', '')
                return { tx, ty }
            }

        }
        // 缩放前,鼠标位置相对图片缩放为1时的位置
        const getSourePosition = (el, s, x, y) => {
            let { tx, ty } = getTransLate(el.style.transform)

            let x2 = (x - tx) / s
            let y2 = (y - ty) / s

            return { x2, y2 }
        }

        // 缩放后,图片需要的位移
        getXY = (s, x, y, x2, y2) => {
            // 缩放后的位置
            let x3 = x2 * s
            let y3 = y2 * s

            // 缩放后的位置移动到鼠标位置,需要的位移
            let x4 = x - x3
            let y4 = y - y3

            return { x4, y4 }
        }

        // 设置图片位置
        const setPosition = (el, x4, y4, s) => {
            el.style.transform = `translate(${x4}px, ${y4}px) scale(${s})`
        }

        img.addEventListener('mousewheel', (e) => {
            const { x, y, deltaY } = e

            s += deltaY * -0.01
            s = Math.min(Math.max(minL, s), maxL)

            // 放大
            if (deltaY < 0) {
                let { x2, y2 } = getSourePosition(img, s - 1, x, y)

                let { x4, y4 } = getXY(s, x, y, x2, y2)

                if (lasts < maxL) {
                    lasts = s
                    setPosition(img, x4, y4, s)
                }
            }
            // 缩小
            if (deltaY > 0) {
                let { x2, y2 } = getSourePosition(img, s + 1, x, y)

                let { x4, y4 } = getXY(s, x, y, x2, y2)

                if (lasts > minL) {
                    lasts = s
                    setPosition(img, x4, y4, s)
                }
            }
        })
    </script>
</body>

</html>

总结

  • 网上大佬的实现都是直接计算缩放后的tanslate

  • 我的实现是先将鼠标位置映射到缩放为1时,再缩放s倍,再回到鼠标位置

  • 初始将图片缩放中心设为0px 0px会节省一些计算

  • transform-origin不要动

相关推荐
崔庆才丨静觅10 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax