您当前的位置:首页 > 计算机 > 编程开发 > Html+Div+Css(前端)

Css动画:爱心动画效果实现

时间:02-12来源:作者:点击数:

前言

本文将详细介绍一个基于HTML5 Canvas实现的爱心动画案例,从代码结构到具体实现细节,帮助读者深入理解前端动画开发的技术要点。

一、项目概述

1.1 功能介绍

本项目实现了一个动态的爱心动画效果。当用户移动鼠标时,页面上会显示多个跟随鼠标移动的小爱心图标,并且在画布上生成动态的爱心粒子效果。这些粒子随着时间的变化逐渐消失,形成一种梦幻般的效果。

1.2 技术栈
  • HTML5:用于创建基本的页面结构。
  • CSS3:用于设置页面样式。
  • JavaScript:用于实现动画逻辑。

二、代码解析

2.1 HTML 结构
<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>前端青山</title>
    <style>
        html, body {
            height: 100%;
            padding: 0;
            margin: 0;
        }
        canvas {
            position: absolute;
            width: 100%;
            height: 100%;
            background-color: black;
        }
        div {
            width: 20px;
            height: 20px;
            position: absolute;
        }
        div img {
            width: 20px;
            height: 20px;
        }
    </style>
</head>
<body>
    <canvas id="pinkboard" width="805" height="946">
        Canvas Not Support
    </canvas>
    <div><img src="img/Heart.png"/></div>
    <div><img src="img/Heart.png"/></div>
    <div><img src="img/Heart.png"/></div>
    <div><img src="img/Heart.png"/></div>
    <div><img src="img/Heart.png"/></div>
    <div><img src="img/Heart.png"/></div>
    <div><img src="img/Heart.png"/></div>
    <div><img src="img/Heart.png"/></div>
</body>
</html>
2.1.1 页面结构
  • <canvas> 标签:用于绘制动态的爱心粒子效果。
  • <div> 标签:用于显示跟随鼠标移动的小爱心图标。
2.1.2 样式设置
  • html, body:设置页面的高度为100%,去除默认的内边距和外边距。
  • canvas:设置画布的位置为绝对定位,宽度和高度为100%,背景色为黑色。
  • div:设置每个小爱心图标的大小为20x20像素,位置为绝对定位。
  • div img:设置小爱心图标的大小为20x20像素。
2.2 JavaScript 实现
2.2.1 鼠标事件处理
var oDivs = document.querySelectorAll("div");
document.onmousemove = function (event) {
    var event = event || window.event;
    oDivs[0].style.top = event.clientY + "px";
    oDivs[0].style.left = event.clientX + "px";
    for (var i = oDivs.length - 1; i > 0; i--) {
        oDivs[i].style.top = oDivs[i-1].offsetTop + "px";
        oDivs[i].style.left = oDivs[i-1].offsetLeft + "px";
    }
}
document.onmousedown = function (event) {
    console.log(new Date().getSeconds());
}
2.2.1.1 onmousemove 事件
  • event 对象:获取当前鼠标事件对象。
  • oDivs[0]:将第一个小爱心图标的位置设置为鼠标当前位置。
  • for 循环:将其他小爱心图标的位置依次设置为前一个小爱心图标的位置,形成跟随效果。
2.2.1.2 onmousedown 事件
  • console.log(new Date().getSeconds()):记录鼠标点击时的时间秒数。
2.2.2 粒子系统实现
2.2.2.1 设置
var settings = {
    particles: {
        length: 500, // 最大粒子数量
        duration: 2, // 粒子持续时间(秒)
        velocity: 100, // 粒子速度(像素/秒)
        effect: -0.75, // 效果参数
        size: 50, // 粒子大小(像素)
    },
};
  • length:最大粒子数量。
  • duration:粒子持续时间。
  • velocity:粒子速度。
  • effect:效果参数,用于调整粒子的运动轨迹。
  • size:粒子大小。
