js实现框选截屏功能

news2024/12/23 19:25:28

实现的思路大概就是,先将dom转化为canvas画布,再对canvas进行裁切,然后通过canvas api生成图片,这里用到了一个库html2canvas

效果如图:
在这里插入图片描述
首先实现框选效果:

const mousedownEvent = (e) => {
	moveX = 0;
	moveY = 0;
	const [startX, startY] = [e.clientX, e.clientY];
	x = startX - viewer.getBoundingClientRect().left;
	y = startY - viewer.getBoundingClientRect().top;
	const divDom = document.createElement("div");
	divDom.id = 'screenshot';
	divDom.width = '1px';
	divDom.height = '1px';
	divDom.style.position = "absolute";
	divDom.style.top = y + "px";
	divDom.style.left = x + "px";
	const closeIcon = document.createElement("span");
	closeIcon.className = 'outline-close-icon';
	closeIcon.textContent = 'x';
	divDom.appendChild(closeIcon);

	closeIcon.addEventListener('click', () => {
		divDom.remove();
	});
	// document.body.appendChild(divDom)
	viewer.appendChild(divDom);
	const moveEvent = (e) => {
		moveX = e.clientX - startX;
		moveY = e.clientY - startY;
		if (moveX > 0) {
			divDom.style.width = moveX + 'px';
		} else {
			divDom.style.width = -moveX + 'px';
			divDom.style.left = e.clientX - viewer.getBoundingClientRect().left + 'px';
		}
		if (moveY > 0) {
			divDom.style.height = moveY + 'px';
		} else {
			divDom.style.height = -moveY + 'px';
			divDom.style.top = e.clientY - viewer.getBoundingClientRect().top + 'px';
		}
	};
	window.addEventListener("mousemove", moveEvent);
	window.addEventListener("mouseup", () => {
		window.removeEventListener("mousemove", moveEvent);
		window.removeEventListener("mousedown", mousedownEvent);
		document.querySelector("body").style.cursor = "default";
	});
};
window.addEventListener("mousedown", mousedownEvent);

全码:

const viewer = document.getElementById('viewer');
let canvas;
document.getElementById('screen-button').addEventListener('click', (e) => {
	// 创建一个canvas画布
	if (canvas) {
		canvas.remove();
	}
	canvas = document.createElement('canvas');
	canvas.setAttribute('id', 'bg_canvas');
	canvas.style.position = "absolute";
	canvas.style.zIndex = 2;
	canvas.style.left = 0;
	canvas.style.top = 0;
	canvas.style.width = '100%';
	canvas.style.height = '100%';
	viewer.appendChild(canvas);

	document.querySelector("body").style.cursor = "crosshair";
	let moveX;
	let moveY;
	let x;
	let y;
	const mousedownEvent = (e) => {
		moveX = 0;
		moveY = 0;
		const [startX, startY] = [e.clientX, e.clientY];
		x = startX - viewer.getBoundingClientRect().left;
		y = startY - viewer.getBoundingClientRect().top;
		const divDom = document.createElement("div");
		divDom.id = 'screenshot';
		divDom.width = '1px';
		divDom.height = '1px';
		divDom.style.position = "absolute";
		divDom.style.top = y + "px";
		divDom.style.left = x + "px";
		const closeIcon = document.createElement("span");
		closeIcon.className = 'outline-close-icon';
		closeIcon.textContent = 'x';
		divDom.appendChild(closeIcon);

		closeIcon.addEventListener('click', () => {
			divDom.remove();
		});
		// document.body.appendChild(divDom)
		viewer.appendChild(divDom);
		const moveEvent = (e) => {
			moveX = e.clientX - startX;
			moveY = e.clientY - startY;
			if (moveX > 0) {
				divDom.style.width = moveX + 'px';
			} else {
				divDom.style.width = -moveX + 'px';
				divDom.style.left = e.clientX - viewer.getBoundingClientRect().left + 'px';
			}
			if (moveY > 0) {
				divDom.style.height = moveY + 'px';
			} else {
				divDom.style.height = -moveY + 'px';
				divDom.style.top = e.clientY - viewer.getBoundingClientRect().top + 'px';
			}
		};
		window.addEventListener("mousemove", moveEvent);
		window.addEventListener("mouseup", () => {

			window.removeEventListener("mousemove", moveEvent);
			window.removeEventListener("mousedown", mousedownEvent);
			document.querySelector("body").style.cursor = "default";

			if (!moveX) {
				return;
			}

			// 把body转成canvas
			html2canvas(viewer, {
				scale: 1,
				// allowTaint: true,
				useCORS: true  //跨域使用
			}).then(canvas2 => {
				let capture_x, capture_y;
				let width = moveX;
				let height = moveY;
				if (width > 0) {
					//从左往右画
					capture_x = startX - canvas.getBoundingClientRect().left + 1;
				} else {
					//从右往左画
					capture_x = x + width + 1;
				}
				if (height > 0) {
					//从上往下画
					capture_y = y + 1;
				} else {
					//从下往上画
					capture_y = y + height + 1;
				}
				printClip(canvas2, capture_x, capture_y, Math.abs(width), Math.abs(height));

				moveX = 0;
				canvas.remove();
			});
		});
	};
	window.addEventListener("mousedown", mousedownEvent);
});

