Using Keyframes – WebKit CSS Animation Examples

本文介绍如何使用CSS动画实现雪花飘落效果。通过@-webkit-keyframes定义动画关键帧,并结合-webkit-animation-name等属性控制动画播放。文章还展示了如何利用-webkit-transform进行旋转和平移。

Now WebKit supports explicit CSS animations! After seeing the new animation examples posted onWebKit.org, I needed to test keyframes by myself.
So I have created a dumb-downed version of the fallen leaves seen on webkit.org blog, called “Let it Snow”.

Unlike the fallen leaves example, I stick strictly with CSS only (means zero JavaScript). Also I tested on Webkit nightly and an iPhone (OS 2.0) Safari. On my iPhone (Mozilla/5.0 (iPhone; U; CPU iPhone OS 2_2 like Mac OS X; en-us) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5G77 Safari/525.20), the animation is slow and some feature is ingored.

Well, let’s see the “Let It Snow” animation in action!

How to use Keyframes?

Keyframes are specified with the CSS “At-Rule” by using the keyword,@-webkit-keyframes, followed by an identifier (= animation-name)

	
@-webkit-keyframes animation-name {
 from {
   style definition ["Before"-state]
 }
 to {
   style definition ["After"-state]
 }
}
	

A keyframe defines the styles applied within the animation. To specify multiple frames, use “%” instead of “from” and “to” keywords.
Here’s an actual example I used for “Let it Snow”.

	
@-webkit-keyframes fade {
  0%   { opacity: 0; }
  10%  { opacity: 0.8; }
  100% { opacity: 0; }
}
	

This style is applie to create each snow flake appearance. A snowflake blurry appears (increase opacity) when 10% of the time elapsed (The total time is defined later. I’ll explain it next).
And at the end, the snowflake disappears (opacity back to zero).

Once the animation timeframe is defined, apply it using -webkit-animation-name and related properties.
I set total animation duration as 5 seconds, and the animatin goes forever (= infinite times. The default is 1).
See the simplified example below.

	
#snow div {
  -webkit-animation-name: fade;
  -webkit-animation-duration: 5s;
  -webkit-animation-iteration-count: infinite;
}
	
	
<div id="snow" class="snow">	
  <div>&#10053;</div> /* an entity for ❅ */
</div>
	

Using Transform

Let’s rotate and move around snowflakes by using -webkit-transform.
rotate, of course, rotate the element, and translate specifies a 2D translation by the vector [tx, ty]. (For more explanations, please see CSS transform spec page).
I used percent, 0 and 100% here, but of course you can use “from” and “to”.
Also note that transform doesn’t seem to work on current iPhone Safari yet.

	
@-webkit-keyframes spin{
  0%   { -webkit-transform: rotate(-180deg) translate(0px, 0px);}
  100% { -webkit-transform: rotate(180deg) translate(10px, 75px);}
}			
	

You can just add the amination-name to the #snow div selector, separating with comma.

	
#snow div {
  -webkit-animation-name: fade, spin;
  ...
}
	

More

For the “Let it snow” example, I also include the cheesy “accumulate” keyframe to make snow accumulate on ground. Kinda ugly though.
Moreover, I gave the -webkit-animation-duration to individual snowflake so all flakes don’t fall all together!

	
.snowflake {
  color: #fff;
  font-size: 2em;
  position: absolute; (Note: The parent container is set relative positioned!)
}
.snowflake.f1 {
  left: 40px;
  -webkit-animation-duration: 5s;
}
.snowflake.f2 {
  font-size: 1.8em;
  left: 120px;
  -webkit-animation-duration: 7s;	
}
...
	
	
<div id="snow" class="snow">	
  <div class="snowflake f1">&#10053;</div> /* an entity for ❅ */
  <div class="snowflake f2">&#10052;</div> /* an entity for ❄ */
  ... (add two more snowflake-div in the actual sample)
</div>
	

To view the entire markup and CSS, just view source of the sample file!

