记录--使用 JS 实现基本的截图功能

news2024/10/7 16:20:39

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

思路分析

在开始动手之前,分析一下整个功能的实现过程:

  1. 根据图片大小创建 canvas1 画布,并将原图片直接定位在 canvas1 上;

  2. 在画布上添加一个蒙层,以区分当前 canvas 图像是被裁剪的原图像;

  3. 在蒙层上方,对裁剪区域(鼠标移动形成的矩形范围)再次进行图像绘制;

  4. 获取裁剪区域的数据,并将该数据定位到另一个 canvas 画布上。

实现过程

准备工作

首先,编写所需的 HTML 结构并获取对应元素。

<body>
  <!-- 上传文件 -->
  <input type="file" id="imageFile" accept="image/*">
  <!-- 保存被裁剪的原图像,初始样式需要设置 display: none -->
  <div class="canvasContainer1">
    <canvas id="canvas1"></canvas>
  </div>
  <!-- 保存裁剪区域的图像,初始样式需要设置 display: none -->
  <div class="canvasContainer2">
    <canvas id="canvas2"></canvas>
  </div>
</body>

<script>
const imageFile = document.querySelector('#imageFile');
const canvasContainer1 = document.querySelector('.canvasContainer1');
const canvasContainer2 = document.querySelector('.canvasContainer2');
const canvas1 = document.querySelector('#canvas1');
const canvas2 = document.querySelector('#canvas2');
const ctx = canvas1.getContext('2d');
const ctx2 = canvas2.getContext('2d');

const imageBox = new Image(); // 创建一个存放图片的容器
</script>

绘制原图像

我们需要监听 input 元素的 change 事件,以获取上传图片的相关参数,这里主要是为了获取图片的宽度和高度。

我们创建一个 FileReader() 对象并监听其 load 事件。load 事件在读取操作成功后立刻执行,在这个方法中我们就可以获取图片的宽高。

function init() {
  imageFile.addEventListener('change', handleFileChange, false); // 监听图片上传事件。
}

function handleFileChange(e) {
  const imgFile = e.target.files[0]; // 获取上传的图片对象。

  const reader = new FileReader();
  reader.onload = function(e) {
    const imgSrc = e.target.result; // 图片文件的 base64 编码格式。
    imageBox.src = imgSrc; // 把图片放入 img 容器。

    // 等图片加载完成后,获取图片的宽高。
    imageBox.onload = function () {
      const imgWidth = this.width, imgHeight = this.height;
      console.log(imgWidth, imgHeight);
    }
  }
  if (imgFile) {
    reader.readAsDataURL(imgFile); // 读取图片文件,读取完成才能获取 result 属性。
  }
}

init();

此时还没有图片,我们创建一个自适应图片大小的  canvas1 画布,并使用  drawImage() 方法将上传的图片直接定位到  canvas1 当中。
function handleFileChange(e) {
  const imgFile = e.target.files[0]; // 获取上传的图片对象。

  const reader = new FileReader();
  reader.onload = function (e) {
    const imgSrc = e.target.result; // 图片的 base64 编码。
    imageBox.src = imgSrc; // 把上传的图像放入 img 容器。

    // 图片加载完毕后执行
    imageBox.onload = function () {
      // 获取图片的宽高。
      const imgWidth = this.width, imgHeight = this.height;
      console.log(imgWidth, imgHeight);
      
      // 创建 canvas 画布并绘制图片。
      generateCanvas(canvasContainer1, canvas1, imgWidth, imgHeight);
      ctx.drawImage(imageBox, 0, 0, imgWidth, imgHeight);
    }
  }
  if (imgFile) {
    reader.readAsDataURL(imgFile); // 将当前file读取成DataURL
  }
}

// 根据 width 和 height 创建 canvas 画布。
function generateCanvas(container, canvas, width, height) {
  container.width = width + 'px';
  container.height = height + 'px';
  canvas.width = width;
  canvas.height = height;
  container.style.display = 'block'; // 显示 canvas 区域。
}

可以看到原图像已经成功被绘制,接下来就可以开始动态绘制截图区域了。

绘制截图区域

在这个过程中,我们需要分别监听 imageBox 容器(原图像)上的 mousedownmousemovemouseup 事件,这些事件的作用如下:

  • mousedown 事件:记录开始截图的位置,并开始监听 mousemovemouseup 事件。
  • mousemove 事件:监听鼠标的偏移量,以计算裁剪区域的宽度和高度。
  • mouseup 事件:截图结束,注销监听 mousedownmousemove 事件,并绘制裁剪区域。
