教你实现图片的点击缩放和移动

文章介绍了如何使用基本的HTML和JavaScript实现图片点击后放大并在页面中移动的功能,包括滚轮缩放和鼠标拖动移动图片。通过创建遮罩层和动态调整图片的transform属性来实现这些交互效果。同时,解决了鼠标抬起时的点击与移动冲突问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

教你实现图片的点击缩放和移动

为了方案的通用性,这回使用基本的html+js进行操作,vue和react使用方法类似,几乎不需要进行什么语法转换操作,注意一下点击事件在自己框架里的写法即可

随便来写一个简单的页面:

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div>
    <p class="list">
      文本内容1
    </p>
    <img onclick="imgClick(event)" 
         src="./img/img.JPG" 
         width="150" 
         height="150" 
         style="object-fit: cover;">
  </div>
</body>
</html>
<style>
  body {
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
  }
</style> 

大概长这个样子:

预览

一般项目中缩略图都是固定尺寸,但是原始尺寸不规则,防止缩小后伸缩异常,最好加上object-fit: cover;属性,如果感兴趣可以参考文档

既然点击放大,那么我们首先来加上点击事件

点击事件

<img onclick="imgClick(event)">
<!-- 其余代码省略 -->
<script>
  function imgClick(e) {
    console.log(e);
  }
</script>

点击事件对象继承了Event,所以你可以通过e.target获取dom上的信息

在进行图片放大的时候,我们最需要的就是img标签上的src图片路径,我们这里新创建图片放大的工具方法,并把图片的路径作为参数。

  function imgClick(e) {
    showImagePreview(e.target.src)
  }

  function showImagePreview(url) {
    // 图片放大工具方法
  }

那么点击放大的方法,实际就是生成两个dom元素,一个遮罩层还有一个不限制宽高的图片标签,按照这种思路,我们来写一个最基本的放大方法:

  // 外部定义dom元素对象,方便后续缩放移动使用
  let div = null
  let img = null
  function showImagePreview(url) {
    div = document.createElement("div")
    div.style = {
      position: "fixed",
      top: "0",
      bottom: "0",
      left: "0",
      right: "0",
      backgroundColor: "rgba(0,0,0,0.8)",
      display: "flex",
      justifyContent: "center",
      alignItems: "center",
      zIndex: "1000",
    }

    img = document.createElement("img")
    img.src = url
    img.style = {
      position: "fixed",
    }
    div.appendChild(img)
    // 遮罩层加一个点击事件,点击遮罩删除元素,回到初始画面
    div.onclick = () => document.body.removeChild(div);
    document.body.appendChild(div)
  }

这里生成一个div作为遮罩层,并添加了简单样式,然后又生成了img标签,并将上面获取到的图片路径给它,最后把img扔到遮罩上,遮罩再扔到body上,现在点击已经可以实现图片放大效果了。如果你使用前端框架,那么上面也可以使用jsx封装组件。

滚轮缩放图片

首先我们要知道鼠标滚轮事件是[wheel](Element:滚轮事件 - Web API 接口参考 | MDN),在浏览器里鼠标滚轮事件一般都是有默认行为的,那就是对滚动条的操作,所以首要就是阻止默认事件的触发。

不过还要想一想,滚轮事件要加到遮罩上,还是加到图片上,如果加到图片上,你的鼠标必须要放到图片内部滚动才会触发,如果图片默认尺寸非常小,这操作无疑是非常难受的,所以我们把滚轮事件放到遮罩层上,从而在任意位置滚动都可以影响图片缩放。

 // 缩放倍数,默认100%
 let scale = 1
 function showImagePreview(url) {
    const div = document.createElement("div")
    // ...
    // img标签添加一个缩放的属性
    img.style = {
      position: "fixed",
      transform: `scale(${scale})`
    }
    // ...
    // 遮罩层添加滚轮事件
    div.onwheel = (e) => zoom(e)
    // 等同于下面这种写法
    // div.addEventListener("wheel", (e) => zoom(e), { passive: false })
 }
 // 新建缩放操作方法
 function zoom(wheelEvent) {
    // 阻止滚轮的默认行为
    wheelEvent.preventDefault()
    // 根据滚轮事件对象的daltaY来判断向上滚动还是向下滚动
    if (wheelEvent.deltaY > 0) {
      scale = scale * 0.9
    } else {
      scale = scale * 1.1
    }
    // 对img的缩放重新设定
    img.style.transform = `scale(${scale})`
  }

