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

思路分析

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

  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

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

记录--使用 JS 实现基本的截图功能的更多相关文章

  1. JS 使用html2canvas实现截图功能的问题记录和解决方案

    在实现“截图”功能时,遇到几个bug,研究了一个上午,终于全部解决了: 下面给大家分享下: 1."图片资源跨域",导致无法截图. 浏览器会提示下面的错误 DOMException: ...

  2. canvas与html5实现视频截图功能

    这段时间一直在研究canvas,突发奇想想做一个可以截屏视频的功能,然后把图片拉去做表情包,哈哈哈哈哈哈~~ 制作方法: 1.在页面中加载视频 在使用canvas制作这个截图功能时,首先必须保证页面上 ...

  3. 基于java的后台截图功能的实现

    Java后台截图功能的实现 背景介绍: 在近期开发的可视化二期项目中的邮件项目中,邮件中的正文中含有图片.该图片的产生是将一些html网页转为图片格式,刚开始考虑使用第三方组件库html2image和 ...

  4. js、jquery实现模糊搜索功能

    模糊搜索功能在工作中应用广泛,并且很实用,自己写了一个方法,以后用到的时候可以直接拿来用了! 实现的搜索功能: 1. 可以匹配输入的字符串找出列表中匹配的项,列表框的高度跟随搜索出的列表项的多少改变 ...

  5. HTMLTESTRunner自动化测试报告增加截图功能

    我们都知道HTMLTESTRunner自动化测试报告,是Unittest单元测试框架报告,那么在做ui测试的时候就有点不适用了. 我们需要出错截图功能. 以下是我改的,增加了截图功能,先展示界面,再展 ...

  6. EasyPlayerPro Windows播放器本地快照抓拍截图功能实现方法

    背景描述 作为一个播放器,截图功能必不可少; 下面主要记录一下截图功能的实现: 实现流程 将解码后的帧进行格式转换(目标格式为RGB24); 采用独立的线程进行截图处理; 截图可保存为BMP或JPG两 ...

  7. 通过jcrop和canvas的画布功能完成对图片的截图功能与视频的截图功能实现

    最近因为工作需要,做了视频截图和图截图的功能.大概需求是,用户点击某个按钮,可以对图片区域进行部分截取,然后进行进一步的业务操作. 首先说图片截图功能的思路, (1)下载Jcrop插件,添加css和j ...

  8. Cesium截图功能

    首先安装  canvas2image npm intsall canvas2image --save 因为项目基于vue,所以需要在canvas2image的最后面 加上 export default ...

  9. 记一次"截图"功能的项目调研过程!

    目录 项目需求 功能调研 AWT Swing Html2Image PhantomJS Headless Chrome 实现方案 结论 项目需求 最近,项目接到了一个新需求,要求对指定URL进行后端模 ...

  10. JS实现前台表格排序功能

    JS实现前台表格排序功能 虽然数据量不大的情况下,前台排序速度比较快,但一般情况下,我们的项目只使用后台排序,原因有二: 一是代码简单:二是前台JS排序对于有分页的情况无法处理. 前段时间,有个功能需 ...

随机推荐

  1. [Java]BigDecimal与Double的区别和使用场景

    BigDecimal与Double的区别和使用场景 背景 在项目中发现开发小组成员在写程序时,对于Oracle数据类型为Number的字段(经纬度),实体映射类型有的人用Double有的人用BigDe ...

  2. Vdbench 使用说明

    一. vdbench简介 vdbench是一个 I/O 工作负载生成器,用于验证数据完整性和度量直接附加和网络连接的存储的性能.它是一个免费的工具,容易使用,而且常常用于测试和基准测试. 可以使用vd ...

  3. 当我忘记SQL怎么写的时候,我……

    当我忘记SQL怎么写的时候,我-- 以往的我,打开电脑百度一番,打开笔记查询一轮,发现没找到我最想要的 不要慌,问题不大 后来发现MySQL本身就带有帮助文档. 于是我一顿操作:win+R >& ...

  4. Java集合篇之set,面试官:请说一说HashSet、LinkedHashSet、TreeSet的区别?

    写在开头 Java的集合世界中主要由List,Set,Queue,Map构成,我们在之前的博文中已经学习了List,接下来我们继续学习Set集合. Set特点:存取无序,不可以存放重复的元素,不可以用 ...

  5. Centos7和Centos8的NFS配置

    Centos7和Centos8的NFS配置几乎是完全一样的 服务端 Centos7默认安装了rpcbind, nfs-utils, 其中rpcbind的服务默认是启用的, nfs-utils默认是禁用 ...

  6. SpringCloud Ribbon负载均衡服务调用实战

    介绍 Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具. 主要功能是提供客户端的软件负载均衡算法和服务调用.Ribbon客户端组件提供一系列完善 ...

  7. Java并发编程实例--19.在一个锁中使用多个条件

    一个锁可能关联了一个或多个条件.这些条件可以在Condition接口中声名. 使用这些条件的目的是去控制一个锁并且可以检查一个条件是true或false,如果为false,则暂停直到 另一个线程来唤醒 ...

  8. Go语言并发编程(4):sync包介绍和使用(下)-Once,Pool,Cond

    sync包下:Once,Pool,Cond 一.sync.Once 执行一次 Once 简介 sync.Once 是 Go 提供的让函数只执行一次的一种实现. 如果 once.Do(f) 被调用多次, ...

  9. 项目实战:Qt+OpenCV激光射击游(识别激光、识别圆)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  10. EF Invalid column name 'Discriminator' Invalid column name 'TagCode'.

    参考资料:Invalid column name 'TagCode'. 该异常和Discriminator没关系,一般原因:1.数据库中字段和实体类字段不一致导致的2.创建新增继承于数据库对应的实体类 ...