/**
 * 打印截取区域
 * @param canvas 截取的canvas
 * @param capture_x 截取的起点x
 * @param capture_y 截取的起点y
 * @param capture_width 截取的起点宽
 * @param capture_height 截取的起点高
 */
async function printClip(canvas2, capture_x, capture_y, capture_width, capture_height) {
	// 创建一个用于截取的canvas
	const clipCanvas = document.createElement('canvas');
	clipCanvas.width = capture_width;
	clipCanvas.height = capture_height;
	// 截取
	clipCanvas.getContext('2d').drawImage(canvas2, capture_x, capture_y, capture_width, capture_height, 0, 0, capture_width, capture_height);
	const clipImgBase64 = clipCanvas.toDataURL();
	// console.log('clipImgBase64->', clipImgBase64);
	// console.log('clipImgBase64->', clipImgBase64.replace(/^data:image\/\w+;base64,/, ""));
	const obj = {
		// file: new File([this.blob],'main.audio',{ type: 'audio/mp3' })
		file: new File([base64ToBlob(clipImgBase64.replace(/^data:image\/\w+;base64,/, ""), 'image/png')], 'test.png', { type: 'image/png' })
	};
	// 生成图片
	// var clipImg = new Image()
	// clipImg.src = clipImgBase64
	downloadIamge(clipImgBase64)
}

/**
 * 下载保存图片
 * @param imgUrl 图片地址
 */
function downloadIamge(imgUrl) {
	// 生成一个a元素
	const a = document.createElement('a');
	// 创建一个单击事件
	const event = new MouseEvent('click');
	// 生成文件名称
	const timestamp = new Date().getTime();
	const name = imgUrl.substring(22, 30) + timestamp + '.png';
	a.download = name;
	// 将生成的URL设置为a.href属性
	a.href = imgUrl;
	// 触发a的单击事件 开始下载
	a.dispatchEvent(event);
}

// 将Base64编码转换为Blob对象
function base64ToBlob(base64, type) {
	const byteCharacters = atob(base64);
	const byteArrays = [];

	for (let offset = 0; offset < byteCharacters.length; offset += 512) {
		let slice = byteCharacters.slice(offset, offset + 512);

		let byteNumbers = new Array(slice.length);
		for (let i = 0; i < slice.length; i++) {
			byteNumbers[i] = slice.charCodeAt(i);
		}

		let byteArray = new Uint8Array(byteNumbers);
		byteArrays.push(byteArray);
	}

	let blob = new Blob(byteArrays, { type: type });
	return blob;
}

坑:

  1. 如果生成图片样式有问题 html就用内联样式
  2. 当截图片的时候如果不识别 就将图片url转化为base64

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

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

相关文章

Java真的没出路了吗?

前言 并不是没有出路&#xff0c;Java当下确实是比较的内卷&#xff0c;但关键在于个人&#xff0c;可以看看不同地方&#xff08;这里主要举例北上广深一线城市&#xff09;对于Java开发工程师这个职位的具体要求&#xff1a; 在以下北上广深这些一线大城市的面试招聘当中不…

CAPL中有符号和无符号数据类型的若干问题

我们知道CAPL中的整数类型分为:无符号(unsigned)和有符号(signed)。 无符号类型有: byte (unsigned, 1 Byte)word (unsigned, 2 Byte)dword (unsigned, 4 Byte)qword(unsigned, 8 Byte)有符号类型有: int (signed, 2 Byte)long (signed, 4 Byte)int64(signed, 8 Byte)什…

Sharding-JDBC数据加密详解与实战

&#x1f680; ShardingSphere &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&…

进销存+小程序商城一体化,多门店管理解决方案-免费试用|亿发

为了适应市场变化和增强管理效率&#xff0c;越来越多的连锁&#xff0c;门店开始转向进销存小程序商城一体化&#xff0c;将进销存与订货商城结合&#xff0c;以实现更便捷、有效的经营模式&#xff0c;让企业迈向数字化时代。让我们一起来看看进销存小程序商城一体化系统相比…

掌握企业声音:企业新闻舆情查询API的重要作用与应用

摘要 在信息时代&#xff0c;企业的声誉和形象可以在瞬息万变的新闻舆情中受到影响。为了保护企业的声誉和形象&#xff0c;以及及时洞察市场动向&#xff0c;企业新闻舆情查询 API 应运而生。本文将探讨企业新闻舆情查询 API 的重要作用与应用&#xff0c;以及它在帮助企业实…

Linux输出内容到指定文件

1. 记录终端输出至文本文件 1.1 解决方案1&#xff1a;利用>和>>命令 区别&#xff1a; > 是把输出转向到指定的文件。注意&#xff1a;如文件已存在的话会重新写入&#xff0c;文件原内容不会保留。 >> 是把输出附加到文件的后面&#xff0c;文件原内容会…

车间生产线数据采集网关让生产透明化

