前言
为了方便老板们做ppt做总结和汇报,,图片中有项目各种记录和统计的信息。需要选中网页中的多张图片后,变成一张大图然后复制到剪切板,我来给他们设计一下。 实现思路:
- 记录选中的图片url
- 把选中的图片依次绘制到canvas中
- 把canvas转成blob,写入剪切板
在项目中,首先有项目信息的图片使用了卡通图片代替;其次为了代码清晰和精简,我使用原生JS向大家讲解。
效果演示:
1. 记录选中的图片url
- 使用
changeSelectedImages函数进行记录选中的图片 - 给选中的图片加上边框
<div class="image-container" id="imageContainer" onclick="changeSelectedImages(event)">
<img src="test01.jpg">
<img src="test02.jpg">
<img src="test03.jpg">
<img src="test04.jpg">
<img src="test05.jpg">
<!-- 可以在此处添加更多图片 -->
</div>
<script>
function changeSelectedImages(event) {
// 判断点击的是不是图片
if (event.target.tagName === 'IMG') {
const imageUrl = event.target.src;
// 如果图片未选中,则添加到选中数组
if (!selectedImages.includes(imageUrl)) {
selectedImages.push(imageUrl);
} else {
// 如果图片已选中,则取消选中
selectedImages = selectedImages.filter(url => url !== imageUrl);
}
// 给选中的图片加边框
event.target.style.border = selectedImages.includes(imageUrl) ? '3px solid blue' : '';
}
}
</script>
2. 依次绘制canvas
点击 copy按钮,或者按下 ctrl+c时,我们需要触发copyImage函数,来进行绘制cavas。
// 为按钮绑定点击事件,生成合成大图
function copyImage() {
createCompositeImage({
imageWidth: 100, // 每张图片的宽度
imageHeight: 100, // 每张图片的高度
imagesPerRow: 3 // 每行显示3张图片
});
}
// 监听 Ctrl + C 按键事件
document.addEventListener('keydown', function (event) {
if (event.ctrlKey && event.key === 'c') {
copyImage();
}
});
图片的大小和每行几张图片用户都是可以设置的,此处默认两张为一行
合成图片的核心代码:
我们合成图片的思路是:
- 创建
Image图片对象 - 使用
ctx.drawImage绘制img图片对象 - 每次绘制一张,更新
canvas中X的坐标 - 如果绘制完一行,需要进行换行,更新
Y的坐标
// 合成大图并绘制到 Canvas
function createCompositeImage(options = {}) {
const {
imageWidth = 100, // 每张图片的宽度,默认为100
imageHeight = 100, // 每张图片的高度,默认为100
imagesPerRow = 3 // 每行显示几张图片,默认为3
} = options;
// 如果没有选中图片,直接返回
if (selectedImages.length === 0) {
alert('请选择一些图片!');
return;
}
// 加载所有图片
const images = selectedImages.map(url => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = url;
img.crossOrigin ="anonymous"; // 解决跨域问题,允许cavas绘制跨域图片
img.onload = () => resolve(img); // 图片加载完成后返回成功
img.onerror = reject; // 图片加载失败后返回失败
});
});
// 所有图片加载成功后合成大图
Promise.all(images).then(imgArray => {
// 计算合成图的宽度和高度
const numRows = Math.ceil(imgArray.length / imagesPerRow); // 计算行数,确保不超出图片数量
const totalWidth = imageWidth * Math.min(imagesPerRow, imgArray.length); // 每行的总宽度
const totalHeight = imageHeight * numRows; // 合成图的总高度
// 设置 Canvas 的宽高
canvas.width = totalWidth;
canvas.height = totalHeight;
// 将每张图片绘制到 Canvas 上
imgArray.forEach((img, index) => {
const row = Math.floor(index / imagesPerRow); // 计算当前图片的行号
const col = index % imagesPerRow; // 计算当前图片的列号
const x = col * imageWidth; // 计算当前图片的 x 坐标
const y = row * imageHeight; // 计算当前图片的 y 坐标
ctx.drawImage(img, x, y, imageWidth, imageHeight); // 绘制图片到 Canvas 上
});
}).catch(error => {
console.error('图片加载失败:', error);
});
}
3. 图片复制到剪切板
我们使用navigator.clipboard操作剪切板。如果有不了解clipboard的同学,非常推荐去学习阮一峰老师写的这篇文章:阮一峰: 剪贴板操作教程
Clipboard.write可以接受blob数据,我们可以把上面生成的canvas转成blob,然后复制到剪切板
1. canvas转成blob:
// 所有图片加载完后合成大图
Promise.all(images).then(imgArray => {
// 合成图操作......
// 将合成图转换为 blob
canvas.toBlob(copyToClipboard); // copyToClipboard是一个回调函数
}).catch(error => {
console.error('图片加载失败:', error);
});
2. Clipboard API 复制 Blob 到剪切板: (圆满结束):
// 将合成图的 Base64 字符串复制到剪切板
function copyToClipboard(blob) {
// 通过 ClipboardItem API 复制图片到剪切板
try {
const clipboardItem = new ClipboardItem({
[blob.type]: blob
});
navigator.clipboard.write([clipboardItem]).then(() => {
alert('合成图已成功复制到剪切板!');
}).catch(err => {
console.error('复制到剪切板失败:', err);
});
} catch (err) {
console.error('Clipboard 操作失败:', err);
}
}
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>合成大图</title>
<style>
.image-container {
display: flex;
flex-wrap: wrap;
}
.image-container img {
width: 100px;
height: 100px;
cursor: pointer;
}
#canvas {
border: 1px solid #000;
margin-top: 20px;
}
#generateButton {
margin-top: 20px;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
}
#generateButton:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1>点击按钮或ctrl+c合成大图</h1>
<!-- 图片展示区 -->
<div class="image-container" id="imageContainer" onclick="changeSelectedImages(event)">
<img src="test01.jpg">
<img src="test02.jpg">
<img src="test03.jpg">
<img src="test04.jpg">
<img src="test05.jpg">
<!-- 可以在此处添加更多图片 -->
</div>
<!-- 用于展示合成大图的 Canvas -->
<canvas id="canvas"></canvas>
<!-- 生成合成图按钮 -->
<button id="generateButton" onclick="copyImage()">生成合成大图</button>
<script>
// 获取图片容器和 Canvas 元素
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const generateButton = document.getElementById('generateButton');
// 用来保存用户选中的图片
let selectedImages = [];
// 直接在 imageContainer 元素上绑定 click 事件,更新选中的图片列表
function changeSelectedImages(event) {
if (event.target.tagName === 'IMG') {
const imageUrl = event.target.src;
// 如果图片未选中,则添加到选中数组
if (!selectedImages.includes(imageUrl)) {
selectedImages.push(imageUrl);
} else {
// 如果图片已选中,则取消选中
selectedImages = selectedImages.filter(url => url !== imageUrl);
}
// 给选中的图片加边框
event.target.style.border = selectedImages.includes(imageUrl) ? '3px solid blue' : '';
}
}
// 合成大图并绘制到 Canvas
function createCompositeImage(options = {}) {
const {
imageWidth = 100, // 每张图片的宽度,默认为100
imageHeight = 100, // 每张图片的高度,默认为100
imagesPerRow = 3 // 每行显示几张图片,默认为3
} = options;
// 如果没有选中图片,直接返回
if (selectedImages.length === 0) {
alert('请选择一些图片!');
return;
}
// 加载所有图片
const images = selectedImages.map(url => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = url;
img.crossOrigin ="anonymous"; // 解决跨域问题,允许cavas绘制跨域图片
img.onload = () => resolve(img);
img.onerror = reject;
});
});
// 所有图片加载完后合成大图
Promise.all(images).then(imgArray => {
// 计算合成图的宽度和高度
const numRows = Math.ceil(imgArray.length / imagesPerRow); // 计算行数,确保不超出图片数量
const totalWidth = imageWidth * Math.min(imagesPerRow, imgArray.length); // 每行的总宽度
const totalHeight = imageHeight * numRows; // 合成图的总高度
// 设置 Canvas 的宽高
canvas.width = totalWidth;
canvas.height = totalHeight;
// 将每张图片绘制到 Canvas 上
imgArray.forEach((img, index) => {
const row = Math.floor(index / imagesPerRow); // 计算当前图片的行号
const col = index % imagesPerRow; // 计算当前图片的列号
const x = col * imageWidth; // 计算图片的 x 坐标
const y = row * imageHeight; // 计算图片的 y 坐标
ctx.drawImage(img, x, y, imageWidth, imageHeight); // 绘制图片到 Canvas 上
});
// 将合成图转换为 blob
canvas.toBlob(copyToClipboard);
}).catch(error => {
console.error('图片加载失败:', error);
});
}
// 将合成图的 Base64 字符串复制到剪切板
function copyToClipboard(blob) {
// 通过 ClipboardItem API 复制图片到剪切板
try {
const clipboardItem = new ClipboardItem({
[blob.type]: blob
});
navigator.clipboard.write([clipboardItem]).then(() => {
alert('合成图已成功复制到剪切板!');
}).catch(err => {
console.error('复制到剪切板失败:', err);
});
} catch (err) {
console.error('Clipboard 操作失败:', err);
}
}
// 为按钮绑定点击事件,生成合成大图
function copyImage() {
createCompositeImage({
imageWidth: 100, // 每张图片的宽度
imageHeight: 100, // 每张图片的高度
imagesPerRow: 3 // 每行显示3张图片
});
}
// 监听 Ctrl + C 按键事件
document.addEventListener('keydown', function (event) {
if (event.ctrlKey && event.key === 'c') {
copyImage();
}
});
</script>
</body>
</html>
对应代码:线上调试
扩展
在实际项目中设计的会比这更加复杂。比如
- 图片的大小不是铺满的,且需要控制偏移位置
- 对应的位置需要添加文字信息
- 文字可以设置大小和颜色 如下:
以及等等更加复杂的场景,大家可以思考如何扩展来满足需求