let startPosition = []; // 记录鼠标点击(开始截图)的位置。
let screenshotData = []; // 保存截取部分的相关信息。

function init() {
  // 监听鼠标点击事件。
  canvas1.addEventListener('mousedown', handleMouseDown, false);
}

// 记录鼠标点击(开始截图)的位置,并监听相关事件。
function handleMouseDown(e) {
  startPosition = [e.offsetX, e.offsetY];

  canvas1.addEventListener('mousemove', handleMouseMove, false);
  canvas1.addEventListener('mouseup', handleMouseUp, false);
}

// 监听鼠标的偏移量,以计算裁剪区域的宽度和高度。
function handleMouseMove(e) {
  // 获取裁剪区域的宽度和高度。
  const { offsetX, offsetY } = e;
  const [startX, startY] = startPosition;
  const [rectWidth, rectHeight] = [offsetX - startX, offsetY - startY];
  console.log('rect', rectWidth, rectHeight);
  
  // 保存裁剪区域的相关信息。
  screenshotData = [startX, startY, rectWidth, rectHeight];
}

// 注销监听事件等后续操作。
function handleMouseUp() {
  canvas1.removeEventListener('mousemove', handleMouseMove, false);
  canvas1.removeEventListener('mouseup', handleMouseUp, false);
}

在 handleMouseMove 函数中,我们已经获取了裁剪区域的宽高,也就是生成截图的宽高。

接下来,我们需要在原图像上展示出我们所裁剪的区域,也就是这个效果:

可以看到,原图像的上方、裁剪区域下方会覆盖一层半透明黑色蒙层,它的作用是区分原图层和裁剪部分图层。所以我们需要在绘制截图区域之前,添加一层蒙层。

注意,在已有内容的 canvas 画布上进行再次绘制之前,需要先清除整个画布的内容。 这里通过 clearRect() 方法清除 canvas1 画布上的所有内容,并添加蒙层。

我们继续来补充 handleMouseMovehandleMouseUp 函数中的逻辑:

const MASKER_OPACITY = 0.4;

function handleMouseMove(e) {
  // 获取裁剪区域的宽度和高度。
  const { offsetX, offsetY } = e;
  const [startX, startY] = startPosition;
  const [rectWidth, rectHeight] = [offsetX - startX, offsetY - startY];
  console.log('rect', rectWidth, rectHeight);
  // 保存裁剪区域的相关信息。
  screenshotData = [startX, startY, rectWidth, rectHeight];
  // 再次绘制前,清理 canvas1 画布上的内容。
  const { width, height } = canvas1;
  ctx.clearRect(0, 0, width, height);
  // 在 canvas1 画布上绘制蒙层。
  drawImageMasker(0, 0, width, height, MASKER_OPACITY);
  // 绘制截图区域。
  drawScreenShot(width, height, rectWidth, rectHeight);
}

// ...

// 绘制图片蒙层,填充范围和颜色,以便区分原图层和裁剪部分图层。
function drawImageMasker(x, y, width, height, opacity) {
  ctx.fillStyle = `rgba(0, 0, 0, ${opacity})`;
  ctx.fillRect(0, 0, width, height);
}

// 绘制裁剪的矩形区域。
function drawScreenShot(canWidth, canHeight, rectWidth, rectHeight) {
  // 在源图像外绘制新图像,只有源图像外的目标图像部分会被显示,源图像是透明的。
  ctx.globalCompositeOperation = 'destination-out';
  ctx.fillStyle = '#2c2c2c';
  ctx.fillRect(...startPosition, rectWidth, rectHeight);

  // 在现有画布上绘制新的图形。
  ctx.globalCompositeOperation = 'destination-over';
  ctx.drawImage(imageBox, 0, 0, canWidth, canHeight, 0, 0, canWidth, canHeight);
}

然后,当我们放开鼠标(结束截图动作)时,除了注销对 mousedownmousemove 事件的监听,还需要将所得的裁剪区域的图像放入另一个 canvas 中。

在绘制新图像的过程中,我们需要使用以下方法:

  • getImageData():读取 canvas 上的内容,返回一个 ImageData 对象,包含了每个像素的信息。
  • putImageData():将 ImagaData 对象的数据放入 canvas 中,覆盖 canvas 中的已有图像。