**钡铼技术加工车间解决方案&#xff0c;通过把网关安装至一体电控箱控制生产设备&#xff0c;并与控制传输带的PLC结合&#xff0c;打造出了一个易部署、易维护、高可靠性&#xff0c;可24小时作业的自动化生产产线。 为什么采集数据&#xff1f;——实现生产透明化 1、数据…

【LeetCode】96.不同的二叉搜索树

题目 给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a; 输入&#xff1a;n 1 输出&#xf…

C. Maximum Set

Problem - 1796C - Codeforces 思路&#xff1a;这个题在做的时候基本的思路是对的&#xff0c;但是没有想到O(1)求答案&#xff0c;枚举的然后T了&#xff0c;我们能够知道&#xff0c;假设前面的数小&#xff0c;那么每个数一定是前面的倍数&#xff0c;所以至少乘以2&#x…

C++ easyx大一期末作业利器

yNodeGUI_v2.0 紧跟着1.0版本的完成&#xff0c;又到了激动人心的C期末作业了。(上学期是C语言的),这学期&#xff0c;我仅仅改了一点点上学期的期末作业&#xff0c;然后很轻松的水…啊不&#xff0c;完成了这次的期末作业。 所以&#xff0c;大家一定要注重复用&#xff01…

go学习 4、复合数据类型

4、复合数据类型 数组、slice、map和结构体 如何使用结构体来解码和编码到对应JSON格式的数据&#xff0c;并且通过结合使用模板来生成HTML页面 数组和结构体是聚合类型;它们的值由许多元素或成员字段的值组成。数组是由同构的元素组成&#xff08;每个数组元素都是完全相同的…

【2023】分享国内外大厂开发主要AI网站

大厂原版 OpenAI ChatGPT 921 New Bing 415 Notion AI 90 百度文心一言 467 TruthGPT 105 讯飞星火认知大模型 141 进阶 Hugging Face 62 写作AI ​ Dyrt 394&#xff08;AI写作&#xff09; ​ DeepL Write 211&#xff08;写作翻译&#xff09; ​AI自动写文章 535 …

怎么转换音频格式?音频格式转换方法分享

当我们在处理音频文件时&#xff0c;可能会遇到一些问题。一些设备和应用程序可能无法播放特定的音频格式&#xff0c;或者需要将音频文件发送给其他人&#xff0c;但他们无法打开该格式。此外&#xff0c;某些网站和应用程序可能只支持特定类型的音频格式&#xff0c;因此我们…

数字孪生的现实意义及不足之处

数字孪生作为一项前沿技术&#xff0c;在当今数字化转型浪潮中发挥着越来越重要的作用。它是将物理世界和数字世界紧密结合的桥梁&#xff0c;为现实问题提供了全新的解决方案。然而数字孪生在现实生活中有什么现实意义嘛&#xff1f;实现难点又在哪&#xff1f;下面简单从几个…

多线程面试题--线程池

目录 介绍 线程池的核心参数/执行原理 核心参数 执行原理​编辑 常见的阻塞队列 ArrayBlockingQueue和LinkedBlockingQueue区别 如何确定核心线程数 线程池的种类有哪些 创建使用固定线程数的线程池 单线程化的线程池 可缓存线程池 “延迟”和“周期执行”的线程池 总…

【每天40分钟,我们一起用50天刷完 (剑指Offer)】第三十八天 38/50【归并排序】【链表第一个交点】【二分】

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

计算机是如何计算四则运算表达式的?

&#x1f389;welcome&#x1f389; ✒️博主介绍&#xff1a;博主大一智能制造在读&#xff0c;热爱C/C&#xff0c;会不定期更新系统、语法、算法、硬件的相关博客&#xff0c;浅浅期待下一次更新吧&#xff01; ✈️算法专栏&#xff1a;算法与数据结构 &#x1f618;博客制…

Python爬虫实战(基础篇)—4获取古诗词给孩子学习(附完整代码)

今天我们来获取古诗词网站的一些古诗词来提供给孩子们学习 PS前面几节课的内容在专栏这里&#xff0c;欢迎大家考古&#xff1a;点我 首先我们看一下网站&#xff1a;点我&#xff0c;今天我们来获取一下【唐诗三百首】 第 1 步&#xff1a;网页分析 在网页中我们发现有许多以…

KnowStreaming系列教程第三篇——调度任务模块

前一篇文章KnowStreaming系列教程第二篇——项目整体架构分析_诸葛子房_的博客-CSDN博客 讲述了KS的整体项目目录&#xff0c;这边文章来讲述下KS在调度模块里面对于指标采集和元数据同步 一、调度模块代码主要在km-task里面 public class TaskClusterAddedListener impleme…

Docker-compose简介和部署编排 and Docker 私有仓库Harbor的简介和部署

Docker-compose简介和部署编排 and Docker 私有仓库的简介和部署 一、Docker-compose简介Ⅰ、compose概述Ⅱ、YAML 文件格式及编写注意事项Ⅲ、YAML支持的数据结构 二、compose部署安装Ⅰ、Docker Compose 环境安装Ⅱ、Docker Compose配置常用字段Ⅲ、Docker Compose 常用命令 …