2.2.2.2 requestAnimationFrame 兼容性处理
(function () {
    var b = 0; var c = ["ms", "moz", "webkit", "o"];
    for (var a = 0; a < c.length && !window.requestAnimationFrame; ++a) {
        window.requestAnimationFrame = window[c[a] + "RequestAnimationFrame"];
        window.cancelAnimationFrame = window[c[a] + "CancelAnimationFrame"] || window[c[a] + "CancelRequestAnimationFrame"];
    }
    if (!window.requestAnimationFrame) {
        window.requestAnimationFrame = function (h, e) {
            var d = new Date().getTime();
            var f = Math.max(0, 16 - (d - b));
            var g = window.setTimeout(function () { h(d + f) }, f);
            b = d + f;
            return g;
        }
    }
    if (!window.cancelAnimationFrame) {
        window.cancelAnimationFrame = function (d) {
            clearTimeout(d);
        }
    }
})();
  • requestAnimationFrame:用于请求浏览器在下一次重绘之前调用指定的函数。
  • cancelAnimationFrame:用于取消通过 requestAnimationFrame 注册的回调函数。
2.2.2.3 Point 类
var Point = (function () {
    function Point(x, y) {
        this.x = (typeof x !== 'undefined') ? x : 0;
        this.y = (typeof y !== 'undefined') ? y : 0;
    }
    Point.prototype.clone = function () {
        return new Point(this.x, this.y);
    };
    Point.prototype.length = function (length) {
        if (typeof length == 'undefined')
            return Math.sqrt(this.x * this.x + this.y * this.y);
        this.normalize();
        this.x *= length;
        this.y *= length;
        return this;
    };
    Point.prototype.normalize = function () {
        var length = this.length();
        this.x /= length;
        this.y /= length;
        return this;
    };
    return Point;
})();
  • Point 类:表示一个二维坐标点。
  • clone 方法:返回一个新的 Point 对象,其坐标值与当前对象相同。
  • length 方法:计算点的长度,或设置点的长度。
  • normalize 方法:将点归一化,使其长度为1。
2.2.2.4 Particle 类
var Particle = (function () {
    function Particle() {
        this.position = new Point();
        this.velocity = new Point();
        this.acceleration = new Point();
        this.age = 0;
    }
    Particle.prototype.initialize = function (x, y, dx, dy) {
        this.position.x = x;
        this.position.y = y;
        this.velocity.x = dx;
        this.velocity.y = dy;
        this.acceleration.x = dx * settings.particles.effect;
        this.acceleration.y = dy * settings.particles.effect;
        this.age = 0;
    };
    Particle.prototype.update = function (deltaTime) {
        this.position.x += this.velocity.x * deltaTime;
        this.position.y += this.velocity.y * deltaTime;
        this.velocity.x += this.acceleration.x * deltaTime;
        this.velocity.y += this.acceleration.y * deltaTime;
        this.age += deltaTime;
    };
    Particle.prototype.draw = function (context, image) {
        function ease(t) {
            return (--t) * t * t + 1;
        }
        var size = image.width * ease(this.age / settings.particles.duration);
        context.globalAlpha = 1 - this.age / settings.particles.duration;
        context.drawImage(image, this.position.x - size / 2, this.position.y - size / 2, size, size);
    };
    return Particle;
})();
  • Particle 类:表示一个粒子。
  • initialize 方法:初始化粒子的位置、速度和加速度。
  • update 方法:更新粒子的位置、速度和年龄。
  • draw 方法:在画布上绘制粒子,使用 ease 函数控制粒子的大小和透明度。
2.2.2.5 ParticlePool 类
var ParticlePool = (function () {
    var particles,
        firstActive = 0,
        firstFree = 0,
        duration = settings.particles.duration;
​
    function ParticlePool(length) {
        particles = new Array(length);
        for (var i = 0; i < particles.length; i++)
            particles[i] = new Particle();
    }
    ParticlePool.prototype.add = function (x, y, dx, dy) {
        particles[firstFree].initialize(x, y, dx, dy);
        firstFree++;
        if (firstFree == particles.length) firstFree = 0;
        if (firstActive == firstFree) firstActive++;
        if (firstActive == particles.length) firstActive = 0;
    };
    ParticlePool.prototype.update = function (deltaTime) {
        if (firstActive < firstFree) {
            for (var i = firstActive; i < firstFree; i++)
                particles[i].update(deltaTime);
        }
        if (firstFree < firstActive) {
            for (var i = firstActive; i < particles.length; i++)
                particles[i].update(deltaTime);
            for (var i = 0; i < firstFree; i++)
                particles[i].update(deltaTime);
        }
        while (particles[firstActive].age >= duration && firstActive != firstFree) {
            firstActive++;
            if (firstActive == particles.length) firstActive = 0;
        }
    };
    ParticlePool.prototype.draw = function (context, image) {
        if (firstActive < firstFree) {
            for (var i = firstActive; i < firstFree; i++)
                particles[i].draw(context, image);
        }
        if (firstFree < firstActive) {
            for (var i = firstActive; i < particles.length; i++)
                particles[i].draw(context, image);
            for (var i = 0; i < firstFree; i++)
                particles[i].draw(context, image);
        }
    };
    return ParticlePool;
})();
  • ParticlePool 类:管理粒子池,包括添加、更新和绘制粒子。
  • add 方法:向粒子池中添加新的粒子。
  • update 方法:更新所有活动粒子的状态。
  • draw 方法:在画布上绘制所有活动粒子。