function handleMouseUp() {
  canvas1.removeEventListener('mousemove', handleMouseMove, false);
  canvas1.removeEventListener('mouseup', handleMouseUp, false);
  // 开始绘制截图区域图片。
  drawScreenshotImage(screenshotData);
  // 如果裁剪得到新图像后,不希望保留原图像,可以设置以下属性。
  // canvasContainer1.style.display = 'none';
}

// 在新容器 canvas2 上绘制新图像。
function drawScreenshotImage(screenshotData) {
  // 获取 canvas1 的数据。
  const data = ctx.getImageData(...screenshotData);
  // 创建 canvas2 画布。
  generateCanvas(canvasContainer2, canvas2, screenshotData[2], screenshotData[3]);
  // 每次绘制前,都先进行清除操作。
  ctx2.clearRect(...screenshotData);
  // 将 canvas1 的数据放入 canvas2 中。
  ctx2.putImageData(data, 0, 0);
}
经过以上步骤,就可以实现我们所需的效果

本文转载于:

https://juejin.cn/post/7264920437242036284

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

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

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

相关文章

MySQL插入数据库 insert into 语句 用法总结

目录 步骤 一、建表&#xff1a; 二、插入第一行数据 二、插入第二行数据&#xff08;指定要插入字段&#xff09; 三、插入第三行数据&#xff08;指定要插入的字段&#xff0c;但不是所有字段&#xff0c;除了(stu_id, stu_gender)&#xff09; 四、使用一条insert in…

maven中常见问题

文章目录 一、配置项提示二、父子打包三、打包之后不显示target四、自定义打包之后的jar包名称五、整个项目打包5.1、父项目管理插件和微服务打包 一、配置项提示 SpringBoot中提示错误信息 表示的是SpringBoot中的注释提示没有配置&#xff01;那么可以来使用一下springboot官…

【VS Code插件开发】通用功能(二)

&#x1f431; 个人主页&#xff1a;不叫猫先生&#xff0c;公众号&#xff1a;前端舵手 &#x1f64b;‍♂️ 作者简介&#xff1a;2022年度博客之星前端领域TOP 2&#xff0c;前端领域优质作者、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步…

红帽8.2版本CSA题库:第六题创建协作目录权限

红帽认证工程师第六题创建协作目录权限 mkdir /home/managers chown :sysmgrs /home/managers chmod 2770 /home/managers 测试&#xff1a; touch /home/managers/12345 ll /home/managers/12345

【雕爷学编程】Arduino动手做(04)---震动传感器模块5

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

Windows和Linux系统上的矢量运算:指令级并行计算SIMD(SSE/VAX)

注&#xff1a;本文的SIMD&#xff0c;指的是CPU指令架构中的相关概念。不涉及GPU端的算力机制。 基本概念 SIMD&#xff0c;Single Instruction/Multiple Data&#xff0c; 即单指流令多数据流&#xff0c;例如一个乘法指令&#xff0c;可以并行的计算8个浮点数的乘法。 SIM…

【Minecraft】Fabric Mod开发完整流程1 - 环境配置与第一个物品

前言 Fabric 是 Minecraft 一款非官方的模组 API,与 Forge mod 不同。它以轻量级和高性能为设计目标,专注于支持新版本的 Minecraft。 Fabric 和 Forge 在各自的加载编译流程上差别很大&#xff0c;所以你很难看见有同时支持二者的 mod&#xff0c;除非做了兼容性处理 Fabri…

新型高速 JavaScript 运行时 Bun 0.7 发布

导读近日&#xff0c;新型 JavaScript 运行时 Bun 正式发布了 0.7 版本&#xff0c;带来了重大的升级。据悉&#xff0c;Bun 是一个配套齐全的 JavaScript 解决方案&#xff0c;集运行时、打包器、转译器和包管理器于一体&#xff0c;追求极致的运行速度。此次更新主要集中在与…

命令提示符之操作基础(Windows)

打开命令提示符 方法一 打开指定文件的文件夹&#xff0c;在路径栏里输入“cmd”&#xff0c;回车&#xff0c;就进入控制台了。默认路径就是指定文件夹的路径。 方法二 打开指定的文件夹&#xff0c;按住shift键&#xff0c;在空白处右击&#xff0c;在菜单栏中选择“在此处打…