deltaY就是鼠标滚轮Y轴方向的滚动量,向上滚动为负数,向下滚动为正数

图片移动操作

拖动图片在遮罩上移动,显而易见,鼠标的mousedownmouseup事件应该要在图片上触发,但是移动事件mousemove需要在遮罩层上触发,到这里其实会有个问题,最上面的代码里我们有一个click事件用来清除遮罩层返回初始页面,这里会和mouseup事件冲突,导致移动完毕后松开鼠标,遮罩层会直接消失,不过我们暂时先注释掉上面的click事件,先来专心做移动操作。

移动操作的原理,就是通过移动事件来计算图片的lefttop值,重新进行定位,所以我们不妨先来分析一下这两个值该怎么计算

示意图

在这个案例里,我们设定图片是屏幕居中,所以相对于遮罩层的原点就在中间位置,我们假定在1的位置移动到了2的位置,1相对浏览器左上角的距离clientXclientY,以及相对于原点位置的lefttop都是已知的,在移动到2的位置后,也可以根据鼠标的移动事件对象获取到最新为止的clientXclientY,那么现在根据这些已知条件,来计算黄色矩形的边长,是不是很简单了,各位读者可以自己思考一下,亦可以直接看下面代码。

我们新创建函数

  function showImagePreview(url) {
    // ...
    // 绑定两个事件
    img.onmousedown = (e) => imgMouseDown(e)
    img.onmouseup = (e) => imgMouseUP(e)
    // ...
  }

  // 鼠标落下
  function imgMouseDown(downEvent) {
    // 阻止默认选中的行为
    downEvent.preventDefault()
    // 获取当前点击时刻的图片left和top值
    const rect = window.getComputedStyle(img, null)
    // 以下两种写法均可,仅做为演示
    // 因为获取的值是例如 10px 这种的字符串,所以使用parseInt直接转为数字,方便后续计算
    let leftNum = parseInt(rect.getPropertyValue("left"))
    let topNum = parseInt(rect.top)
    // 点击后为遮罩层绑定鼠标移动事件,注意是遮罩层的事件
    div.onmousemove = (moveEvent) => {
      // 移动后的client坐标 - 移动前的client坐标 + 移动前的left、top,就是最新的left和top
      // 不要忘记加px单位,否则无法展示
      img.style.top = moveEvent.clientY - downEvent.clientY + topNum + "px";
      img.style.left = moveEvent.clientX - downEvent.clientX + leftNum + "px";
    }
  }
  // 松开鼠标
  function imgMouseUP(e) {
    // 将遮罩层的鼠标移动事件清空,防止松开鼠标后图片依然跟随鼠标移动
    div.onmousemove = null
  }

关于getComputedStyle可以参考Window.getComputedStyle() - Web API 接口参考 | MDN

这里也可以使用img.style.left来获取,但是这种方法只能获取行内样式,很有可能会获取不到值

解决mouseup与click冲突

click事件会在mousedownmouseup后触发,也就是没有办法通过改变改变状态来控制click是否触发,而且这个click是遮罩层的事件,阻止事件冒泡也没有什么作用。所以一般常见的方式,就是判断鼠标落下和抬起的时间差,时间差大于200ms认为是拖动,反之则认为是点击,这样按下和抬起操作之后,click可以根据结果来判断是否执行,