基于分布式模型预测控制的多个固定翼无人机一致性控制(Matlab代码实现)内容概要:本文围绕“基于分布式模型预测控制的多个固定翼无人机一致性控制”展开,采用Matlab代码实现相关算法,属于顶级EI期刊的复现研究成果。文中重点研究了分布式模型预测控制(DMPC)在多无人机系统中的一致性控制问题,通过构建固定翼无人机的动力学模型,结合分布式协同控制策略,实现多无人机在复杂环境下的轨迹一致性和稳定协同飞行。研究涵盖了控制算法设计、系统建模、优化求解及仿真验证全过程,并提供了完整的Matlab代码支持,便于读者复现实验结果。; 适合人群:具备自动控制、无人机系统或优化算法基础,从事科研或工程应用的研究生、科研人员及自动化、航空航天领域的研发工程师;熟悉Matlab编程和基本控制理论者更佳; 使用场景及目标:①用于多无人机协同控制系统的算法研究与仿真验证;②支撑科研论文复现、毕业设计或项目开发;③掌握分布式模型预测控制在实际系统中的应用方法,提升对多智能体协同控制的理解与实践能力; 阅读建议:建议结合提供的Matlab代码逐模块分析,重点关注DMPC算法的构建流程、约束处理方式及一致性协议的设计逻辑,同时可拓展学习文中提及的路径规划、编队控制等相关技术,以深化对无人机集群控制的整体认知。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <TITLE></TITLE> <META NAME="Generator" CONTENT="EditPlus"> <META NAME="Author" CONTENT=""> <META NAME="Keywords" CONTENT=""> <META NAME="Description" CONTENT=""> <style> html, body { height: 100%; padding: 0; margin: 0; background: #000; } canvas { position: absolute; width: 100%; height: 100%; } .child { position: fixed; top: 50%; left: 50%; margin-top: -75px; margin-left: -100px; } h4 { font-size: 30px; font-family:'Verdana, Geneva, Tahoma, sans-serif'; color: #ffffff; position: relative; left: 10px; } </style> </HEAD> <BODY> <div class="child"> <h4>I LOVE YOU 王奕蕾</h4> </div> <canvas id="pinkboard"></canvas> <script> /* * Settings */ var settings = { particles: { length: 500, // maximum amount of particles duration: 2, // particle duration in sec velocity: 100, // particle velocity in pixels/sec effect: -0.75, // play with this for a nice effect size: 30, // particle size in pixels }, }; /* * RequestAnimationFrame polyfill by Erik Möller */ (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) } } }()); /* * Point class */ 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; })(); /* * Particle class */ 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; })(); /* * ParticlePool class */ var ParticlePool = (function () { var particles, firstActive = 0, firstFree = 0, duration = settings.particles.duration; function ParticlePool(length) { // create and populate particle pool 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); // handle circular queue firstFree++; if (firstFree == particles.length) firstFree = 0; if (firstActive == firstFree) firstActive++; if (firstActive == particles.length) firstActive = 0; }; ParticlePool.prototype.update = function (deltaTime) { var i; // update active particles if (firstActive < firstFree) { for (i = firstActive; i < firstFree; i++) particles[i].update(deltaTime); } if (firstFree < firstActive) { for (i = firstActive; i < particles.length; i++) particles[i].update(deltaTime); for (i = 0; i < firstFree; i++) particles[i].update(deltaTime); } // remove inactive particles while (particles[firstActive].age >= duration && firstActive != firstFree) { firstActive++; if (firstActive == particles.length) firstActive = 0; } }; ParticlePool.prototype.draw = function (context, image) { // draw active particles if (firstActive < firstFree) { for (i = firstActive; i < firstFree; i++) particles[i].draw(context, image); } if (firstFree < firstActive) { for (i = firstActive; i < particles.length; i++) particles[i].draw(context, image); for (i = 0; i < firstFree; i++) particles[i].draw(context, image); } }; return ParticlePool; })(); /* * Putting it all together */ (function (canvas) { var context = canvas.getContext('2d'), particles = new ParticlePool(settings.particles.length), particleRate = settings.particles.length / settings.particles.duration, // particles/sec time; // get point on heart with -PI <= t <= PI 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 ); } // creating the particle image using a dummy canvas var image = (function () { var canvas = document.createElement('canvas'), context = canvas.getContext('2d'); canvas.width = settings.particles.size; canvas.height = settings.particles.size; // helper function to create the path 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; } // create the path context.beginPath(); var t = -Math.PI; var point = to(t); context.moveTo(point.x, point.y); while (t < Math.PI) { t += 0.01; // baby steps! point = to(t); context.lineTo(point.x, point.y); } context.closePath(); // create the fill context.fillStyle = '#ea80b0'; context.fill(); // create the image var image = new Image(); image.src = canvas.toDataURL(); return image; })(); // render that thing! function render() { // next animation frame requestAnimationFrame(render); // update time var newTime = new Date().getTime() / 1000, deltaTime = newTime - (time || newTime); time = newTime; // clear canvas context.clearRect(0, 0, canvas.width, canvas.height); // create new particles 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); } // update and draw particles particles.update(deltaTime); particles.draw(context, image); } // handle (re-)sizing of the canvas function onResize() { canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; } window.onresize = onResize; // delay rendering bootstrap setTimeout(function () { onResize(); render(); }, 10); })(document.getElementById('pinkboard')); </script> </BODY> </HTML>
08-15
<think>我们被要求创建一个带有粒子动画效果的表白网页。根据引用[1][2][3][4]中的描述,这类网页通常包含以下元素: 1. 粒子动画(如烟花、爱心、文字粒子等) 2. 浪漫的背景和文字 3. 背景音乐 4. 3D相册或图片展示 5. 可自定义的文字和图片 但是,用户特别提到了“粒子动画效果”,因此我们将重点放在粒子动画上。我们可以创建一个粒子系统,粒子可以形成文字(比如“I Love You”)或者爱心等图案,并且粒子可以动态运动(如烟花爆炸、漂浮等)。 设计思路: 1. 使用HTML5 Canvas来绘制粒子动画。 2. 粒子可以随机生成,也可以按照特定的文字形状生成。 3. 粒子具有位置、速度、大小等属性,并随时间更新。 4. 可以添加鼠标交互,例如鼠标移动时粒子跟随或避开。 我们将创建一个简单的粒子系统,粒子从屏幕底部向上飘动(模拟烟花上升),然后爆炸(粒子散开)形成爱心或文字。 由于引用中提到了多个效果,我们这里选择实现一个粒子形成文字(自定义表白文字)的动画,并具有烟花爆炸的效果。 步骤: 1. 创建HTML结构,包含一个canvas元素。 2. 使用CSS设置canvas为全屏,并设置背景为浪漫的颜色(如黑色或深蓝色,模拟夜空)。 3. 使用JavaScript编写粒子系统。 粒子系统设计: - 粒子类(Particle):包含位置、速度、大小、颜色、生命周期等属性,以及更新和绘制的方法。 - 粒子系统(ParticleSystem):管理多个粒子,包括生成、更新和销毁粒子。 - 文字粒子:将文字转换为粒子位置(我们可以预先计算文字在画布上的位置,然后让粒子向这些位置移动)。 为了简化,我们可以分阶段: 阶段1:粒子从屏幕底部随机位置向上运动(模拟上升的烟花)。 阶段2:当粒子到达一定高度(或随机时间)时,爆炸成多个粒子,这些粒子向四周散开,然后逐渐消失。 阶段3:在爆炸后,我们可以让粒子重新组合成爱心或文字。 但是,同时实现上升、爆炸和重组比较复杂。因此,我们可以选择实现一个更简单的效果:粒子从屏幕中心向外扩散,然后逐渐形成文字。 参考引用[2]中的“云雾状粒子文字3D开场”,我们可以让粒子从中心向外扩散,然后聚集形成文字。 具体步骤: 1. 初始化canvas,获取上下文。 2. 定义粒子数组。 3. 初始化粒子:在中心位置生成,并给一个随机的速度和方向(向外扩散)。 4. 更新粒子:每一帧更新粒子的位置(根据速度),并逐渐减小速度(模拟阻力),然后当粒子速度接近0时,开始向目标位置(文字形状的位置)移动。 5. 文字形状:我们可以使用一个离屏canvas,将文字绘制上去,然后获取文字像素位置,作为粒子的目标位置。 或者,我们可以预先定义好文字的形状(比如用点阵表示),然后让粒子移动到这些点阵的位置。 为了简单,我们采用以下方法: - 首先,我们定义要显示的文字,比如“I Love You”。 - 然后,我们创建一个点阵数组(目标位置数组),这些点阵是文字在画布上的位置。 - 在初始化时,粒子在屏幕中心随机位置,然后向外扩散。扩散后,我们将每个粒子的目标位置设置为点阵数组中的一个位置(按顺序分配)。 注意:粒子数量需要和点阵数量一致,或者我们可以一个点阵位置由多个粒子组成(这里我们一个点阵位置只分配一个粒子)。 实现步骤: 1. 设置画布,并获取点阵数据(通过将文字绘制到离屏canvas,然后获取非透明像素的位置)。 2. 创建粒子数组,数量与点阵数量相同。 3. 初始状态:所有粒子集中在屏幕中心附近,并有一个随机的初速度(向外)。 4. 然后,粒子在阻力作用下减速,之后开始向各自的目标位置移动(使用缓动函数)。 由于用户要求是表白网页,我们还可以添加背景音乐和自定义文字的功能。 但是,由于代码量限制,我们这里先实现粒子动画部分。 代码结构: HTML部分: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>粒子动画表白</title> <style> body { margin:0; overflow:hidden; background:#000; } canvas { display:block; } </style> </head> <body> <canvas id="canvas"></canvas> <script> // JavaScript代码 </script> </body> </html> JavaScript部分: 我们将编写一个完整的粒子动画脚本。 由于获取文字点阵数据需要离屏渲染,我们可以在初始化时创建一个离屏canvas,将文字绘制上去,然后遍历像素获取位置。 注意:为了性能,我们不会每一帧都获取点阵,而是在初始化时获取一次。 步骤: 1. 创建主画布和上下文。 2. 创建离屏画布,设置相同的尺寸。 3. 在离屏画布上绘制文字(白色,大字体)。 4. 遍历离屏画布的像素数据,收集非透明像素的位置(每隔一定步长取一个点,避免点太密集)。 5. 根据收集到的位置数组创建粒子,每个粒子的初始位置在屏幕中心附近,目标位置为对应的点阵位置。 6. 动画循环:更新粒子位置(先向外扩散,然后减速,再向目标位置移动),绘制粒子。 粒子状态: - 扩散阶段:粒子从中心向外运动,速度逐渐减小。 - 归位阶段:当扩散速度接近0时,粒子开始向目标位置移动(使用缓动函数,比如:当前位置与目标位置的距离乘以一个系数作为速度分量)。 为了美观,我们还可以在粒子运动过程中改变颜色和大小。 代码实现如下:</think># 粒子动画表白网页实现 我将创建一个带有粒子动画效果的表白网页,包含动态粒子系统、爱心动画和浪漫文字效果。 ## 设计思路 - 使用Canvas实现粒子系统(粒子上升、烟花爆炸效果) - 添加爱心生成动画和漂浮效果 - 实现文字粒子动画和浪漫告白语 - 添加背景音乐和交互效果 下面是完整的实现代码: ```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, #0c0e2b, #1a1a52, #2c0046); color: #fff; font-family: 'Microsoft YaHei', sans-serif; min-height: 100vh; overflow: hidden; position: relative; } #canvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; } .container { max-width: 1000px; margin: 0 auto; padding: 20px; text-align: center; position: relative; z-index: 10; } header { padding: 40px 0 30px; animation: fadeIn 2s ease-out; } h1 { font-size: 4rem; margin-bottom: 10px; text-shadow: 0 0 15px rgba(255, 0, 128, 0.8); background: linear-gradient(45deg, #ff00cc, #ff0066, #ff3366); -webkit-background-clip: text; background-clip: text; color: transparent; letter-spacing: 3px; } .subtitle { font-size: 1.5rem; margin-bottom: 40px; color: #ffccf9; } .love-container { position: relative; height: 300px; margin: 40px auto; perspective: 1000px; } .heart { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 120px; color: #ff3366; text-shadow: 0 0 20px rgba(255, 51, 102, 0.7); animation: heartbeat 1.2s infinite; z-index: 5; } .message-box { background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border-radius: 20px; padding: 30px; margin: 40px 0; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.1); max-width: 800px; margin: 0 auto; } .message { font-size: 1.8rem; line-height: 1.6; margin-bottom: 30px; font-weight: 300; text-shadow: 0 0 10px rgba(255, 255, 255, 0.5); } .message span { display: inline-block; transition: all 0.3s ease; } .message span:hover { color: #ff66cc; transform: scale(1.2); text-shadow: 0 0 20px #ff66cc; } .controls { margin: 30px 0; } button { background: linear-gradient(45deg, #ff00cc, #ff0066); color: white; border: none; padding: 15px 40px; font-size: 1.2rem; border-radius: 50px; cursor: pointer; margin: 10px 15px; transition: all 0.3s ease; box-shadow: 0 5px 15px rgba(255, 0, 102, 0.4); } button:hover { transform: translateY(-5px); box-shadow: 0 8px 20px rgba(255, 0, 102, 0.6); } .footer { margin-top: 50px; padding: 20px; color: rgba(255, 255, 255, 0.6); font-size: 1rem; } .particle-text { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; } @keyframes fadeIn { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } @keyframes heartbeat { 0% { transform: translate(-50%, -50%) scale(1); } 25% { transform: translate(-50%, -50%) scale(1.1); } 50% { transform: translate(-50%, -50%) scale(1); } 75% { transform: translate(-50%, -50%) scale(1.15); } 100% { transform: translate(-50%, -50%) scale(1); } } @keyframes float { 0% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-20px) rotate(10deg); } 100% { transform: translateY(0) rotate(0deg); } } @media (max-width: 768px) { h1 { font-size: 2.5rem; } .message { font-size: 1.4rem; } .heart { font-size: 80px; } } </style> </head> <body> <canvas id="canvas"></canvas> <div class="container"> <header> <h1>❤ 给最爱的你 ❤</h1> <div class="subtitle">粒子动画传递我的心意</div> </header> <div class="love-container"> <div class="heart">❤</div> </div> <div class="message-box"> <div class="message"> <span>在</span> <span>茫</span> <span>茫</span> <span>人</span> <span>海</span> <span>中</span> <span>,</span> <span>遇</span> <span>见</span> <span>你</span> <span>是</span> <span>我</span> <span>最</span> <span>美</span> <span>的</span> <span>奇</span> <span>迹</span> <span>。</span> <span>每</span> <span>一</span> <span>个</span> <span>粒</span> <span>子</span> <span>都</span> <span>代</span> <span>表</span> <span>我</span> <span>对</span> <span>你</span> <span>的</span> <span>思</span> <span>念</span> <span>。</span> </div> <div class="controls"> <button id="fireworksBtn">绽放烟花</button> <button id="heartsBtn">生成爱心</button> <button id="messageBtn">爱的告白</button> </div> </div> <div class="footer"> 每一颗粒子都承载着我对你的爱意 ❤ 永恒不变 </div> </div> <audio id="bgMusic" loop> <source src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" type="audio/mp3"> </audio> <script> // 获取Canvas元素和上下文 const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; // 粒子数组 let particles = []; let hearts = []; let textParticles = []; let fireworks = []; // 鼠标位置 const mouse = { x: null, y: null, radius: 100 }; // 监听鼠标移动 window.addEventListener('mousemove', (e) => { mouse.x = e.x; mouse.y = e.y; }); // 监听窗口大小变化 window.addEventListener('resize', () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }); // 粒子类 class Particle { constructor(x, y, color) { this.x = x; this.y = y; this.size = Math.random() * 3 + 1; this.color = color || `hsl(${Math.random() * 360}, 70%, 60%)`; this.speedX = Math.random() * 3 - 1.5; this.speedY = Math.random() * 3 - 1.5; this.life = 100; } update() { this.x += this.speedX; this.y += this.speedY; // 粒子生命衰减 if (this.life > 0) this.life -= 0.5; // 边界反弹 if (this.x <= 0 || this.x >= canvas.width) this.speedX *= -1; if (this.y <= 0 || this.y >= canvas.height) this.speedY *= -1; // 鼠标互动 const dx = mouse.x - this.x; const dy = mouse.y - this.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < mouse.radius) { this.speedX = dx * 0.05; this.speedY = dy * 0.05; } } draw() { ctx.fillStyle = this.color; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); } } // 爱心类 class Heart { constructor(x, y) { this.x = x; this.y = y; this.size = Math.random() * 20 + 10; this.speedX = Math.random() * 2 - 1; this.speedY = Math.random() * -3 - 1; this.color = `hsl(${Math.random() * 60 + 330}, 100%, 60%)`; this.rotation = Math.random() * Math.PI * 2; this.rotationSpeed = Math.random() * 0.1 - 0.05; this.life = 100; } update() { this.x += this.speedX; this.y += this.speedY; this.rotation += this.rotationSpeed; this.life -= 0.7; } draw() { ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.rotation); ctx.scale(this.size / 20, this.size / 20); ctx.fillStyle = this.color; ctx.beginPath(); ctx.moveTo(0, 0); ctx.bezierCurveTo(-10, -15, -30, 0, 0, 25); ctx.bezierCurveTo(30, 0, 10, -15, 0, 0); ctx.fill(); ctx.restore(); } } // 烟花类 class Firework { constructor(x, y) { this.x = x; this.y = y; this.particles = []; this.createParticles(); } createParticles() { const color = `hsl(${Math.random() * 360}, 100%, 60%)`; const particleCount = 100; for (let i = 0; i < particleCount; i++) { const angle = Math.random() * Math.PI * 2; const speed = Math.random() * 5 + 2; this.particles.push({ x: this.x, y: this.y, size: Math.random() * 3 + 2, color: color, speedX: Math.cos(angle) * speed, speedY: Math.sin(angle) * speed, life: 100 }); } } update() { for (let i = 0; i < this.particles.length; i++) { const p = this.particles[i]; p.x += p.speedX; p.y += p.speedY; p.speedY += 0.05; // 重力 p.life -= 1; if (p.life <= 0) { this.particles.splice(i, 1); i--; } } } draw() { for (const p of this.particles) { ctx.fillStyle = p.color; ctx.globalAlpha = p.life / 100; ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill(); } ctx.globalAlpha = 1; } } // 创建初始粒子 function createParticles(count) { for (let i = 0; i < count; i++) { const x = Math.random() * canvas.width; const y = Math.random() * canvas.height; particles.push(new Particle(x, y)); } } // 创建爱心 function createHeart() { const x = Math.random() * canvas.width; const y = canvas.height; hearts.push(new Heart(x, y)); } // 创建烟花 function createFirework() { const x = Math.random() * canvas.width; const y = Math.random() * canvas.height * 0.5; fireworks.push(new Firework(x, y)); } // 创建文字粒子 function createTextParticles(text, x, y) { ctx.font = "bold 100px Arial"; ctx.fillStyle = "white"; ctx.fillText(text, x, y); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; for (let y = 0; y < canvas.height; y += 5) { for (let x = 0; x < canvas.width; x += 5) { const index = (y * canvas.width + x) * 4; if (data[index + 3] > 128) { textParticles.push(new Particle(x, y, `hsl(${Math.random() * 60 + 300}, 100%, 70%)`)); } } } ctx.clearRect(0, 0, canvas.width, canvas.height); } // 动画循环 function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 更新和绘制背景粒子 for (let i = 0; i < particles.length; i++) { particles[i].update(); particles[i].draw(); if (particles[i].life <= 0) { particles.splice(i, 1); i--; } } // 补充新粒子 if (particles.length < 100) { createParticles(5); } // 更新和绘制爱心 for (let i = 0; i < hearts.length; i++) { hearts[i].update(); hearts[i].draw(); if (hearts[i].life <= 0) { hearts.splice(i, 1); i--; } } // 更新和绘制烟花 for (let i = 0; i < fireworks.length; i++) { fireworks[i].update(); fireworks[i].draw(); if (fireworks[i].particles.length === 0) { fireworks.splice(i, 1); i--; } } // 更新和绘制文字粒子 for (let i = 0; i < textParticles.length; i++) { textParticles[i].update(); textParticles[i].draw(); if (textParticles[i].life <= 0) { textParticles.splice(i, 1); i--; } } requestAnimationFrame(animate); } // 初始化 function init() { createParticles(100); // 按钮事件监听 document.getElementById('fireworksBtn').addEventListener('click', () => { createFirework(); }); document.getElementById('heartsBtn').addEventListener('click', () => { for (let i = 0; i < 5; i++) { setTimeout(() => createHeart(), i * 200); } }); document.getElementById('messageBtn').addEventListener('click', () => { createTextParticles("我爱你", canvas.width/2 - 150, canvas.height/2); }); // 自动生成爱心 setInterval(() => { if (Math.random() > 0.7) createHeart(); }, 1000); // 开始动画 animate(); // 播放背景音乐 const bgMusic = document.getElementById('bgMusic'); document.body.addEventListener('click', () => { bgMusic.play(); }, { once: true }); } // 启动 window.onload = init; </script> </body> </html> ``` ## 功能特点 1. **粒子动画系统**: - 背景中持续漂浮的彩色粒子 - 粒子会与鼠标交互(靠近时会被推开) - 粒子有生命周期,会逐渐消失 2. **爱心效果**: - 点击"生成爱心"按钮会创建多个爱心 - 爱心会向上漂浮并旋转 - 爱心有大小、颜色和旋转速度的随机变化 3. **烟花效果**: - 点击"绽放烟花"按钮会创建烟花爆炸效果 - 烟花粒子受重力影响,有物理运动轨迹 - 烟花粒子有生命周期,会逐渐消失 4. **文字粒子效果**: - 点击"爱的告白"按钮会生成"我爱你"文字粒子 - 文字由彩色粒子组成,粒子会自由运动 5. **交互功能**: - 背景音乐自动播放(需用户点击页面) - 鼠标移动会影响粒子运动 - 文字中的每个字都有悬停效果 6. **响应式设计**: - 适配各种屏幕尺寸 - 在移动设备上有良好表现 ## 使用说明 1. 页面加载后会自动生成背景粒子 2. 点击"生成爱心"按钮会创建漂浮的爱心 3. 点击"绽放烟花"按钮会创建烟花爆炸效果 4. 点击"爱的告白"按钮会生成文字粒子效果 5. 鼠标在页面上移动会与粒子产生互动 6. 点击页面任意位置开始播放背景音乐 这个表白网页结合了粒子系统、动画效果和浪漫元素,适合用于表白、纪念日或特殊场合的惊喜礼物。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值