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

news2024/11/24 19:47:59

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

为了方案的通用性,这回使用基本的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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/717569.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何在Windows 10和Linux上安装nvm

在Windows 10上安装nvm&#xff1a; 访问nvm的GitHub页面&#xff08;https://github.com/coreybutler/nvm-windows/releases &#xff09;&#xff0c;下载最新的安装程序。运行下载的安装程序&#xff0c;按照提示进行安装。选择合适的安装路径。完成安装后&#xff0c;打开…

你对未来Android 车载开发市场怎么看?

前阵子看到一个新闻说&#xff1a;“中国进口车销量创10年来新低 “产业变革效应”正在显现”&#xff0c;于是我的好奇心让我点开看了一下 原来是国产的新能源汽车的崛起&#xff0c;受到了不少国内外客户的欢迎&#xff0c;使得订单量暴涨&#xff0c;出口的数量反超进口车数…

【python】jupyter notebook的快捷键

Jupyter Notebook 提供了许多快捷键以方便操作。这些快捷键分为两类&#xff1a;命令模式快捷键和编辑模式快捷键。 命令模式快捷键&#xff1a; 在命令模式下&#xff0c;细胞边框是蓝色的。这些快捷键主要用于操作单元格。 Enter&#xff1a;进入编辑模式Shift Enter&…

MBD开发 STM32 ADC

在cubemx配置好adc&#xff0c;和串口(阻塞) function [nbChar,buff] convert(u)if coder.target(Sfun)buff uint32(0);nbChar uint16(0); elsecoder.cinclude(stdio.h);coder.cinclude(main.h);coder.cinclude(getBuffPtr.h);string char(zeros(1,20));stringSize uint16…

3F倾听模型

3F倾听模型 3F倾听模型|苏格拉底说&#xff1a;“上天赐给每个人两只耳朵&#xff0c;而只有一张嘴巴&#xff0c;就是要求人们多听&#xff0c;少说话。” 模型介绍 运用心得 到底听什么&#xff1f; 倾听听事实&#xff0b;感受情绪&#xff0b;理解对方意图 1、分清事实…

48 # 单向链表

上一节讲了可读流&#xff0c;在讲可写流之前得了解一下链表。 比如&#xff1a;并发往文件中写入数据 write("1"); write("2"); write("3");每次写都会开个线程&#xff0c;上面的写入可能出现 123&#xff0c;321&#xff0c;213… node 中…

EMQX在Windows系统下的开机自启与异常自动重启脚本

0.前言 由于为某万年老项目做运维&#xff0c;但源码遗失以及项目遗留问题导致emqx经常崩溃&#xff0c;故无法追根溯源&#xff0c;迫于无奈才不得已出此下策&#xff0c;以定时监测并自动重启EXMQ为临时解决方案&#xff0c;若能一劳永逸当然再好不过&#xff0c;各位友友们自…

GS DynamicMeshActor

GS DynamicMeshActor GeometryScript DynamicMeshActor。 ADynamicMeshActor与AStaticMeshActor类似之处在于&#xff0c;它主要是UDynamicMeshComponent的容器。但是&#xff0c;DynamicMeshActor确实为想要基于UDynamicMesh实现程序化网格体生成的Actor蓝图提供了一些特定支…

【C语言】图文解析,深入浅出汉诺塔问题

君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello,这里是君兮_&#xff0c;今天更新一篇我一直想更但是一直没空写的博客——汉诺塔问题&#xff0c;在所有有关递归的问题中&#xff0c;汉诺塔问题是最经典的问题之一&#xff0c;可以说&#xff0c;如果你能真正理…

GitGithub 上手就是这么简单!

Git 和 Github 对程序员或计算机爱好者来说肯定都不陌生&#xff0c;这个不多说&#xff01;下面要做的就是带你光速掌握 git 和 github 的日常操作&#xff0c;让你做到心中有数。 我们为什么要学习Git&Github&#xff1f;嗯… 相信你是带着目的学习的&#xff01; 不过…

Nginx三大核心功能