2.2.2.6 主程序
(function (canvas) {
    var context = canvas.getContext('2d'),
        particles = new ParticlePool(settings.particles.length),
        particleRate = settings.particles.length / settings.particles.duration, // 粒子/秒
        time;
​
    function pointOnHeart(t) {
        return new Point(
            160 * Math.pow(Math.sin(t), 3),
            130 * Math.cos(t) - 50 * Math.cos(2 * t) - 20 * Math.cos(3 * t) - 10 * Math.cos(4 * t) + 25
        );
    }
​
    var image = (function () {
        var canvas = document.createElement('canvas'),
            context = canvas.getContext('2d');
        canvas.width = settings.particles.size;
        canvas.height = settings.particles.size;
​
        function to(t) {
            var point = pointOnHeart(t);
            point.x = settings.particles.size / 2 + point.x * settings.particles.size / 350;
            point.y = settings.particles.size / 2 - point.y * settings.particles.size / 350;
            return point;
        }
​
        context.beginPath();
        var t = -Math.PI;
        var point = to(t);
        context.moveTo(point.x, point.y);
        while (t < Math.PI) {
            t += 0.01;
            point = to(t);
            context.lineTo(point.x, point.y);
        }
        context.closePath();
        context.fillStyle = '#ea80b0';
        context.fill();
​
        var image = new Image();
        image.src = canvas.toDataURL();
        return image;
    })();
​
    function render() {
        requestAnimationFrame(render);
​
        var newTime = new Date().getTime() / 1000,
            deltaTime = newTime - (time || newTime);
        time = newTime;
​
        context.clearRect(0, 0, canvas.width, canvas.height);
​
        var amount = particleRate * deltaTime;
        for (var i = 0; i < amount; i++) {
            var pos = pointOnHeart(Math.PI - 2 * Math.PI * Math.random());
            var dir = pos.clone().length(settings.particles.velocity);
            particles.add(canvas.width / 2 + pos.x, canvas.height / 2 - pos.y, dir.x, -dir.y);
        }
​
        particles.update(deltaTime);
        particles.draw(context, image);
    }
​
    function onResize() {
        canvas.width = canvas.clientWidth;
        canvas.height = canvas.clientHeight;
    }
    window.onresize = onResize;
​
    setTimeout(function () {
        onResize();
        render();
    }, 10);
})(document.getElementById('pinkboard'));
  • pointOnHeart 函数:计算心形路径上的点。
  • image 变量:创建一个表示心形图案的图像。
  • render 函数:主渲染循环,负责更新和绘制粒子。
  • onResize 函数:处理窗口大小变化,重新设置画布的尺寸。
  • setTimeout:延迟启动渲染循环,确保初始渲染时画布尺寸正确。

三、总结

本文详细介绍了如何使用HTML5 Canvas实现一个动态的爱心动画效果。通过解析代码,我们了解了粒子系统的实现原理,以及如何利用JavaScript和Canvas API进行动画渲染。希望本文能为前端开发者提供一些灵感和技术参考,帮助大家在项目中实现更多有趣的效果。

四、扩展阅读

  • HTML5 Canvas 官方文档:https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
  • JavaScript 实现动画效果:https://www.cdsy.xyz/computer/programme/js/20210307/cd161511120212595.html
  • 粒子系统原理:https://en.wikipedia.org/wiki/Particle_system

通过学习这些资源,读者可以进一步深入了解和掌握前端动画开发的相关知识和技术。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