新闻详情

新闻详情

首页 / 资讯中心 / 详情

HTML5 Canvas樱花树特效:递归分形与粒子系统实战

发布时间:2026/10/1 1:45:14来源:尧图网络
HTML5 Canvas樱花树特效:递归分形与粒子系统实战
简介这是一份基于HTML5 Canvas的樱花树木动态特效源码面向前端初学者与网页视觉特效爱好者帮助理解Canvas图形绘制与粒子动画的实现思路。压缩包共6个文件约3KB包含html页面、js脚本、css样式、使用帮助txt及两个url链接分别承担页面结构、动画逻辑、样式控制与使用指引。代码完整演示了树干与树枝的路径绘制、花瓣的arc填充、飘落粒子系统、requestAnimationFrame动画循环以及鼠标交互与性能优化等关键环节读者可据此掌握随机分支生成、粒子重置与clearRect局部重绘等技巧。目前已有5436人学习下载适合作为Canvas入门练手与网页浪漫背景特效的参考案例。1. 从一张静态页面到满屏落樱HTML5 Canvas 樱花树木特效到底在做什么很多人第一次看到「HTML5 Canvas 樱花树木特效代码」这类需求脑子里浮现的是某个现成的 JS 插件复制一段 script 标签就能跑。实际做过就知道真正让页面「活」起来的不是某个库而是 Canvas 2D 的绘制循环加上一套自己写的粒子系统。樱花树是静态骨架飘落的花瓣是动态粒子两者叠在同一张画布上靠 requestAnimationFrame 每帧重绘。这套东西能解决的需求很具体给个人主页、节日活动页、产品落地页加一层不依赖视频、不依赖 GIF 的轻量氛围动效文件体积小、可调参数多、移动端也能跑。适合谁会一点 JavaScript、能看懂 canvas 绘图 API、想自己掌控动画节奏而不是被插件绑架的前端。下面从零把这棵树和这场花瓣雨拆开讲清楚。2. 樱花树怎么画Canvas 2D 递归分形与花瓣粒子的建模思路2.1 为什么用递归分形画树而不是贴一张 PNG最省事的做法当然是让设计师出一张樱花树 PNG往 canvas 上一 drawImage 完事。但这样做的代价是树的大小固定、缩放会糊、想改枝干角度得重新出图而且和飘落花瓣的层次关系很难做自然。用递归分形fractal tree画树本质是把一根枝干当成一条线段从起点按某个角度分叉出两根更短、更细的子枝干递归到深度耗尽为止。这样树是「算」出来的不是「贴」出来的改一个角度参数整棵树的形态就变了缩放不失真还能让花瓣从真实的枝干末端位置生成而不是随机撒在屏幕上方。递归分形的核心参数只有四个初始枝干长度、每层长度衰减系数、分叉角度、递归深度。深度每加一层枝干数量翻倍深度 10 就是 2 的 10 次方量级的分支所以深度一般控制在 9 到 12 之间再多浏览器单帧计算就吃力了。角度决定树的「张开程度」0.4 到 0.6 弧度之间比较像真实的樱花树太小像扫把太大像爆炸。// 递归绘制枝干x,y 起点len 长度angle 角度depth 剩余深度 function drawBranch(ctx, x, y, len, angle, depth) { if (depth 0) return; // 计算枝干终点坐标 const x2 x Math.cos(angle) * len; const y2 y Math.sin(angle) * len; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x2, y2); // 枝干越细越浅模拟木质渐变 ctx.lineWidth depth * 0.6; ctx.strokeStyle rgba(90, 60, 50, ${0.3 depth * 0.06}); ctx.stroke(); // 递归出左右两根子枝干长度衰减 0.72角度各偏 0.5 弧度 drawBranch(ctx, x2, y2, len * 0.72, angle - 0.5, depth - 1); drawBranch(ctx, x2, y2, len * 0.72, angle 0.5, depth - 1); }这段代码里len * 0.72是长度衰减0.72 意味着每往上一层枝干缩短 28%这个值低于 0.6 树会显得矮胖高于 0.8 会显得瘦长。angle ± 0.5是分叉角左右对称。lineWidth depth * 0.6让靠近根部的枝干粗、末梢细视觉上更像真树。strokeStyle的 alpha 随深度增加末梢更淡避免整棵树糊成一团黑。递归的终止条件是depth 0也就是画到第 10 层左右就停。2.2 花瓣粒子系统位置、速度、旋转、透明度的四要素树画完了是静态的樱花特效的灵魂在飘落的花瓣。花瓣不用真的画一朵五瓣花用一个小椭圆或者一段二次贝塞尔曲线就够因为它在屏幕上通常只有几像素到十几像素细节看不出来。每片花瓣是一个对象至少要有这几个属性x、y 当前位置vx、vy 速度分量rotation 当前旋转角vr 旋转角速度size 大小alpha 透明度。每帧更新时 x vxy vyrotation vr当 y 超出画布高度就重置回顶部形成循环。花瓣的运动不能是纯匀速直线下落那样像下雨不像花瓣。真实花瓣下落时左右摇摆所以给 vx 加一个基于正弦的扰动让水平方向来回飘。透明度也可以随下落位置变化靠近底部稍微淡出避免花瓣在底部堆积得太突兀。// 初始化 80 片花瓣 const petals []; for (let i 0; i 80; i) { petals.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, vy: 0.5 Math.random() * 1.5, // 下落速度 0.5~2 vx: -0.5 Math.random(), // 水平初速度 rotation: Math.random() * Math.PI * 2, vr: -0.02 Math.random() * 0.04, // 旋转角速度 size: 4 Math.random() * 6, // 花瓣尺寸 4~10 alpha: 0.5 Math.random() * 0.5, phase: Math.random() * Math.PI * 2 // 摇摆相位让每片花瓣不同步 }); }vy范围 0.5 到 2 是下落速度太快像冰雹太慢像悬浮。vx给一个 -0.5 到 0.5 的初值配合后面的正弦扰动。phase是关键如果所有花瓣用同一个正弦函数它们会整齐划一地左右摆看起来像机械波加了随机相位后每片花瓣的摇摆节奏不同才有自然感。size和alpha的随机范围让花瓣有远近层次大而实的看起来近小而淡的看起来远。2.3 主循环requestAnimationFrame 与每帧清屏的配合Canvas 动画的标准做法是每帧先 clearRect 清空整张画布再重新画树和所有花瓣。不清屏的话上一帧的痕迹会残留花瓣会拖出一条条尾巴。清屏用ctx.clearRect(0, 0, canvas.width, canvas.height)不要用 fillRect 填白色因为页面背景可能是渐变色填白会盖住背景。主循环用 requestAnimationFrame 而不是 setInterval原因是 rAF 会跟着浏览器刷新率走60Hz 屏幕就是 60 帧120Hz 就是 120 帧而且页面切到后台时会自动暂停省电。setInterval 不管页面可不可见都按固定间隔跑容易掉帧和浪费性能。function loop() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 先画树背景层 drawBranch(ctx, canvas.width / 2, canvas.height, 120, -Math.PI / 2, 10); // 再画花瓣前景层 petals.forEach(p { p.phase 0.02; p.x p.vx Math.sin(p.phase) * 0.8; // 正弦摇摆 p.y p.vy; p.rotation p.vr; // 超出底部就回到顶部随机位置 if (p.y canvas.height 20) { p.y -20; p.x Math.random() * canvas.width; } ctx.save(); ctx.translate(p.x, p.y); ctx.rotate(p.rotation); ctx.globalAlpha p.alpha; ctx.fillStyle #ffb7c5; ctx.beginPath(); ctx.ellipse(0, 0, p.size, p.size * 0.6, 0, 0, Math.PI * 2); ctx.fill(); ctx.restore(); }); requestAnimationFrame(loop); }Math.sin(p.phase) * 0.8是水平摇摆0.8 是摇摆幅度调大花瓣飘得更野调小更含蓄。ctx.save()和ctx.restore()成对出现保证 translate 和 rotate 不会污染下一片花瓣的坐标系这是 Canvas 里最容易翻车的地方之一忘了 restore 会导致后面所有绘制都带着上一片的位移和旋转。ellipse画椭圆花瓣半径 x 是 sizey 是 size 的 0.6 倍压扁一点更像花瓣形状。3. 让特效跑得稳画布适配、性能控制与移动端兼容3.1 高分屏适配devicePixelRatio 与画布尺寸的两倍关系在 Retina 屏或者手机上直接设 canvas 的 width/height 属性等于 CSS 尺寸画出来的东西会糊因为 canvas 的绘图缓冲区默认是 1 倍像素而屏幕物理像素是 2 倍甚至 3 倍。解决办法是把 canvas 的 width/height 属性设成 CSS 尺寸乘以 devicePixelRatio然后用 ctx.scale 把绘图坐标系缩回来。function resizeCanvas() { const dpr window.devicePixelRatio || 1; const rect canvas.getBoundingClientRect(); // 绘图缓冲区按物理像素设置 canvas.width rect.width * dpr; canvas.height rect.height * dpr; // 坐标系缩放回 CSS 像素这样后续代码不用改 ctx.setTransform(dpr, 0, 0, dpr, 0, 0); } window.addEventListener(resize, resizeCanvas); resizeCanvas();setTransform(dpr, 0, 0, dpr, 0, 0)等价于 scale(dpr, dpr)但用 setTransform 会重置之前的变换矩阵避免多次 resize 后缩放叠加。注意 resize 之后要重新计算花瓣的 x 范围否则窗口变大后花瓣只集中在左侧。这个坑我在一个活动页上踩过用户把浏览器从半屏拖到全屏花瓣全挤在左边三分之一排查了半天才发现是 resize 没重置粒子位置。3.2 花瓣数量与帧率80 片和 300 片的性能分水岭花瓣数量直接决定每帧的绘制调用次数。每片花瓣一次 save、一次 translate、一次 rotate、一次 ellipse、一次 fill、一次 restore80 片就是 480 次调用桌面浏览器轻松跑满 60 帧。但到 300 片就是 1800 次调用中低端手机上会掉到 30 帧以下肉眼能看出卡顿。我的经验值是桌面端 120 到 150 片移动端 50 到 80 片用navigator.userAgent或者更靠谱的window.matchMedia((pointer: coarse))判断触屏设备来动态调整。另外花瓣的 size 越大fill 的像素面积越大GPU 填充压力也越大所以移动端除了减数量还可以把 size 上限从 10 降到 7。设备类型花瓣数量size 范围目标帧率桌面端120~1504~1060fps移动端50~803~730~60fps低端设备30~503~630fps如果还想再压榨性能可以把花瓣预渲染到一张离屏 canvas 上主循环里只做 drawImage省掉每帧的路径构建。但离屏 canvas 会失去每片花瓣独立旋转的灵活性除非预渲染多个旋转角度的贴图复杂度上去了一般项目没必要。3.3 页面切后台与内存rAF 自动暂停之外还要做什么requestAnimationFrame 在页面切到后台时会自动暂停这是好事但有个细节切回来时p.phase这类累加变量不会跳变因为 rAF 不执行就不累加所以动画会从暂停的地方继续不会出现花瓣瞬移。真正要注意的是 resize 事件和 visibilitychange 事件的处理顺序以及长时间运行后 petals 数组有没有泄漏。如果页面里有多个 canvas 或者频繁创建销毁记得在组件卸载时 cancelAnimationFrame把 rAF 的 id 存下来。我见过一个单页应用路由切换时旧页面的 rAF 没取消新页面又起了一个两个循环同时跑内存和 CPU 都翻倍用户反馈「越用越卡」就是这个原因。let rafId null; function start() { if (rafId) return; // 防止重复启动 rafId requestAnimationFrame(loop); } function stop() { if (rafId) { cancelAnimationFrame(rafId); rafId null; } } // 页面隐藏时停显示时恢复 document.addEventListener(visibilitychange, () { document.hidden ? stop() : start(); });if (rafId) return这个守卫很关键防止多次调用 start 导致多个循环并行。visibilitychange 里手动 stop/start 其实 rAF 自己也会暂停但显式管理能保证状态干净尤其是配合上面的守卫使用时。4. 避坑与排查樱花特效最常见的 5 个翻车现场4.1 花瓣拖影忘了 clearRect 或者清屏区域不对现象是花瓣后面拖着一条半透明的尾巴像彗星。原因通常是主循环里没调 clearRect或者 clearRect 的宽高用了 CSS 尺寸而不是 canvas 的绘图缓冲区尺寸。在做了 devicePixelRatio 适配之后canvas.width 是物理像素如果 clearRect 传的是 CSS 尺寸右下角会有一块区域清不干净花瓣在那里越积越多。解决方法是 clearRect 统一用canvas.width和canvas.height因为 setTransform 已经把坐标系缩放到 CSS 像素了clearRect 传物理像素值会被自动缩放反而清多。最稳妥的写法是ctx.clearRect(0, 0, canvas.width, canvas.height)配合 setTransform 使用。4.2 树画出来是歪的或者倒的角度单位搞混现象是枝干朝下长或者朝右长不是朝上。原因是 Canvas 的 y 轴向下为正角度 0 是朝右-Math.PI / 2才是朝上。很多人从数学坐标系过来习惯性写Math.PI / 2以为朝上结果树倒着长。另外分叉角的正负也要注意angle - 0.5和angle 0.5一个偏左一个偏右如果两个都写加号树会一边倒。解决方法是画之前先在纸上标一下起点在底部中央角度 -PI/2 朝上左分叉减角度右分叉加角度。4.3 移动端花瓣卡顿数量没降、size 没减、还开了阴影现象是桌面流畅手机上一顿一顿。除了花瓣数量还有一个隐藏杀手是ctx.shadowBlur。有人为了让花瓣有发光效果加了阴影桌面看不出来移动端 GPU 直接跪。阴影在 Canvas 里是逐像素计算的开销极大。解决方法是移动端彻底去掉 shadowBlur用半透明的叠加椭圆模拟柔光或者干脆不加。另外ctx.globalCompositeOperation lighter也会增加混合开销移动端慎用。4.4 花瓣在底部堆积重置逻辑只判了 y 没判 x现象是花瓣落到屏幕底部后没有回到顶部而是堆在底边。原因是重置条件写成了if (p.y canvas.height)但花瓣的 x 可能已经飘出屏幕左右两侧y 却还没到底于是永远不重置。或者重置时只改了 y 没改 x花瓣全从同一个 x 位置落下。解决方法是重置时同时随机 x并且判断条件加上左右边界if (p.y canvas.height 20 || p.x -50 || p.x canvas.width 50)超出任意一边都回收。4.5 页面缩放后画布模糊只监听了 resize 没重设 dpr现象是用户按 Ctrl加号放大页面或者把窗口拖到另一块不同缩放比的显示器上canvas 变糊。原因是 devicePixelRatio 变了但 resize 事件里没重新读取。浏览器缩放会触发 resize但 dpr 是动态的必须在每次 resize 时重新读window.devicePixelRatio。解决方法是把 dpr 的读取放在 resizeCanvas 函数内部而不是模块顶层只读一次。另外用window.matchMedia监听 dpr 变化更可靠但兼容性一般resize 里重读已经够用。5. 进阶调参与复用把樱花树做成可配置的动效组件5.1 用配置对象收拢所有魔法数字上面代码里的 0.72、0.5、80、0.8 这些数字散落在各处改一个参数要翻半天。实际项目里我会把它们收进一个 config 对象初始化时合并默认值这样换一个页面只改配置不改逻辑。const defaultConfig { branch: { length: 120, decay: 0.72, angle: 0.5, depth: 10 }, petal: { count: 100, minSize: 4, maxSize: 10, sway: 0.8, speed: [0.5, 2] }, color: { branch: 90,60,50, petal: #ffb7c5 } }; function createSakura(canvas, userConfig) { const cfg { ...defaultConfig, ...userConfig }; // 后续逻辑全部读 cfg不再出现硬编码 }{ ...defaultConfig, ...userConfig }是浅合并如果 userConfig 里只传了petal.count其他 petal 字段会被整个覆盖丢失。生产环境要用深合并或者约定配置必须传完整子对象。我一般用 lodash 的 merge 或者自己写一个两层的递归合并避免这个坑。5.2 花瓣形状从椭圆升级到贝塞尔曲线椭圆花瓣看久了还是有点假想更像樱花可以用二次贝塞尔画一个带缺口的瓣形。樱花花瓣顶端有个小凹口用两段贝塞尔能画出来。function drawPetal(ctx, size) { ctx.beginPath(); ctx.moveTo(0, -size); // 右侧弧线到顶端凹口 ctx.quadraticCurveTo(size * 0.8, -size * 0.3, size * 0.3, size * 0.2); // 凹口 ctx.quadraticCurveTo(0, 0, -size * 0.3, size * 0.2); // 左侧弧线回起点 ctx.quadraticCurveTo(-size * 0.8, -size * 0.3, 0, -size); ctx.fill(); }quadraticCurveTo的控制点决定弧度size * 0.8是控制点横向偏移调大花瓣更圆调小更尖。中间那个quadraticCurveTo(0, 0, ...)就是顶端凹口控制点在原点让曲线往里凹。这个形状比椭圆多两次路径构建性能开销增加约 30%花瓣数量要相应下调 20% 左右。5.3 验证特效是否真的在跑用帧率计数和绘制调用数调完之后怎么确认性能达标我会在开发阶段加一个简易的 FPS 计数器每 60 帧统计一次平均帧间隔显示在角落。另外用performance.now()包住主循环看单帧耗时有没有超过 16.7ms60 帧的预算。如果单帧超过 16.7ms说明这一帧的绘制量已经撑不住 60 帧了要么减花瓣要么减深度。let lastTime performance.now(); let frames 0; let fps 0; function loop(now) { frames; if (now - lastTime 1000) { fps frames; frames 0; lastTime now; console.log(FPS:, fps); } // ... 绘制逻辑 requestAnimationFrame(loop); }这个计数器只在开发时开上线前注释掉因为 console.log 本身也有开销。我习惯在 config 里加一个debug: false开关控制是否输出 FPS 和绘制调用数上线时关掉。最后说个我自己的习惯每次做完这类 Canvas 动效我都会在低端安卓机上真机跑一遍不是模拟器。模拟器的 GPU 和真机差太多模拟器 60 帧真机可能 25 帧。花瓣数量、size、阴影这三个参数是性能的命门调完必须真机验证。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

