先定一个小目标:放大镜🔍效果实现

3,371 阅读4分钟

需求描述:

最近接到一个小需求,由于页面的显示空间有限,所以有些高清的大图不得不缩放了显示,但是又想要看清楚每一个细节。如果利用弹窗,显示完整图片,然后出滚动条。这样的话图片太大就需要不停的滚动,不是很方便。所以想到了放大镜的效果,鼠标悬浮在哪儿就出现哪个局部的方法。想到了之前写过的利用canvas实现自定义头像功能,里面也有放大局部的功能,不过这次我不想用canvas了,哈哈,话不多说,直接开搞吧。

成果展示:

放大效果demo.gif

抛出疑问:

看到效果展示,大家应该或多或少都有点思路吧。这里我抛出几个问题:鼠标进入目标区域,展示出放大镜,鼠标离开目标区域后放大镜消失,但是放大镜一出现鼠标实际上就离开了目标区域这个时候放大镜就消失了,然后又进入了目标区域,放大镜又出现了,哈哈,已经有画面了吧,这个怎么处理呢?放大镜内部展示的“放大”效果是如何做到的呢?在看下面的文章之前大家可以先行思考下(哈哈哈,思考完自己就会写了,但是还是留个赞再走哈)

实现思路:

1.放大镜在目标位置的出现与消失; 2.放大镜内部的放大效果; 3.如果是移动端,需要怎么处理。

放大镜在目标位置的出现与消失

放大镜的出现比较简单,鼠标悬浮在目标区域,就让放大镜出现。鼠标离开悬浮区域,放大镜就消失。但是像之前在疑问环节抛出的问题,这个时候放大镜就会不停的在“出现”与“消失之间徘徊”。这里我的解决办法就是多给一层蒙版,在鼠标进入目标区域,会在目标区域一样的位置“上空”(z-index来控制层级)出现一层蒙版,按照目标区域、放大镜、蒙版这样的先后层级去展示,这样蒙版会在最上方。然后我们的mosuemove事件是绑定在蒙版上面的,而且鼠标离开蒙版区域放大镜和蒙版自身才会消失。这样的话,蒙版岂不是遮住了放大镜,简单通过设置蒙版的opacity属性即可解决。下面上代码(只放html和js哈,最后面会有完整代码):

html:
    <div class="container">
        <!-- 目标容器 -->

        <div class="inner">
            <img src="./imgs/small.jpeg" />
        </div>
    </div>
    <!-- 蒙版容器 -->
    <div class="zoom-img-overlay"></div>
js:
const $zoomOverlay = $('.zoom-img-overlay');
const $originContainer = $('.inner');
const $originImg = $originContainer.find('img');
$originImg
    .on('mouseenter', function(ev) {
        $zoomOverlay.css({
            left: $originImg.offset().left,
            top: $originImg.offset().top,
            width: $originImg.width(),
            height: $originImg.height(),
            display: 'block'
        })
        $zoomHolder.show();
    })

放大效果实现

之所以看不清目标图,是因为受限于目标容器,对原图进行了缩放,所以我们只要想办法在放大镜区域内看到原图的相应区域,也就实现了“放大”的效果。那么怎么才能知道在放大镜中需要放入的区域对应的是原图的那一部分呢。我们以目标区域左上角为坐标原点,按照目标区域的大小和原图的大小比例,将鼠标向左和向下移动的距离,等比的进行放大,是不是就得到了这个时候鼠标对应的原图中的位置,哈哈哈,是不是很简单,下面上代码:

html:新增一个原图放置容器
<div class="zoom-holder">
        <div class="img_wrapper">
            <img src="./imgs/big.jpeg" />
        </div>
    </div>
js:
const $zoomHolder = $('.zoom-holder');
    const $zoomOverlay = $('.zoom-img-overlay');
    const $originContainer = $('.inner');
    const $originImg = $originContainer.find('img');
let orgW, orgH, newW, newH; //目标宽、目标高、原图宽、原图高

    const orgImg = new Image();
    orgImg.onload = function() {
        console.log(orgImg.width, 'loaded')
        orgW = orgImg.width;
        orgH = orgImg.height;
    }
    orgImg.src = $originImg[0].src;
    const newImg = new Image();
    newImg.onload = function() {
        newW = newImg.width;
        newH = newImg.height;
    }
    newImg.src = $zoomHolder.find('.img_wrapper img')[0].src;
    
        $zoomOverlay
    .on('mouseleave', function(ev) {
        $zoomHolder.hide();
        $zoomOverlay.css({'left': 'auto', 'top': 'auto', 'width': 'auto', 'height': 'auto',display : "none"});
    })
    .on('mousemove', function(ev) {
        const moveX = ev.pageX - parseInt($zoomOverlay.css('left'));
        const moveY = ev.pageY - parseInt($zoomOverlay.css('top'));
        const lastX = moveX / (orgW/newW);
        const lastY = moveY / (orgH/newH);

        $zoomHolder.css({
            left: ev.pageX - 150,
            top: ev.pageY - 150
        })
        $zoomHolder.find('.img_wrapper img').css({
            left: -(lastX-150),
            top: -(lastY-150)
        })
    })
    

这里我们需要注意的是,要提前拿到目标区域宽高、原图的宽高,在放大镜超出的范围内,通过overflow:hidden隐藏掉原图超出的部分,这样就会只呈现放大区域内原图的效果。

移动端效果复刻

如果这个效果想要在移动端实现呢,其实原理都一样,下面我总结下几个要注意的点:

  1. 事件绑定修改,不再是mouseenter和mousemove,取而代之的是touchstart和touchend事件,即在我们touch目标区域的时候,放大镜就会出现在相应的地方;
  2. 在touchstart的时候,就需要进行一次原图的位置移动计算,因为这个时候如果只是出现放大镜,那么里面的放大区域和touch的区域对应不上;
  3. pageX和pageY的计算发生变化,通过ev.originalEvent.changedTouches[0].pageX和ev.originalEvent.changedTouches[0].pageY分别获得。

写在最后:

以上就是放大镜效果实现的全部内容,现在来看是不是很简单,需要大家对鼠标位置相关的属性比较熟悉。当然有些性能优化的点,比如防抖之类的需要大家后面自己去实现,以及最后可以封装成对应的放大组件,哈哈哈,相信大家自己一定可以的做到的。

相关链接:

codesandbox线上地址