两颗爱心缓慢从两侧朝中间靠近,时间大约5秒,这两爱心缓慢跳动并且散发粒子效果,直到合为一个是这两颗爱心大小的两倍,并且有粒子效果,爱心颜色为粉白色的代码
时间: 2025-07-19 09:26:35 浏览: 6
<think>我们将创建一个动画,展示两个爱心从屏幕两侧向中间移动,合并成一个更大的爱心,并伴随粒子效果。
步骤:
1. 创建两个爱心(使用CSS或Canvas绘制),分别位于屏幕左右两侧。
2. 使用动画让两个爱心向中间移动,同时可以添加跳动效果(缩放动画)。
3. 当两个爱心相遇时,它们合并成一个更大的爱心(两倍大小),并伴随粒子爆炸效果。
4. 合并后的爱心继续显示粒子效果(比如粒子从爱心周围散开又聚集)。
由于需要复杂的动画和粒子效果,我们将使用Canvas来实现。
实现思路:
1. 使用Canvas绘制爱心(使用数学函数生成爱心形状的点)。
2. 定义两个爱心的位置(初始位置在屏幕两侧),并设置移动的目标位置(屏幕中心)。
3. 在移动过程中,每个爱心也会有自己的粒子效果(比如爱心周围有粒子环绕)。
4. 当两个爱心到达中心位置时,隐藏这两个爱心,并在中心位置生成一个更大的爱心,同时触发粒子爆炸效果(粒子从中心向外扩散,然后逐渐回到爱心形状)。
5. 合并后的爱心持续显示粒子效果。
我们将使用面向对象的方式创建粒子系统。
注意:为了简化,我们可以将整个过程分为几个阶段:
阶段1:两个爱心从两侧向中间移动(同时有跳动效果)。
阶段2:爱心合并,伴随粒子爆炸。
阶段3:显示合并后的爱心,并持续粒子效果。
由于代码较长,我们将逐步实现。
爱心生成函数(参数方程):
x = 16 * (sin(t))^3
y = 13 * cos(t) - 5 * cos(2t) - 2 * cos(3t) - cos(4t)
注意:这个函数生成的爱心是倒置的,我们需要翻转y轴。
我们将使用两个类:
1. Heart 类:表示爱心对象,包含位置、大小、颜色等属性,以及绘制和更新方法。
2. Particle 类:表示粒子,用于创建粒子效果。
动画循环:使用requestAnimationFrame。
由于用户要求粉白色爱心,我们可以设置爱心的颜色为粉红色渐变。
步骤代码:
1. 创建Canvas并设置全屏
2. 定义Heart类
3. 定义Particle类
4. 创建两个爱心对象(leftHeart和rightHeart)
5. 创建粒子数组(用于两个爱心移动时的粒子效果和合并时的爆炸效果)
6. 实现动画循环,根据阶段更新爱心位置和粒子状态
阶段控制:
使用一个变量stage来表示当前阶段:
stage 0: 两个爱心向中间移动(持续3秒)
stage 1: 爱心合并爆炸效果(持续1秒)
stage 2: 显示合并后的爱心并持续粒子效果
注意:为了简化,我们使用时间线控制,而不是真正的秒数(用帧数估算)。
具体实现:</think># 爱心合并动画特效
我将为您创建一个精美的动画效果:两个粉白色爱心从屏幕两侧缓慢靠近并跳动,伴随粒子效果,最终合并为一个两倍大小的爱心,并继续显示粒子效果。
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>爱心合并动画特效</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #1a1a2e, #16213e, #0f3460);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
font-family: 'Arial', sans-serif;
color: #fff;
}
.container {
position: relative;
width: 100%;
max-width: 900px;
text-align: center;
z-index: 10;
padding: 20px;
}
h1 {
font-size: 3.5rem;
margin-bottom: 20px;
background: linear-gradient(45deg, #ff7eb9, #ff65a3, #ff1493);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: pulse 3s infinite;
text-shadow: 0 0 15px rgba(255, 105, 180, 0.3);
}
.subtitle {
font-size: 1.4rem;
margin-bottom: 30px;
color: #ffb6c1;
line-height: 1.6;
}
.hearts-container {
position: relative;
margin: 30px auto;
width: 100%;
height: 300px;
}
.heart {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 120px;
height: 110px;
transition: all 1.5s ease-in-out;
}
.heart.left {
left: 20%;
}
.heart.right {
right: 20%;
}
.heart::before,
.heart::after {
content: "";
position: absolute;
top: 0;
width: 60px;
height: 96px;
background: linear-gradient(45deg, #ffb6c1, #ff9dbb);
border-radius: 60px 60px 0 0;
box-shadow: 0 0 20px rgba(255, 182, 193, 0.7);
}
.heart::before {
left: 60px;
transform: rotate(-45deg);
transform-origin: 0 100%;
}
.heart::after {
left: 0;
transform: rotate(45deg);
transform-origin: 100% 100%;
}
.merged-heart {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0);
width: 240px;
height: 220px;
opacity: 0;
z-index: 5;
}
.merged-heart::before,
.merged-heart::after {
content: "";
position: absolute;
top: 0;
width: 120px;
height: 192px;
background: linear-gradient(45deg, #ff7eb9, #ff65a3);
border-radius: 120px 120px 0 0;
box-shadow: 0 0 40px rgba(255, 105, 180, 0.8);
}
.merged-heart::before {
left: 120px;
transform: rotate(-45deg);
transform-origin: 0 100%;
}
.merged-heart::after {
left: 0;
transform: rotate(45deg);
transform-origin: 100% 100%;
}
.message {
margin: 30px auto;
padding: 25px;
border-radius: 15px;
background: rgba(255, 182, 193, 0.1);
border: 1px solid rgba(255, 105, 180, 0.3);
width: 100%;
max-width: 600px;
text-align: center;
backdrop-filter: blur(5px);
opacity: 0;
transform: translateY(20px);
transition: all 1.5s ease 3.5s;
}
.message p {
font-size: 1.3rem;
line-height: 1.8;
color: #ffb6c1;
margin: 10px 0;
}
.controls {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 20px;
}
button {
padding: 12px 30px;
border: none;
border-radius: 30px;
background: linear-gradient(45deg, #ff7eb9, #ff65a3);
color: white;
font-size: 1.1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(255, 105, 180, 0.3);
}
button:hover {
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(255, 105, 180, 0.5);
}
button:active {
transform: translateY(1px);
}
canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.footer {
margin-top: 20px;
text-align: center;
color: #ffb6c1;
font-size: 1rem;
z-index: 10;
opacity: 0;
transition: opacity 1.5s ease 4s;
}
@keyframes pulse {
0% { text-shadow: 0 0 15px rgba(255, 105, 180, 0.3); }
50% { text-shadow: 0 0 25px rgba(255, 105, 180, 0.6), 0 0 35px rgba(255, 20, 147, 0.4); }
100% { text-shadow: 0 0 15px rgba(255, 105, 180, 0.3); }
}
@keyframes heartbeat {
0% { transform: translateY(-50%) scale(1); }
50% { transform: translateY(-50%) scale(1.1); }
100% { transform: translateY(-50%) scale(1); }
}
@media (max-width: 768px) {
h1 { font-size: 2.5rem; }
.subtitle { font-size: 1.1rem; }
.heart {
width: 80px;
height: 73px;
}
.heart::before,
.heart::after {
width: 40px;
height: 64px;
}
.heart.left { left: 10%; }
.heart.right { right: 10%; }
.merged-heart {
width: 160px;
height: 147px;
}
.merged-heart::before,
.merged-heart::after {
width: 80px;
height: 128px;
}
.message p { font-size: 1.1rem; }
}
</style>
</head>
<body>
<div class="container">
<h1>爱心合并动画特效</h1>
<p class="subtitle">两颗心相遇,合二为一,永恒相伴</p>
<div class="hearts-container">
<div class="heart left" id="leftHeart"></div>
<div class="heart right" id="rightHeart"></div>
<div class="merged-heart" id="mergedHeart"></div>
</div>
<div class="message" id="message">
<p>我们的相遇就像这两颗心</p>
<p>从遥远的地方慢慢靠近</p>
<p>最终融为一体,永不分离</p>
</div>
<div class="controls">
<button id="replayBtn">重新播放动画</button>
</div>
<div class="footer" id="footer">
粒子效果会持续显示 | 点击按钮重新播放动画
</div>
</div>
<canvas id="particleCanvas"></canvas>
<script>
// 获取Canvas元素和上下文
const canvas = document.getElementById('particleCanvas');
const ctx = canvas.getContext('2d');
const leftHeart = document.getElementById('leftHeart');
const rightHeart = document.getElementById('rightHeart');
const mergedHeart = document.getElementById('mergedHeart');
const message = document.getElementById('message');
const footer = document.getElementById('footer');
const replayBtn = document.getElementById('replayBtn');
// 设置Canvas尺寸为窗口大小
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
// 初始调整尺寸
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// 粒子类
class Particle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.originalX = x;
this.originalY = y;
this.color = color;
this.size = Math.random() * 3 + 1;
this.speedX = Math.random() * 2 - 1;
this.speedY = Math.random() * 2 - 1;
this.gravity = 0.05;
this.resistance = 0.98;
this.alpha = 1;
this.life = 100;
this.targetScale = 1;
this.scattered = false;
}
update() {
if (this.scattered) {
// 粒子分散状态
this.speedY += this.gravity;
this.speedX *= this.resistance;
this.speedY *= this.resistance;
this.x += this.speedX;
this.y += this.speedY;
// 随机移动
if (Math.random() < 0.1) {
this.speedX += (Math.random() - 0.5) * 0.5;
this.speedY += (Math.random() - 0.5) * 0.5;
}
// 减少生命周期
this.life -= 0.5;
this.alpha = this.life / 100;
} else {
// 计算回到原始位置的力
const dx = this.originalX - this.x;
const dy = this.originalY - this.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// 添加重力
this.speedY += this.gravity;
// 添加回到原始位置的力
if (distance > 1) {
this.speedX += dx * 0.01;
this.speedY += dy * 0.01;
}
// 应用阻力
this.speedX *= this.resistance;
this.speedY *= this.resistance;
// 更新位置
this.x += this.speedX;
this.y += this.speedY;
}
}
draw() {
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size * this.targetScale, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
scatter() {
this.scattered = true;
this.speedX = (Math.random() - 0.5) * 10;
this.speedY = (Math.random() - 0.5) * 10;
this.gravity = 0.1;
this.life = 100;
}
reset() {
this.scattered = false;
this.life = 100;
this.alpha = 1;
this.x = this.originalX + (Math.random() - 0.5) * 100;
this.y = this.originalY + (Math.random() - 0.5) * 100;
this.speedX = Math.random() * 4 - 2;
this.speedY = Math.random() * 4 - 2;
}
isAlive() {
return this.life > 0;
}
}
// 爱心生成函数
function heartFunction(t, scale = 10) {
// 爱心参数方程
const x = 16 * Math.pow(Math.sin(t), 3);
const y = -(13 * Math.cos(t) - 5 * Math.cos(2*t) - 2 * Math.cos(3*t) - Math.cos(4*t));
return { x: x * scale, y: y * scale };
}
// 生成爱心形状的粒子
function createHeartParticles(centerX, centerY, count, scale = 10) {
const particles = [];
for (let i = 0; i < count; i++) {
const t = (i / count) * Math.PI * 2;
const point = heartFunction(t, scale);
// 随机颜色
const r = Math.floor(Math.random() * 55 + 200);
const g = Math.floor(Math.random() * 50 + 100);
const b = Math.floor(Math.random() * 100 + 150);
const color = `rgb(${r}, ${g}, ${b})`;
particles.push(new Particle(
centerX + point.x,
centerY + point.y,
color
));
}
return particles;
}
// 全局变量
let particles = [];
let particleCount = 200;
let animationPhase = 0;
let animationProgress = 0;
let mergedParticles = [];
// 初始化粒子
function initParticles() {
// 清除现有粒子
particles = [];
// 为左侧爱心创建粒子
const leftRect = leftHeart.getBoundingClientRect();
const leftCenterX = leftRect.left + leftRect.width / 2;
const leftCenterY = leftRect.top + leftRect.height / 2;
particles = particles.concat(createHeartParticles(leftCenterX, leftCenterY, particleCount/2, 4));
// 为右侧爱心创建粒子
const rightRect = rightHeart.getBoundingClientRect();
const rightCenterX = rightRect.left + rightRect.width / 2;
const rightCenterY = rightRect.top + rightRect.height / 2;
particles = particles.concat(createHeartParticles(rightCenterX, rightCenterY, particleCount/2, 4));
// 为合并爱心创建粒子(初始不可见)
const mergedRect = mergedHeart.getBoundingClientRect();
const mergedCenterX = mergedRect.left + mergedRect.width / 2;
const mergedCenterY = mergedRect.top + mergedRect.height / 2;
mergedParticles = createHeartParticles(mergedCenterX, mergedCenterY, particleCount, 8);
}
// 开始动画
function startAnimation() {
// 重置状态
animationPhase = 0;
animationProgress = 0;
// 重置爱心位置
leftHeart.style.left = '20%';
rightHeart.style.right = '20%';
leftHeart.style.animation = 'heartbeat 1s infinite';
rightHeart.style.animation = 'heartbeat 1s infinite 0.5s';
// 隐藏合并爱心
mergedHeart.style.opacity = '0';
mergedHeart.style.transform = 'translate(-50%, -50%) scale(0)';
// 隐藏消息和页脚
message.style.opacity = '0';
message.style.transform = 'translateY(20px)';
footer.style.opacity = '0';
// 初始化粒子
initParticles();
// 开始动画循环
animate();
}
// 动画循环
function animate() {
// 清除画布,使用半透明背景实现拖尾效果
ctx.fillStyle = 'rgba(10, 8, 30, 0.2)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 根据动画阶段更新
switch(animationPhase) {
case 0: // 爱心向中间移动
animationProgress += 0.005;
if (animationProgress >= 1) {
animationPhase = 1;
animationProgress = 0;
// 添加合并粒子效果
particles.forEach(p => p.scatter());
// 显示合并爱心
setTimeout(() => {
mergedHeart.style.opacity = '1';
mergedHeart.style.transform = 'translate(-50%, -50%) scale(1)';
message.style.opacity = '1';
message.style.transform = 'translateY(0)';
footer.style.opacity = '1';
}, 800);
} else {
// 移动爱心
leftHeart.style.left = `${20 - animationProgress * 18}%`;
rightHeart.style.right = `${20 - animationProgress * 18}%`;
}
break;
case 1: // 粒子分散效果
animationProgress += 0.01;
if (animationProgress >= 1) {
animationPhase = 2;
}
break;
case 2: // 合并爱心粒子效果
// 持续显示合并爱心粒子效果
break;
}
// 更新和绘制所有粒子
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].update();
particles[i].draw();
// 移除已完成的粒子
if (animationPhase >= 1 && !particles[i].isAlive()) {
particles.splice(i, 1);
}
}
// 绘制合并爱心粒子
if (animationPhase >= 1) {
mergedParticles.forEach(p => {
p.update();
p.draw();
// 随机移动粒子
if (Math.random() < 0.05) {
p.speedX += (Math.random() - 0.5) * 0.5;
p.speedY += (Math.random() - 0.5) * 0.5;
}
});
}
requestAnimationFrame(animate);
}
// 添加重新播放按钮事件
replayBtn.addEventListener('click', startAnimation);
// 页面加载完成后开始动画
window.addEventListener('load', () => {
// 添加爱心跳动动画
leftHeart.style.animation = 'heartbeat 1s infinite';
rightHeart.style.animation = 'heartbeat 1s infinite 0.5s';
// 开始动画
setTimeout(startAnimation, 1000);
});
</script>
</body>
</html>
```
## 实现原理
这个爱心合并动画特效包含以下关键部分:
1. **爱心结构**:
- 使用CSS伪元素(::before和::after)创建爱心形状
- 通过旋转两个半圆形成爱心形状
- 数学表达式:$x = 16 \sin^3(t)$, $y = -(13 \cos(t) - 5 \cos(2t) - 2 \cos(3t) - \cos(4t))$[^1]
2. **动画阶段**:
- **阶段0**:两个爱心从屏幕两侧向中间移动,同时有跳动效果
- **阶段1**:爱心相遇时触发粒子爆炸效果
- **阶段2**:显示合并后的爱心并持续粒子效果
3. **粒子系统**:
- 使用Canvas创建粒子效果
- 每个粒子有位置、速度、生命周期等属性
- 粒子根据爱心形状分布,使用参数方程生成位置
4. **视觉效果**:
- 粉白色渐变爱心,带有发光效果
- 粒子拖尾效果增强视觉体验
- 深蓝色渐变背景突出爱心效果
- 响应式设计适配不同屏幕尺寸
## 使用方法
1. 打开页面后,动画会自动开始
2. 观察两个爱心从屏幕两侧向中间移动并跳动
3. 当爱心相遇时,会触发粒子爆炸效果
4. 爱心合并成一个更大的爱心,并持续显示粒子效果
5. 点击"重新播放动画"按钮可以重新观看效果
这个动画完全使用原生JavaScript和CSS实现,无需任何外部库,可在所有现代浏览器中运行。
阅读全文
相关推荐












