给你几张不同的图片,用JavaScript怎么做合并成一个大图?

450 阅读6分钟

前言

为了方便老板们做ppt做总结和汇报,,图片中有项目各种记录和统计的信息。需要选中网页中的多张图片后,变成一张大图然后复制到剪切板,我来给他们设计一下。 实现思路:

  1. 记录选中的图片url
  2. 把选中的图片依次绘制到canvas中
  3. 把canvas转成blob,写入剪切板

在项目中,首先有项目信息的图片使用了卡通图片代替;其次为了代码清晰和精简,我使用原生JS向大家讲解。

效果演示:

image.png

1. 记录选中的图片url

  1. 使用 changeSelectedImages 函数进行记录选中的图片
  2. 给选中的图片加上边框
<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();
    }
});

图片的大小和每行几张图片用户都是可以设置的,此处默认两张为一行

合成图片的核心代码:

我们合成图片的思路是:

  1. 创建Image图片对象
  2. 使用ctx.drawImage绘制img图片对象
  3. 每次绘制一张,更新canvasX的坐标
  4. 如果绘制完一行,需要进行换行,更新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>

对应代码:线上调试


扩展

在实际项目中设计的会比这更加复杂。比如

  1. 图片的大小不是铺满的,且需要控制偏移位置
  2. 对应的位置需要添加文字信息
  3. 文字可以设置大小和颜色 如下:
1737510794727.jpg

以及等等更加复杂的场景,大家可以思考如何扩展来满足需求