【canvas】前端创造的图片粒子动画效果:HTML5 Canvas 技术详解

前端创造的图片粒子动画效果:HTML5 Canvas 技术详解

我们将深入探讨如何通过 HTML5 的 Canvas 功能,将上传的图片转换成引人入胜的粒子动画效果。这种效果将图片分解成小粒子,并在用户与它们交互时产生动态变化。我们将分步骤详细解析代码,让你能够理解每一行代码的作用,并自己实现这一效果。

环境准备

首先,你需要一个简单的 HTML 元素和一些样式设置:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Particle Image Animation from Uploaded Image</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
            overflow: hidden;
        }
        canvas, input {
            display: block;
            margin: auto;
        }
    </style>
</head>
<body>
    <input type="file" id="upload" accept="image/*">
    <canvas id="canvas" hidden></canvas>
</body>
</html>

这段 HTML 设置了一个文件输入控件供用户上传图片,以及一个 Canvas 元素用于渲染动画效果。样式使页面内容居中显示,并将背景设置为浅灰色。

JavaScript 部分

JavaScript 脚本是这个效果的核心,下面我们逐一解析每个部分的功能。

1. 初始化和载入图片:
javascript 复制代码
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let particles = [];
const numOfParticles = 5000;
const uploadInput = document.getElementById('upload');

uploadInput.addEventListener('change', function(event) {
    const file = event.target.files[0];
    if (file && file.type.startsWith('image')) {
        const reader = new FileReader();
        reader.onload = function(e) {
            const maxSize = 500; // 最大尺寸
                        let width = img.width;
                        let height = img.height;
                        let scale = Math.min(maxSize / width, maxSize / height);
                        if (scale < 1) {
                            width *= scale;
                            height *= scale;
                        }
                        canvas.width = width;
                        canvas.height = height;
                        ctx.drawImage(img, 0, 0, width, height);
                        canvas.hidden = false;
                        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                        createParticles(imageData);
                        animate();
            };
        };
        reader.readAsDataURL(file);
    }
});

在这部分代码中,我们首先获取 Canvas 元素并配置基本画布(context)。监听文件输入控件的变化事件,当用户选择一个图片文件时,使用 FileReader 对象读取文件内容,将其转换为 Base64 编码的 URL,然后载入 <img> 元素。图片载入完毕后,把它绘制到 Canvas 上,然后提取图片的像素数据。

2. 创建粒子:
javascript 复制代码
function createParticles(imageData) {
    particles = [];
    const { width, height } = imageData;
    for (let i = 0; i < numOfParticles; i++) {
        const x = Math.random() * width;
        const y = Math.random() * height;
        const color = imageData.data[(~~y * width + ~~x) * 4];
        particles.push(new Particle(x, y, color));
    }
}

这个函数根据图片的像素数据随机生成指定数量的粒子。每个粒子具有位置(x,y)和基于图片某一点的颜色。粒子的初始位置是随机分布的。

3. 定义粒子对象:
javascript 复制代码
function Particle(x, y, color) {
    this.x = x;
    this.originalX = x;
    this.y = y;
    this.originalY = y;
    this.color = `rgba(${color},${color},${color}, 0.5)`;

    this.draw = function() {
        ctx.fillStyle = this.color;
        ctx.fillRect(this.x, this.y, 2, 2);
    };

    this.update = function() {
        let dx = this.originalX - this.x;
        let dy = this.originalY - this.y;
        this.x += dx * 0.1;
        this.y += dy * 0.1;

        this.draw();
    };
}

粒子对象具有 drawupdate 方法。draw 方法用来在 Canvas 上绘制粒子,update 方法则负责更新粒子的位置,使它们逐渐回到原始位置。

4. 动画循环和鼠标交互:
javascript 复制代码
function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    particles.forEach(particle => particle.update());
    requestAnimationFrame(animate);
}

animate 函数清空画布并更新所有粒子的位置,然后通过 requestAnimationFrame 递归调用自身以形成动画循环。

完整代码

复制这段代码到一个.html文件,可以直接在浏览器允许该demo,实际操作一番。

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Particle Image Animation from Uploaded Image</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
            overflow: hidden;
        }

        canvas,
        input {
            display: block;
            margin: auto;
        }
    </style>
</head>

<body>
    <input type="file" id="upload" accept="image/*">
    <canvas id="canvas" hidden></canvas>
    <script>
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        let particles = [];
        const numOfParticles = 5000;
        const uploadInput = document.getElementById('upload');

        uploadInput.addEventListener('change', function (event) {
            const file = event.target.files[0];
            if (file && file.type.startsWith('image')) {
                const reader = new FileReader();
                reader.onload = function (e) {
                    const img = new Image();
                    img.src = e.target.result;
                    img.onload = function () {
                        const maxSize = 500; // 最大尺寸
                        let width = img.width;
                        let height = img.height;
                        let scale = Math.min(maxSize / width, maxSize / height);
                        if (scale < 1) {
                            width *= scale;
                            height *= scale;
                        }
                        canvas.width = width;
                        canvas.height = height;
                        ctx.drawImage(img, 0, 0, width, height);
                        canvas.hidden = false;
                        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                        createParticles(imageData);
                        animate();
                    };
                };
                reader.readAsDataURL(file);
            }
        });

        function createParticles(imageData) {
            particles = [];
            const { width, height } = imageData;
            for (let i = 0; i < numOfParticles; i++) {
                const x = Math.random() * width;
                const y = Math.random() * height;
                const color = imageData.data[(~~y * width + ~~x) * 4];
                particles.push(new Particle(x, y, color));
            }
        }

        function Particle(x, y, color) {
            this.x = x;
            this.originalX = x;
            this.y = y;
            this.originalY = y;
            this.color = `rgba(${color},${color},${color}, 0.5)`;

            this.draw = function () {
                ctx.fillStyle = this.color;
                ctx.fillRect(this.x, this.y, 2, 2);
            };

            this.update = function () {
                let dx = this.originalX - this.x;
                let dy = this.originalY - this.y;
                this.x += dx * 0.1;
                this.y += dy * 0.1;

                this.draw();
            };
        }

        function animate() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            particles.forEach(particle => particle.update());
            requestAnimationFrame(animate);
        }

        canvas.addEventListener('mousemove', function (e) {
            const rect = canvas.getBoundingClientRect();
            const mouseX = e.clientX - rect.left;
            const mouseY = e.clientY - rect.top;

            particles.forEach(particle => {
                const dx = mouseX - particle.x;
                const dy = mouseY - particle.y;
                const dist = Math.sqrt(dx * dx + dy * dy);

                if (dist < 50) {
                    const angle = Math.atan2(dy, dx);
                    particle.x -= Math.cos(angle);
                    particle.y -= Math.sin(angle);
                }
            });
        });
    </script>
</body>

</html>
相关推荐
J不A秃V头A18 分钟前
Vue3:编写一个插件(进阶)
前端·vue.js
司篂篂42 分钟前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客1 小时前
pinia在vue3中的使用
前端·javascript·vue.js
宇文仲竹1 小时前
edge 插件 iframe 读取
前端·edge
Kika写代码2 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构
哆木2 小时前
部署在线GBA游戏,并通过docker安装启动
游戏·html·gba
天下无贼!3 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr3 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林3 小时前
npm发布插件超级简单版
前端·npm·node.js
罔闻_spider3 小时前
爬虫----webpack
前端·爬虫·webpack