如何实现一个丝滑的放大镜效果

思路

  • 一个容器包裹两张图片
  • 一份静态图片一直放着
  • 一份动态移动位置的图片 外面包裹一层作为放大镜 设置溢出隐藏
  • 绑定移动事件 让图片和图片放大镜移动位置相反即可

效果

由于gif帧率问题,看着卡,实际丝滑无比 msedge_wGbUGUQ3y3.gif

实现

a {
    display: block;
    position: absolute;
    left: 0;
    top: 0;
    text-decoration: none;
    color: inherit;
}

.container {
    position: relative;
    margin: 20px;
}

.move {
    display: none;
    position: absolute;
    left: 0;
    top: 0;
    box-shadow: 0 0 5px #ccc;
    z-index: 2;
    border-radius: 3px;
    overflow: hidden;
}

<div class="container">
    <a href="https://juejin.cn/user/3936714046056088/posts" class="move">
        <img src="https://i0.hdslb.com/bfs/vc/c13315f4c4195b342fd0d2795fd6c8b090a717bf.jpg" alt="">
    </a>
    <a href="https://juejin.cn/user/3936714046056088/posts" class="static-img">
        <img src="https://i0.hdslb.com/bfs/vc/c13315f4c4195b342fd0d2795fd6c8b090a717bf.jpg" alt="">
    </a>
</div>

配置文件

 const config = {
    w: 640,
    h: 360,
    moveHeight: 60,
    moveWidth: 60,
    scale: 2.2
};

分别是图片大小,放大镜大小,缩放大小

获取DOM

const container = document.querySelector('.container'),
    staticImg = container.querySelector('.static-img img'),
    move = container.querySelector('.move'),
    moveImg = move.querySelector('img');

初始化配置

function setSize() {
    staticImg.style.width = config.w + 'px';
    staticImg.style.height = config.h + 'px';
    container.style.height = config.h + 'px';
    container.style.width = config.w + 'px';

    move.style.width = config.moveWidth + 'px';
    move.style.height = config.moveHeight + 'px';
    move.style.transform = `scale(${config.scale})`;

    moveImg.style.width = config.w + 'px';
    moveImg.style.height = config.h + 'px';
}

事件

  • 首先需要显示和隐藏放大镜
function setVisible() {
    const flag = getComputedStyle(move).display === 'none'
        ? 'block'
        : 'none';

    move.style.display = flag;
}

container.addEventListener('mouseover', setVisible);
container.addEventListener('mouseout', setVisible);

通过取反的方式,复用函数

  • 接下来是重点了,处理移动逻辑,先写俩辅助函数
function getRect(el) {
    return el.getBoundingClientRect();
}

function getStyle(el, key) {
    return parseInt(getComputedStyle(el)[key]);
}
const { left: _left, top: _top } = getRect(container),
    halfWidth = getStyle(move, 'width') / 2,
    halfHeight = getStyle(move, 'height') / 2;
    
container.addEventListener('mousemove', (e) => {
    const { clientX, clientY } = e;
    const x = clientX - _left - halfWidth,
        y = clientY - _top - halfHeight;

    move.style.transform = `translate(scale(${config.scale}) ${x}px, ${y}px)`;
    moveImg.style.transform = `translate(${-x}px, ${-y}px)`;
});

经典问答环节

你为什么要搞个getBoundingClientRect,很帅吗?offsetLeft不能用吗??

  • 因为getBoundingClientRect是获取全部矩形的属性,包括left、translate等等

那你为什么要搞个getComputedStyle,很帅吗?el.style.width不能用吗??

  • el.style.width不能只能获取行内样式,不包括类的样式
  • el.style.width不能获取隐藏元素的样式

这里用鼠标坐标 - 容器偏移位置得到移动位置

注意,还要减去图片一半的大小,因为坐标在中心

现在能用吗??

不能,让你看看先

msedge_Wobo7cy21G.gif

为什么偏移越来越多呢?

因为transform属性是有顺序的,这里先放大再移动,所以改一下即可

move.style.transform = `translate(${x}px, ${y}px) scale(${config.scale})`;

能用了吗??

还是不能,超出边界了怎么办

msedge_dsXBVqih6M.gif

function judge(x, y) {
    return (
        x < -halfWidth || y < -halfHeight ||
        x > container.offsetWidth - halfWidth ||
        y > container.offsetHeight - halfHeight
    );
}

 container.addEventListener('mousemove', (e) => {
    const { clientX, clientY } = e;
    const x = clientX - _left - halfWidth,
        y = clientY - _top - halfHeight;

    if (judge(x, y)) {
        return;
    }

    move.style.transform = `translate(${x}px, ${y}px) scale(${config.scale})`;
    moveImg.style.transform = `translate(${-x}px, ${-y}px)`;
});

加个判断边界即可,允许超出一半,大功告成

源码 gitee.com/cjl2385/dig…

全部评论

相关推荐

2025-11-05 10:55
中南大学 Java
要双修的猫头鹰:这面试官怕不是个m
我来点评面试官
点赞 评论 收藏
分享
2025-12-28 09:59
复旦大学 Java
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务