所以我们在showImagePreview函数里再为遮罩层绑定mousedownmouseup,并修改click事件的写法:

  // 记录初始点击时间
  let startTime = 0
  // 区分是否为点击
  let isClick = true
  function showImagePreview(url) {
    //...
    // 记录点击初始时间
    div.onmousedown = (e) => startTime = e.timeStamp
    // 遮罩层鼠标抬起,主要记录时间差是否大于200ms
    div.onmouseup = (e) => divMouseUp(e)
    // click最后触发,根据时间差的结果判断是否要触发
    div.onclick = () => {
      if (isClick) {
        document.body.removeChild(div)
      }
    };
    //...
  }
  
  function divMouseUp(e) {
    // 时间差超过200ms不执行点击事件
    if (e.timeStamp - startTime > 200) {
      isClick = false
    } else {
      isClick = true
    }
  }
  

至此所有功能均实现完毕

下面是全部代码:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div>
    <p class="list">
      文本内容1
    </p>
    <img onclick="imgClick(event)" src="./img/img.JPG" width="150" height="150" style="object-fit: cover;">
  </div>
  <!--  -->
</body>

</html>
<script>
  function imgClick(e) {
    showImagePreview(e.target.src)
  }
  // 遮罩层对象
  let div = null
  // 图片对象
  let img = null
  // 缩放倍数
  let scale = 1
  // 记录初始点击时间
  let startTime = 0
  // 区分是否为点击
  let isClick = true
  // 图片放大函数
  function showImagePreview(url) {
    // 创建遮罩
    div = document.createElement("div")
    div.style.position = "fixed";
    div.style.top = "0";
    div.style.bottom = "0";
    div.style.left = "0";
    div.style.right = "0";
    div.style.backgroundColor = "rgba(0,0,0,0.8)";
    div.style.display = "flex";
    div.style.justifyContent = "center";
    div.style.alignItems = "center";
    div.style.zIndex = "1000";
    // 遮罩层鼠标滚轮事件
    div.onwheel = (e) => zoom(e)
    // 记录点击初始时间
    div.onmousedown = (e) => startTime = e.timeStamp
    // 遮罩层鼠标抬起,主要记录时间差是否大于200ms
    div.onmouseup = (e) => divMouseUp(e)
    // click最后触发,根据时间差的结果判断是否要触发
    div.onclick = () => {
      if (isClick) {
        document.body.removeChild(div)
      }
    };
    // 创建图片
    img = document.createElement("img")
    img.src = url
    img.style.position = "relative"
    img.style.transform = `scale(${scale})`
    // 图片移动操作
    img.onmousedown = (e) => imgMouseDown(e)
    img.onmouseup = (e) => imgMouseUP(e)
    // div.addEventListener("wheel", (e) => zoom(e), { passive: false })
    div.appendChild(img)
    document.body.appendChild(div)
  }
  // 图片缩放操作函数
  function zoom(wheelEvent) {
    wheelEvent.preventDefault()
    if (wheelEvent.deltaY > 0) {
      scale = scale * 0.9
    } else {
      scale = scale * 1.1
    }
    img.style.transform = `scale(${scale})`
  }
  
  function imgMouseDown(downEvent) {
    downEvent.preventDefault()
    const rect = window.getComputedStyle(img, null)
    let leftNum = parseInt(rect.getPropertyValue("left"))
    let topNum = parseInt(rect.top)
    div.onmousemove = (moveEvent) => {
      img.style.top = moveEvent.clientY - downEvent.clientY + topNum + "px";
      img.style.left = moveEvent.clientX - downEvent.clientX + leftNum + "px";
    }
  }
  // 便于理解单独抽离
  function imgMouseUP(e) {
    div.onmousemove = null
  }

  function divMouseUp(e) {
    if (e.timeStamp - startTime > 200) {
      isClick = false
    } else {
      isClick = true
    }
  }
</script>
<style>
  body {
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
  }
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cRack_cLick

感谢你的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值