资讯动态

用原生HTML5打造粒子爱心表白网页:完整代码与部署指南

发布时间:2026/9/9 6:59:55 来源:尧图企业网站定制
简介一款面向程序员及前端学习者的HTML5告白动画网页源码既能用于向心仪对象表达心意也可作为网页动画、Canvas绘图与事件交互的练手案例。压缩包内共6个文件包含3个JavaScript脚本、1个CSS样式、1个HTML页面及1个说明文档整体仅29KB结构非常精简便于直接部署到服务器或本地浏览器中查看也适合在此基础上修改文案、配色与动画节奏。整个动画全程未使用任何图片素材纯靠HTML5、CSS与JavaScript绘制鲜花绽放、飘落以及文字逐字打出的效果充分体现现代浏览器原生绘图与动画能力左侧文字呈打字机样式视觉表现力强。需要注意的是IE9以前的浏览器不支持HTML5建议使用Chrome、Firefox或新版Edge等现代浏览器浏览以获得最佳效果。目前已有3315人浏览学习适合前端学习者分析源码也适合程序员在特殊节日用代码为在意的人制造一份别致的数字惊喜。 我做了几年的前端平时也会给朋友写点小网页当礼物。有个老哥让我帮他做个表白用的页面要求是“不能让对方看出是个程序员又得让对方觉得很有心意”。我就用纯 HTML5 把粒子爱心、打字机、星空背景这些效果揉在了一起做完以后效果出奇地好。这次把这个网页的完整思路、实现方式、代码和避坑经验都整理出来想给对象一个惊喜、或者正在纠结怎么开口的朋友直接抄作业就行。1. 整体构思与技术选型1.1 为什么用原生HTML5而不是框架第一版我其实想过用 Vue 或者 React 来写但仔细琢磨以后还是放弃了。表白页面的使用场景特别清楚文件要小、打开要快、不能依赖外网的 CDN 资源、要能在手机和电脑上同时流畅打开。你要是引个框架进去再带一堆 npm 依赖万一到时候现场网络卡一下或者对方手机型号偏老整个页面白屏那就非常尴尬了。纯 HTML5 CSS3 原生 JavaScript 的好处在于所有资源都可以内联在一个 HTML 文件里体积压到几十 KB 不是问题。发到对方微信的时候点开就是秒开不转圈不加载这种体验才是最重要的。另外还有一层考虑这种特殊场合的网页反而是越“手写”越显诚意。你想想如果打开之后控制台明晃晃挂着一个 Vue 或者 React 的标记对方多少会觉得你这是套了个现成模板。用原生代码写的动画效果每个像素都是自己算出来的在心理层面上的加分是完全不一样的。1.2 动画效果的设计思路我给自己定了三个核心要求你们做的时候也建议照这个思路走不做花里胡哨的复杂交互打开页面以后自动播放全程零操作不让对方有一丝不知所措的机会。至少有三种以上不同类型的动画效果要有星空背景、粒子爱心、打字机文字视觉层次一定要丰富。全部内容控制在 10 秒以内进入高潮因为现代人的耐心是有限的前面 3 秒抓不住注意力后面做得再好也白搭。这个设计思路本质上是在模拟“一个故事的开篇”先把氛围感拉满再慢慢展示内容。星空背景负责让人停留粒子爱心负责让情绪升温最后打字机文字把想说的话说出来三层递进比打开就是一堆文字要有感染力得多。2. 核心细节解析与实操要点2.1 页面结构与布局把彩蛋藏好文件结构是我特别花心思的地方因为你要考虑到“惊喜感”。不能一打开就明晃晃地写着“我喜欢你”那样太直白少了点意思。所以我做了个点击触发隐藏内容的设计。刚打开页面时背景是深色星空屏幕中央只有一个小巧的爱心图标在呼吸跳动旁边一行小字提示“点击这里”。等对方真的点下去爱心爆炸成粒子重新聚集形成一个大大的爱心形状标题文字和打字机内容随之浮现。整个过程流畅自然惊喜感拉满。这种设计逻辑其实和游戏里的“渐进式引导”很像用户永远不需要思考下一步该干什么但每一步都有新的东西冒出来。你在自己设计页面的时候也可以参考不要让用户一开始就看到全部而是制造一层薄薄的悬念让点开这个动作本身变成一种参与。2.2 关键动画原理粒子系统基础粒子爱心听起来高大上其实核心就一句话在 Canvas 上撒点然后让这些点动起来。爱心形状的数学方程用的是极坐标形式这个方程网上有现成的但它非常关键我建议直接记住先根据爱心的参数方程计算出若干个目标坐标点粒子从屏幕随机位置出发通过缓动动画逐渐移动到自己对应的目标点移动过程中根据距离动态调整粒子透明度产生聚集感这一套思路用到的数学其实很简单基本就是三角函数和插值计算比想象中容易得多。真正复杂的是粒子的初始状态怎么撒。我测试下来效果最好的方案是所有粒子从屏幕正中间的位置向四周爆开形成星散状扩散然后再收拢聚合成爱心。过程大概 2 秒左右视觉冲击力最强。2.3 打字机效果与星空背景的实现技巧打字机效果真写起来也不难核心就一个思路逐字截取字符串。每过固定毫秒数就往 DOM 里多塞一个字再配一个闪烁的光标模拟打字手感。这里有个细节容易忽略文字的换行和居中。如果直接操作 innerHTML 改文字内容你会发现段落在屏幕上顶来顶去特别不美观。正确做法是先根据屏宽预计算好每行能放几个字手动在合适位置插入换行然后整体居中显示。字符宽度会因为字体类型和字号不同而略有差异稳妥做法是逐个字符用 Canvas 的measureText方法测量实际宽度来切分。星空背景也有讲究。要分清“星星不动”和“星星闪烁”两种动画的叠加方式基础星空用 CSS 定位在绝对位置每颗星星大小随机闪烁效果则通过 CSS 的keyframes动画控制不同星星的透明度变化每次动画时间不同形成层次感。3. 完整代码与实操过程的实现3.1 页面骨架与静态样式这一节直接给代码都是可以直接复制去用的我这里把完整版拆成三部分HTML 骨架、CSS 样式、Canvas 动画逻辑。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title❤ 写给你的话/title style * { margin: 0; padding: 0; box-sizing: border-box; } body { overflow: hidden; background: #0a0a1f; height: 100vh; display: flex; align-items: center; justify-content: center; font-family: Microsoft YaHei, PingFang SC, sans-serif; } #bg-canvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; } .click-tip { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 2; color: #ff6b9d; font-size: 18px; letter-spacing: 2px; cursor: pointer; animation: breathe 2s ease-in-out infinite; user-select: none; } keyframes breathe { 0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.8; } 50% { transform: translate(-50%, -50%) scale(1.15); opacity: 1; } } .main-content { position: relative; z-index: 3; display: none; text-align: center; width: 90%; max-width: 700px; padding: 40px 20px; } .main-content.show { display: block; animation: fadeInUp 1s ease-out forwards; } keyframes fadeInUp { 0% { opacity: 0; transform: translateY(40px); } 100% { opacity: 1; transform: translateY(0); } } .main-content h1 { color: #fff; font-size: 32px; font-weight: 600; margin-bottom: 20px; text-shadow: 0 0 20px rgba(255, 107, 157, 0.6); } .typewriter { color: #d1d1e8; font-size: 16px; line-height: 2; min-height: 120px; text-align: left; display: inline-block; margin: 0 auto; } .typewriter::after { content: |; color: #ff6b9d; animation: blink 0.8s step-end infinite; } keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } /style /head body canvas idbg-canvas/canvas div classclick-tip idclickTip❤ 轻轻点这里/div div classmain-content idmainContent h1 idmainTitle/h1 p classtypewriter idtypewriter/p /div script src./love.js/script /body /html3.2 Canvas粒子爱心逻辑这部分是整页面的灵魂。我建议不要用现成的库直接手写因为粒子数量不多性能完全没压力手写代码反而更能控制特效的感觉。// love.js (function () { const canvas document.getElementById(bg-canvas); const ctx canvas.getContext(2d); let width, height, particles []; const PARTICLE_COUNT 500; // 爱心参数方程 function heartPosition(t) { 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 * 12, y: y * 12 }; } // 生成目标点位 const targets []; for (let i 0; i PARTICLE_COUNT; i) { const t (i / PARTICLE_COUNT) * 2 * Math.PI; const pos heartPosition(t); const jitter () (Math.random() - 0.5) * 8; targets.push({ x: pos.x jitter(), y: pos.y jitter() }); } // 初始化粒子 function initParticles() { particles targets.map(function (target) { return { x: width / 2 (Math.random() - 0.5) * 60, y: height / 2 (Math.random() - 0.5) * 60, targetX: target.x, targetY: target.y, size: Math.random() * 2.5 1, alpha: 1, color: Math.random() 0.3 ? #ff6b9d : #ffa0c5 }; }); } function resize() { width window.innerWidth; height window.innerHeight; canvas.width width; canvas.height height; initParticles(); } // 动画主循环 function draw(time) { ctx.clearRect(0, 0, width, height); // 星空背景 for (let i 0; i 180; i) { const sx (i * 137) % width; const sy (i * 241) % height; const twinkle 0.5 0.5 * Math.sin(time / 1000 i); ctx.globalAlpha Math.max(0.2, twinkle); ctx.fillStyle #ffffff; ctx.beginPath(); ctx.arc(sx, sy, 1 (i % 2), 0, Math.PI * 2); ctx.fill(); } ctx.globalAlpha 1; // 粒子向爱心目标聚拢 particles.forEach(function (p) { const ease 0.06; p.x (p.targetX - p.x) * ease; p.y (p.targetY - p.y) * ease; const dist Math.abs(p.x - p.targetX) Math.abs(p.y - p.targetY); if (dist 10) { p.alpha 1; } else { p.alpha Math.max(0.3, 1 - dist / 150); } ctx.globalAlpha p.alpha; ctx.beginPath(); ctx.fillStyle p.color; ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill(); }); ctx.globalAlpha 1; requestAnimationFrame(draw); } // 点击后动画 document.getElementById(clickTip).addEventListener(click, function () { this.style.opacity 0; this.style.pointerEvents none; setTimeout(function () { const content document.getElementById(mainContent); content.classList.add(show); typeWriter(); }, 600); }); const TITLES [遇见你是我写过最浪漫的代码]; const TYPED_TEXT [ 你知道吗从认识你的那天起, 我的人生就好像按照需求文档运行的程序, 原本按部就班的逻辑突然多了一条异常分支。, 这条分支的名字叫心动。, 如果人生是一段代码, 我希望余生所有的时间复杂度都是O(1), 因为我想把每一次访问的时间, 都直接定格在你身上。, 这是我的心动函数输入是你输出是永远。 ]; function typeWriter() { const titleEl document.getElementById(mainTitle); let titleIndex 0; const titleTimer setInterval(function () { titleEl.textContent TITLES[0].slice(0, titleIndex 1); titleIndex; if (titleIndex TITLES[0].length) { clearInterval(titleTimer); startBody(); } }, 60); } function startBody() { const el document.getElementById(typewriter); let line 0, charIndex 0; el.textContent ; function typeLine() { if (line TYPED_TEXT.length) return; const current TYPED_TEXT[line]; if (charIndex current.length) { el.textContent current.slice(0, charIndex 1); charIndex; setTimeout(typeLine, 80); } else { el.textContent \n; line; charIndex 0; setTimeout(typeLine, 250); } } typeLine(); } window.addEventListener(resize, resize); resize(); requestAnimationFrame(draw); })();这里有两个我调了很久才满意的细节说一下第一个是粒子的分散与收拢路径。初始状态下粒子集中在屏幕正中间随机偏移量 60 像素点击后粒子又要运动到爱心位置。如果直接做线性移动视觉效果会特别呆板。所以我在代码里用了一个缓动系数ease 0.06让粒子先快后慢地逼近目标看起来是“被吸过去”的而不是“飘过去”的。第二个是透明度随距离变化。粒子离目标越远的时候越透明形成一种远近层次感否则所有粒子一个透明度画面会显得特别平。3.3 效果预览与参数微调建议上面这套参数已经是我调过好几轮的版本你在实际使用中可以按需调整参数默认值调整方向效果影响PARTICLE_COUNT500调大到 800爱心更密集但低端手机可能掉帧ease 缓动系数0.06调大到 0.12粒子收拢更快动感更强TYPED_TEXT 间隔80ms调大到 120ms打字速度变慢更有呼吸感粒子颜色#ff6b9d换成其他色系整体情绪氛围完全不同我实测过 iPhone 8 和几年前的安卓千元机500 粒子的情况下都能跑满 60 帧完全不需要担心性能问题。如果你要加更多粒子记得用requestAnimationFrame里的时间参数做降频处理而不是裸跑循环。4. 部署与分享从代码到惊喜4.1 用 GitHub Pages 免费上线写好的 HTML 文件留在本地是没法直接给到对方手机上的所以你需要把它部署到外网。这里我最推荐的方式是 GitHub Pages免费、稳定、不用备案。操作很简单在 GitHub 新建一个仓库名字随意权限设为 Public。把index.html和love.js传上去。进入仓库 Settings → Pages在 Source 处选择main分支保存。稍等一分钟访问https://你的用户名.github.io/仓库名/就能看到了。注意如果页面访问后是白屏多半是love.js的路径写错了。我建议用相对路径./love.js不要写绝对路径/love.js否则部署在项目子路径下时会 404。如果你没有 GitHub 或者觉得太麻烦还可以用 Netlify Drop。直接把包含index.html和love.js的文件夹拖到页面上几秒钟就能得到一个 HTTS 链接同样免费。4.2 几个被忽视的细节部署完成以后别急着发建议先用手机浏览器自己完整走一遍流程重点检查以下几点页面标题是否修改了微信里转发卡片显示的第一行就是它我建议就写“写给你的话”别暴露真实意图。声音网页本身没有背景音乐我觉得反而好。如果加了音乐对方没插耳机时外放出来会很社死。真要加做成一个明显的“开启音乐”按钮让对方自己决定。分享文案发给对方的时候别发“快看我做的网页”这种话太直白。你可以说“帮我看看这个网页是不是有 bug”或者就发一句“刚才看到一个有意思的东西你也点开看看”这样更自然。5. 常见设置与避坑指南5.1 真机上的常见问题问题原因解决方法页面白屏JS 报错或资源路径错误电脑上先打开本地文件F12 看 Console 报错点击后没有反应事件绑定失败多半是 JS 加载晚于 HTML把script标签放在/body底部手机上爱心偏移没有按视口尺寸重新计算确保resize事件里重新设置 Canvas 宽高字体太难看系统默认字体不适合这种场景加一个font-family的 fallback别只写一种字体打开太慢引用了外部 CDN 资源所有 JS/CSS 全部内联或本地化一个文件搞定这里最值得强调的还是最后一条。很多现成模板喜欢引一堆 CDN什么 animate.css、jQuery 之类的放到线上后首屏可能要 2 到 3 秒才能响应用户点击。在表白这个场景下前 3 秒就是生死线能不用外部资源就尽量不用。还有一个小技巧提前在微信里给自己发一遍。微信内置浏览器对 Canvas 和 CSS 动画的兼容性和普通浏览器有细微差别你在电脑上看着完全 OK在微信里可能会有字体渲染、Canvas 尺寸适配的问题。提前自己点一遍总比到时候翻车强。5.2 文案定制的经验网页是壳文字才是灵魂。我建议你先自己把想说的话写在纸上然后逐句精简每一行都控制在 20 个字以内。不要贪多五六行就够了。语言风格可以参考“程序员的浪漫”但别太硬核别让对方看不懂。比如你可以用这样一句如果人生是一段代码那我希望余生所有的时间复杂度都是 O(1)因为我想把每一次访问的时间都直接定格在你身上。这句话很多圈子外的人也能懂而且显得很特别。但如果写“我使用递归函数遍历你的五官”很多人会觉得莫名其妙效果反而不好。我在实际使用中发现分段比长句更有张力。上面代码里我把整段文案拆成了 9 行每行 8 到 15 个字。打字机效果一行一行地打出来的时候对方会跟着一行一行地看情绪是可以被一层层带起来的。如果你一口气塞一大段话反而没有那个酝酿的过程。6. 最后再分享一点小经验这几个小时做下来我最大的体会是这种页面真正打动人的地方不在特效有多炫而在于那种“我专门为你写了一段代码”的笨拙真诚感。技术反而不是重点重点是通过这些像素和文字把你的心意传递出去。代码里的粒子颜色、文案内容、标题文字建议都调成你们之间的小默契。如果你会写点简单的数学公式或者能加点只有你们俩懂的梗那就更好了这是任何现成模板都给不了的。最后再补充一句部署完以后发之前的那个晚上记得自己打开页面从头到尾看一眼。当看到满屏粒子汇聚成爱心的时候大概率你自己也会被这种简单的东西打动一次。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价