一、反向代理&#x1f349; (1)什么是正向代理&#x1f95d; 概念 正向代理是一个位于客户端和目标服务器之间的代理服务器&#xff08;中间服务器&#xff09;。为了从目标服务器取得内容&#xff0c;客户端向代理服务器发送一个请求&#xff0c;并且指定目标服务器&#xf…

Lion:闭源大语言模型的对抗蒸馏

Lion&#xff1a;闭源大语言模型的对抗蒸馏 Lion&#xff0c;由香港科技大学提出的针对闭源大语言模型的对抗蒸馏框架&#xff0c;成功将 ChatGPT 的知识转移到了参数量 7B的 LLaMA 模型&#xff08;命名为 Lion&#xff09;&#xff0c;在只有 70k训练数据的情况下&#xff0…

白嫖一份小白到进阶网络安全学习宝典【建议收藏】

前言 想学网络安全但是无从下手的小白看过来&#xff0c;非常系统的学习资料&#xff0c;无数小白看了这份资料都已经成功入门&#xff0c;涵盖多个网络安全知识点&#xff0c;我愿称之为网络安全自学宝典。 一、概念性知识 1、了解什么是网络安全 2、清楚法律法规 3、网络安…

【ElatsticSearch】ES索引库与文档的增删改查

文章目录 一、操作索引库1、mapping映射属性2、索引库的CRUD 二、文档操作1、新增文档2、查询文档3、删除文档4、修改文档5、注意点 一、操作索引库 1、mapping映射属性 mapping是对索引库中文档的约束&#xff0c;常见的mapping属性包括&#xff1a; type&#xff1a;字段数…

【计算机视觉 | 图像分割】arxiv 计算机视觉关于图像分割的学术速递(7 月 4 日论文合集)

文章目录 一、分割|语义相关(18篇)1.1 TomatoDIFF: On-plant Tomato Segmentation with Denoising Diffusion Models1.2 CGAM: Click-Guided Attention Module for Interactive Pathology Image Segmentation via Backpropagating Refinement1.3 RefSAM: Efficiently Adapting …

Python实现通过GUI界面提交参数,来启动python脚本

前言 本文是该专栏的第30篇,后面会持续分享python的各种干货知识,值得关注。 假设现在有如下需求,用python写一个GUI界面,在该GUI界面上输入参数信息,然后再点击GUI界面上的提交按钮,启动并执行对应的python脚本。换言之,在GUI界面上输入的参数信息传递给对应的python脚…

图像处理常用算法(基础)

同图像灰度不同,边界处一般会有明显的边缘,利用此特征可以分割图像。需要说明的是:边缘和物体间的边界并不等同,边缘指的是图像中像素的值有突变的地方,而物体间的边界指的是现实场景中的存在于物体之间的边界。有可能有边缘的地方并非边界,也有可能边界的地方并无边缘,…

【动态规划算法练习】day14

文章目录 一、97. 交错字符串1.题目简介2.解题思路3.代码4.运行结果 二、712. 两个字符串的最小ASCII删除和1.题目简介2.解题思路3.代码4.运行结果 三、718. 最长重复子数组1.题目简介2.解题思路3.代码4.运行结果 总结 一、97. 交错字符串 1.题目简介 97. 交错字符串 给定三个…

嵌入式如何面试10家公司,拿到9个offer的?

又快到一年一度的秋招季&#xff0c;不少同学私信学长吐苦水&#xff0c;明明在各大招聘网站上海投了那么多家公司&#xff0c;收到的面试通知却屈指可数&#xff0c;好不容易拿到面试机会&#xff0c;却在一面就扑街...... 很多同学能力还行&#xff0c;但是经验谈不上很出彩&…

【IMX6ULL驱动开发学习】15.IMX6ULL驱动开发问题记录(sleep被kill_fasync打断)

发现问题的契机&#xff1a; 学习异步通知的时候&#xff0c;自己实现一个功能&#xff1a;按键控制蜂鸣器&#xff0c;同时LED灯在闪烁 结果&#xff1a;LED好像也同时被按键控制了 最后调试结果发现&#xff1a; 应用层的sleep被驱动层的kill_fasync打断&#xff0c;所以sle…