基于nodejs+vue+uniapp微信小程序的短视频分享系统

开发语言 node.js 框架&#xff1a;Express 前端:Vue.js 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat 开发软件&#xff1a;VScode 3.1小程序端 用户注册页面&#xff0c;输入用户的个人信息点击注册即可。 注册完成后会返回到登录页面&#xff0c;用户输入自己注…

液态金属——究竟是个美丽的概念还是大有可为

液态金属是一种新型的合金材料&#xff0c;在低温熔炼制备工艺下&#xff0c;将不同的金属材料按照一定的配比&#xff0c;通过温度控制使其充分融合&#xff0c;从而形成新的金属材料&#xff08;也可以理解为表面工艺处理手法&#xff09;。 液态金属可看作由正离子流体和自由…

electron+vue3全家桶+vite项目搭建【13.1】ipc通信的使用,主进程与渲染进程之间的交互

文章目录 引入IPC通信[主/渲染]进程对应渲染进程>主进程代码测试测试效果 主进程>渲染进程代码测试测试效果 双向通信代码测试测试效果 引入 electron项目常常由一个主进程和多个渲染进程构成&#xff0c;渲染进程之间是隔离的&#xff0c;而所有渲染进程都和主进程共享…

优雅的使用 Dockerfile 定制镜像

一、使用 Dockerfile 定制镜像 1.1、Dockerfile 定制镜像 1.2、FROM 指定基础镜像 1.3、RUN 执行命令 1.4、构建镜像 1.5、镜像构建上下文&#xff08;Context&#xff09; 1.6、其他 docker build 的用法 二、Dockerfile 指令 2.1、COPY 2.2、ADD 2.3、CMD 2.4、EN…

IDEWA项目实践——mybatis的一些基本原理以及案例

系列文章目录 IDEA项目实践——创建Java项目以及创建Maven项目案例、使用数据库连接池创建项目简介 IDEA创建项目的操作步骤以及在虚拟机里面创建Scala的项目简单介绍_intellij 创建scala IDEA项目实践——动态SQL、关系映射、注解开发 文章目录 系列文章目录 1.MyBatis …

基于Tars高并发IM系统的设计与实现-实战篇5

基于Tars高并发IM系统的设计与实现-实战篇5 群聊服务 GroupChatServer 群聊服务既可以接受来自BrokerServer的用户请求&#xff0c;也需要接收来自其他服务的RPC请求;所以本服务提供两套RPC接口&#xff1a;通用RPC接口和专用RPC接口。 通用RPC接口 通用RPC接口主要处理如下…

Jenkins自动化打包脚本

一、背景 jenkins可以设置定时任务打包&#xff0c;也已手动点按钮打包&#xff0c;还可以通过执行http请求打包&#xff0c;今天我们就通过shell脚本&#xff0c;通过curl命令进行jenkins打包。 二、步骤 2.1 在jenkins上构建项目 设置触发器 2.2 通过shell脚本触发远程构…

【RabbitMQ上手——单实例安装5种简单模式实现通讯过程】

【RabbitMQ入门-单实例安装&5种简单模式实现通讯过程】 一、环境说明二、安装RabbitMQ三、用户权限及Virtual Host设置四、5种简单模式实现通讯过程的实现五、小结 一、环境说明 安装环境&#xff1a;虚拟机VMWare Centos7.6 Maven3.6.3 JDK1.8RabbitMQ版本&#xff1a;…

并发——线程的生命周期和状态

文章目录 Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态&#xff08;图源《Java 并发编程艺术》4.1.4 节&#xff09;。 线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示&am…

点对点协议PPP

点对点协议PPP(Point-to-Point Protocol)是目前使用最广泛的点对点数据链路层协议。PPP协议是因特网的正确标准。 基本格式&#xff1a; PPP协议是数据链路格式。格式如下&#xff1a; 标志(Flag)字段: PPP的定界符&#xff0c;取值为0x7E 地址(Address)字段: 取值为0xFF&…

多语言自动翻译海外跨境电商独立站源码开发

要搭建一个多语言自动翻译的海外跨境电商独立站&#xff0c;需要进行以下步骤&#xff1a; 1. 选择合适的开发语言和框架&#xff1a;根据自己的技术实力和需求&#xff0c;选择适合的开发语言和框架。 2. 设计数据库结构&#xff1a;根据电商的业务需求&#xff0c;设计数据…