通达信TdxHqApi.dll实时行情采集器的调用链拆解与工程实践 2026/10/1 7:25:22

通达信TdxHqApi.dll实时行情采集器的调用链拆解与工程实践

简介:一套基于TdxHqApi.dll的实时股票数据采集器完整项目源码,面向量化交易开发者、行情数据研究人员及C#/Java混合技术栈学习者,解决从通达信接口获取实时行情与交易数据时的封装、解析和工程集成难题。压缩包共248个文件、约105.88MB&#…

阅读更多 →
小家电复位电路从RC到专用长按复位IC的选型与设计 2026/10/1 7:25:09

小家电复位电路从RC到专用长按复位IC的选型与设计

小家电的复位电路,这两年正在经历一轮静悄悄的替换。如果你拆过最近一两年的养生壶、电动牙刷、便携榨汁杯或者桌面加湿器,会发现板子上原本该有的RC延时网络不见了,取而代之的是一颗SOT-23-6或者更小封装的长按复位IC。这个变化不是某个方案…

阅读更多 →
Win7版Steam提示内容不可用?补libzstd.dll修复Zstd 2026/10/1 7:25:09

Win7版Steam提示内容不可用?补libzstd.dll修复Zstd

如果你手里还有一台Win7或者8.1的老机器,并且坚持拿它跑Steam,最近多半撞上过一个让人血压升高的场面:游戏库列表正常,商店页面也能刷开,但只要点下载,进度条转两下就停住,然后弹出一个"内…

阅读更多 →
把HIL测试接进CI:自动化回归流水线搭建实录 2026/10/1 7:25:09

把HIL测试接进CI:自动化回归流水线搭建实录

宏控天工做嵌入式控制器开发,软件几乎每天都在改。每次改完都要人去手动跑一遍 HIL 台架,跑完等结果、记报告、再通知开发——这套流程在小团队还能转,到了量产阶段根本跟不上迭代速度。解决办法就是把 HIL 测试接进 CI(持续集成&…

阅读更多 →
工作室手游多开福音!掌派云手机移动端同步操作来了! 2026/10/1 7:25:09

工作室手游多开福音!掌派云手机移动端同步操作来了!

做手游多开的工作室,想必都遇到过一个很现实的难题:过去云手机批量同步管控,只能在电脑客户端操作。一旦人离开工位,外出办事或者下班休息,遇到云机掉线、任务卡死,没办法批量处理,只能等回到电…

阅读更多 →
GAIA工程化Agent评测:六层能力解剖与落地避坑指南 2026/10/1 7:25:09

GAIA工程化Agent评测:六层能力解剖与落地避坑指南

1. 这不是跑个benchmark那么简单:为什么“工程化Agent评测”正在成为新分水岭最近在几个技术社区刷到“XiheAgent”“GAIA评测”这些词的频率越来越高,尤其看到【实战评测】华为云码道检视修复智能体:召回率91.3%这种标题,我第一反…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