十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Canvas打造SaaS落地页发光组网节点动效:从概念到完整实现

用Canvas打造SaaS落地页发光组网节点动效:从概念到完整实现 最近在给一套 SaaS 产品做官网落地页产品主题涉及 AI 人工智能和 Web3 网页介绍设计师给的关键词是“发光轮番放入组网节点效果”。说白了就是页面上那些不断点亮、连线的发光节点像真实网络拓扑一样把科技感撑起来。这类效果在 AI、Web3、SaaS 产品页里非常常见但网上的实现资料大多比较零散。本文把从概念拆解、动效方案选型、Canvas 核心代码到整页落地的完整流程整理出来新手可以照着一步步做有基础的同学可以直接跳到核心代码和排错清单复用。1. 项目背景与核心概念1.1 SaaS 产品为什么需要专属落地页SaaS 是 Software as a Service 的缩写意思是软件即服务。用户不需要自己安装、维护软件只要通过浏览器登录就能使用按订阅付费典型产品有各种在线文档、项目管理工具、客户管理系统等。SaaS 产品的特点是“先体验、后购买”所以官网落地页承担的任务非常重访客可能只花几秒钟判断产品是否专业、是否值得继续了解。在做 SaaS 官网时很多人会把注意力全放在功能列表和价格页上却忽略了首页第一屏的视觉表达。实际上第一屏决定了用户是否继续往下滑。对于 AI、Web3 这类偏技术向的产品如果只是放一张静态截图加几句介绍很难让访客快速感知到产品的“连接能力”和“智能化”。这时候动效和视觉包装的作用就体现出来了。顺带提一下很多同学会混淆云服务里的几个概念面试和方案汇报里经常被问到。它们的核心区别是“用户自己管理什么、厂商管理什么”服务模式中文名称用户通常管理典型示例IaaS基础设施即服务操作系统、应用、数据云服务器PaaS平台即服务应用、数据云数据库、容器平台SaaS软件即服务只使用软件不关心底层在线协作工具DaaS数据即服务按需获取数据处理能力数据接口、BI 服务理解这些区别有助于在落地页里准确描述产品定位。很多 SaaS 产品实际上会同时涉及 IaaS 或 PaaS 的能力页面文案如果写错了懂行的用户一眼就能看出来反而影响信任感。1.2 AI、Web3 与“组网节点”视觉语言为什么 AI、Web3 主题的落地页特别喜欢用节点连线图这里面其实有很朴素的设计逻辑。AI 产品强调“智能背后的数据处理”Web3 强调“分布式、去中心化、多节点协作”SaaS 平台强调“把不同业务系统连接起来”。这三种主题抽象成视觉符号之后都会收敛到同一个图形中心节点连接周围节点节点之间有数据流动。一颗颗发光的小圆点代表设备、服务器、AI 模型、区块链节点或者业务系统连接它们的线条代表数据流、协议通道或者信任关系。“发光轮番放入组网节点效果”可以拆成两件事第一是“组网”也就是节点之间通过连线形成网络结构第二是“轮番发光”也就是节点按一定节奏依次点亮让视觉上形成数据轮询、信号扫描的感觉。这种效果非常适合放在 SaaS 产品页的 Hero 区首屏大图区域作为背景或者放在“产品架构”章节展示平台与各系统的连接关系。1.3 AE 模板与 Web 动效的关系项目标题里提到了 AE 模板这里先理清 AE 和 Web 落地页的关系避免一开始就走偏。AE 是 Adobe After Effects 的缩写是一款专业的动态图形和视频合成软件。AE 模板通常是一套做好的工程文件设计师只要替换文字、Logo、颜色就能快速生成一段片头或宣传动画。在落地页项目里AE 模板主要用于两类场景制作官网首屏的背景视频或产品宣传片头导出为 MP4 或 WebM 后用video标签嵌入页面。制作图标动画、Logo 动效等轻量素材通过 Bodymovin 插件导出为 Lottie JSON 格式再用前端库lottie-web渲染。AE 做出来的动画质量高但体积大、交互能力弱Web 前端直接用 Canvas 写的动画体积小、可控性强非常适合做交互式的背景节点效果。实际项目里我通常建议“AE 做品牌片头Canvas 做页面实时动效”两者搭配使用。本文的核心动效就是用原生 Canvas 实现的不需要引入任何第三方库。2. 环境准备与技术选型2.1 开发环境本文示例不依赖任何前端框架全部使用 HTML、CSS、原生 JavaScript因此环境准备非常简单工具说明操作系统Windows 10/11、macOS、Linux 均可浏览器Chrome 或 Edge 最新版编辑器VS Code推荐安装 Live Server 插件本地服务器Live Server 插件或 Python 3 自带 http.server设计辅助Adobe After Effects可选用于制作宣传片头版本方面不需要写死现代浏览器对 Canvas 和 ES6 语法的支持已经很稳定。如果你使用 Python 3本地预览时在项目目录执行下面命令即可python3 -m http.server 8080然后浏览器访问http://localhost:8080。如果是 Windows 且只安装了 Python命令可能是python -m http.server 8080。2.2 动效技术方案对比做节点类动效先要选对技术方案。不同方案的制作成本、性能和交互能力差别很大我整理了一张对比表方案制作成本运行性能交互能力适用场景AE 导出视频较高中等低片头、宣传视频LottieBodymovin中等较高中图标、局部交互动画Canvas 2D 手写较低高高粒子背景、网络拓扑WebGL / Three.js高中高高3D 节点场景纯 CSS 动画低高低简单发光、旋转装饰做“组网节点”这种大范围背景动效Canvas 2D 是目前性价比最高的选择。它不需要额外的运行时库动画逻辑完全由代码控制节点数量、运动速度、连线距离都可以动态调节。如果未来想做 3D 版本的节点球体效果再升级到 Three.js 也不迟。2.3 设计规范与色彩科技感落地页一般使用深色背景加高亮光效。本文示例采用以下色彩规范页面背景#060a14接近深空蓝黑衬托发光元素。主光效色#00e5ff青色发光代表数据、AI、科技感。辅助色#7c5cff紫色用于渐变和次级强调。正文文字#e8f1ff接近白色保证深色背景下的可读性。次要文字#8fa3bf浅灰蓝用于说明文字。字体使用系统字体栈不需要加载外部字体避免网络请求拖慢首屏。示例中的中文用PingFang SC和Microsoft YaHei英文和数字用系统默认的无衬线字体即可。3. 核心效果拆解发光轮番组网节点3.1 效果拆解在写代码之前先把这个视觉效果拆成四层每一层对应一组逻辑背景层深色渐变背景突出前景发光元素。节点层在画布中随机生成若干粒子每个粒子代表一个网络节点带有自己的坐标、速度和半径。连线层当两个节点之间的距离小于阈值时绘制一条半透明直线距离越近线条越亮模拟网络连通关系。发光层每个节点拥有一个“发光计时器”计时器大于 0 时节点外圈出现青色光晕并随时间衰减从而产生节点依次点亮、轮番发光的视觉效果。这种分层思路很重要。很多同学一上来就想着怎么画出漂亮的图形代码写到一半发现逻辑全混在一起。先拆层再写每一层代码会清晰很多。3.2 创建 Canvas 画布与适配高分屏第一步是在 HTML 中放入画布并让它铺满 Hero 区域。这里有一个高频坑如果不处理devicePixelRatio设备像素比在高分屏上 Canvas 内容会显得模糊。原因是物理像素和 CSS 像素不一致Canvas 默认的绘图缓冲区太小。处理方式是读取window.devicePixelRatio把 Canvas 的width和height乘以该值再通过ctx.setTransform缩放绘图上下文。这样画布内的一切坐标都还是按 CSS 像素计算但渲染清晰度会明显提升。下面先给出这个核心的思路完整代码在第四节统一给出。3.3 节点生成与运动规则每个节点本质上是这样一个对象{ x: 100, // 横坐标 y: 200, // 纵坐标 vx: 0.3, // 水平速度 vy: -0.2, // 垂直速度 radius: 2, // 节点半径 glowTime: 0, // 当前剩余发光帧数 glowMax: 40 // 一次发光的持续帧数 }节点在画布范围内随机生成每帧更新位置。为了不让节点跑出画面使用“碰到边界就反弹”的策略同时把坐标限制在0到width、0到height之间。速度值不需要太大0.3到0.6之间就足够否则画面会显得太躁动。3.4 连线规则与发光绘制连线的核心是“距离判断”。两两遍历所有节点计算欧几里得距离const dx a.x - b.x; const dy a.y - b.y; const dist Math.sqrt(dx * dx dy * dy);当dist小于设定的LINK_DISTANCE时绘制一条线线条透明度与距离成反比形成“近处清晰、远处淡化”的渐变效果。这种两两遍历的复杂度是 O(n²)节点数量在 50 个左右时性能完全没问题但如果把节点数加到 200 以上就要考虑空间索引优化这个问题放到第六节再展开。发光效果使用 Canvas 的shadowBlur属性实现。绘制节点圆点之前设置shadowColor为青色、shadowBlur为发光强度画完后立刻重置为 0避免影响后续绘制。3.5 轮番点亮动画逻辑“轮番点亮”是本文效果最有特点的部分。实现思路是给每个节点一个随机的发光周期初始时节点glowTime随机分配有的刚亮有的已熄灭。每帧如果glowTime 0则减 1。当glowTime等于 0 时以较低概率例如每帧 1.5%重新触发一次发光把glowTime设置为glowMax。发光的亮度不是恒定的而是根据glowTime / glowMax计算过渡比例让节点从亮到暗平滑衰减。用概率触发而不是固定周期的好处是节点的点亮间隔不完全一致视觉上更像真实网络中的随机信号不会出现机械的“排队闪烁”感。如果希望做更规则的扫描效果也可以把概率触发改成定时器按固定时间间隔点亮一圈节点。4. 完整落地页实战4.1 项目结构规划按照可维护性原则项目结构如下saas-ai-web3-landing/ ├── index.html ├── css/ │ └── style.css ├── js/ │ └── network.js └── assets/ └── intro.mp4页面规划为四个区块Hero 首屏Canvas 动效 标题 视频、核心能力、产品架构、页脚。项目虽小但结构清晰后续扩展“客户案例”“FAQ”等区块时只需要继续追加 HTML 和 CSS。4.2 编写页面骨架 index.html!-- 文件路径index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 meta namedescription contentSaaS 智能协作平台官网落地页示例融合 AI 人工智能与 Web3 网页介绍视觉风格使用 Canvas 实现发光组网节点动效。 meta namekeywords contentSaaS, AI, Web3, 落地页, 组网节点, Canvas动画, AE模板 titleSaaS 智能协作平台 - AI Web3 企业组网工作空间/title link relstylesheet hrefcss/style.css /head body !-- Hero 首屏 -- header classhero canvas idnetworkCanvas aria-hiddentrue/canvas div classhero-content span classbadgeAI Web3 新一代协作平台/span h1让企业的每一次连接span classhighlight都发光/span/h1 p classsubtitle 面向中大型团队的 SaaS 协作套件将 AI 能力、Web3 分布式身份与企业组网节点深度打通 让数据流转更安全、更高效。 /p div classhero-actions a href#features classbtn-primary立即体验/a a href#architecture classbtn-ghost查看架构/a /div /div div classhero-mock !-- AE 模板导出的宣传视频放置在产品预览位 -- !-- 实际部署时请把视频压缩到合适体积或改用 Lottie 动画 -- video idintroVideo muted loop playsinline preloadmetadata source srcassets/intro.mp4 typevideo/mp4 当前浏览器不支持 video 标签建议使用 Chrome 或 Edge 访问。 /video /div /header !-- 核心能力 -- section idfeatures classsection features div classcontainer h2核心能力/h2 p classsection-desc从团队协作到生态互联一套平台覆盖高频业务场景。/p div classgrid article classcard h3AI 工作助手/h3 p自动汇总项目进度、生成会议纪要、辅助编写需求文档把重复工作交给 AI。/p /article article classcard h3Web3 身份与权限/h3 p基于分布式身份标识的多方协作权限模型企业密钥由自己掌控数据访问全程可审计。/p /article article classcard h3组网节点可视化/h3 p实时展示各业务节点的连接状态与数据流向异常节点自动告警运维效率提升显著。/p /article /div /div /section !-- 产品架构 -- section idarchitecture classsection architecture div classcontainer h2产品架构/h2 p classsection-descSaaS 平台作为核心节点连接 AI、Web3 身份、数据中台与各业务系统。/p div classarch-diagram aria-label产品架构示意图 div classarch-coreSaaS 平台/div div classarch-node n1AI 服务/div div classarch-node n2Web3 身份/div div classarch-node n3数据中台/div div classarch-node n4业务系统/div /div /div /section !-- 页脚 -- footer classfooter div classcontainer p© 2024 SaaS 智能协作平台示例项目仅用于技术演示。/p /div /footer script srcjs/network.js/script /body /html页面中 Canvas 放在 Hero 内部使用绝对定位铺满整个 Hero 区域。标题和按钮用相对定位放在 Canvas 上层这样“背景动效 前景文字”就能形成完整的首屏视觉。4.3 编写全局样式 style.css/* 文件路径css/style.css */ :root { --bg-dark: #060a14; --bg-card: rgba(13, 22, 39, 0.85); --primary: #00e5ff; --secondary: #7c5cff; --text-main: #e8f1ff; --text-sub: #8fa3bf; --border-color: rgba(0, 229, 255, 0.25); } * { margin: 0; padding: 0; box-sizing: border-box; } html { scroll-behavior: smooth; } body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Microsoft YaHei, sans-serif; color: var(--text-main); background: var(--bg-dark); line-height: 1.7; overflow-x: hidden; } /* Hero 首屏 */ .hero { position: relative; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; background: radial-gradient(circle at 50% 40%, #0d1a2e 0%, var(--bg-dark) 70%); overflow: hidden; padding: 60px 20px; } #networkCanvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; } .hero-content { position: relative; z-index: 2; text-align: center; max-width: 760px; padding: 40px 20px; } .badge { display: inline-block; padding: 6px 16px; border: 1px solid var(--border-color); border-radius: 999px; font-size: 14px; color: var(--primary); background: rgba(0, 229, 255, 0.08); margin-bottom: 24px; } .hero h1 { font-size: clamp(32px, 6vw, 56px); font-weight: 700; line-height: 1.25; margin-bottom: 20px; } .highlight { background: linear-gradient(90deg, var(--primary), var(--secondary)); -webkit-background-clip: text; background-clip: text; color: transparent; } .subtitle { font-size: clamp(16px, 2vw, 20px); color: var(--text-sub); margin-bottom: 36px; } .hero-actions { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; } .btn-primary { display: inline-block; padding: 12px 32px; border-radius: 8px; background: linear-gradient(90deg, var(--primary), var(--secondary)); color: #04121f; font-weight: 600; text-decoration: none; transition: transform 0.2s ease, box-shadow 0.2s ease; } .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0, 229, 255, 0.35); } .btn-ghost { display: inline-block; padding: 12px 32px; border-radius: 8px; border: 1px solid var(--border-color); color: var(--text-main); text-decoration: none; transition: background 0.2s ease; } .btn-ghost:hover { background: rgba(0, 229, 255, 0.1); } .hero-mock { position: relative; z-index: 2; width: min(720px, 90%); margin-top: 40px; border-radius: 16px; border: 1px solid var(--border-color); overflow: hidden; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); } .hero-mock video { display: block; width: 100%; height: auto; } /* 通用区块 */ .section { padding: 90px 20px; } .container { max-width: 1100px; margin: 0 auto; } .section h2 { font-size: clamp(26px, 4vw, 38px); text-align: center; margin-bottom: 12px; } .section-desc { text-align: center; color: var(--text-sub); margin-bottom: 48px; } /* 核心能力卡片 */ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 24px; } .card { background: var(--bg-card); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 16px; padding: 32px 28px; transition: border-color 0.2s ease, transform 0.2s ease; } .card:hover { border-color: var(--border-color); transform: translateY(-4px); } .card h3 { font-size: 20px; margin-bottom: 12px; } .card p { color: var(--text-sub); font-size: 15px; } /* 架构图 */ .architecture { background: linear-gradient(180deg, rgba(13, 22, 39, 0.4), transparent); } .arch-diagram { position: relative; max-width: 720px; height: 360px; margin: 0 auto; } .arch-core, .arch-node { position: absolute; left: 50%; top: 50%; display: flex; align-items: center; justify-content: center; border-radius: 12px; font-size: 14px; font-weight: 600; } .arch-core { width: 140px; height: 140px; transform: translate(-50%, -50%); background: radial-gradient(circle, rgba(0, 229, 255, 0.25), rgba(0, 229, 255, 0.05)); border: 1px solid var(--primary); color: var(--primary); font-size: 18px; box-shadow: 0 0 40px rgba(0, 229, 255, 0.3); } .arch-node { width: 96px; height: 96px; border: 1px solid rgba(124, 92, 255, 0.5); background: rgba(124, 92, 255, 0.1); color: var(--text-main); } .arch-node.n1 { transform: translate(-50%, -50%) translate(250px, -80px); } .arch-node.n2 { transform: translate(-50%, -50%) translate(250px, 80px); } .arch-node.n3 { transform: translate(-50%, -50%) translate(-250px, -80px); } .arch-node.n4 { transform: translate(-50%, -50%) translate(-250px, 80px); } /* 页脚 */ .footer { border-top: 1px solid rgba(255, 255, 255, 0.06); padding: 30px 20px; text-align: center; color: var(--text-sub); font-size: 14px; } /* 响应式 */ media (max-width: 640px) { .arch-diagram { height: 480px; } .arch-node.n1 { transform: translate(-50%, -50%) translate(110px, -80px); } .arch-node.n2 { transform: translate(-50%, -50%) translate(110px, 80px); } .arch-node.n3 { transform: translate(-50%, -50%) translate(-110px, -80px); } .arch-node.n4 { transform: translate(-50%, -50%) translate(-110px, 80px); } }4.4 编写动效脚本 network.js// 文件路径js/network.js (() { const canvas document.getElementById(networkCanvas); if (!canvas) return; const ctx canvas.getContext(2d); const dpr window.devicePixelRatio || 1; let width 0; let height 0; let particles []; // 可调参数 const PARTICLE_COUNT 50; // 节点数量 const LINK_DISTANCE 130; // 连线阈值 const COLOR_RGB 0, 229, 255; // 青色光效 RGB // 根据父容器尺寸设置画布适配高分屏 function resize() { const rect canvas.parentElement.getBoundingClientRect(); width rect.width; height rect.height; canvas.width Math.floor(width * dpr); canvas.height Math.floor(height * dpr); canvas.style.width width px; canvas.style.height height px; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); } // 生成节点 function createParticles() { particles []; for (let i 0; i PARTICLE_COUNT; i) { particles.push({ x: Math.random() * width, y: Math.random() * height, vx: (Math.random() - 0.5) * 0.6, vy: (Math.random() - 0.5) * 0.6, radius: Math.random() * 2 1, glowTime: Math.random() * 60, glowMax: 30 Math.floor(Math.random() * 30) }); } } // 更新位置与发光状态 function update() { for (const p of particles) { p.x p.vx; p.y p.vy; // 边界反弹并做一次钳制防止越界 if (p.x 0 || p.x width) p.vx * -1; if (p.y 0 || p.y height) p.vy * -1; p.x Math.max(0, Math.min(width, p.x)); p.y Math.max(0, Math.min(height, p.y)); // 发光计时逻辑轮番点亮 if (p.glowTime 0) { p.glowTime--; } else if (Math.random() 0.015) { p.glowTime p.glowMax; } } } // 绘制连线与节点 function draw() { ctx.clearRect(0, 0, width, height); // 第一层节点连线 for (let i 0; i particles.length; i) { for (let j i 1; j particles.length; j) { const a particles[i]; const b particles[j]; const dx a.x - b.x; const dy a.y - b.y; const dist Math.sqrt(dx * dx dy * dy); if (dist LINK_DISTANCE) { const alpha (1 - dist / LINK_DISTANCE) * 0.45; ctx.strokeStyle rgba(${COLOR_RGB}, ${alpha}); ctx.lineWidth 1; ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke(); } } } // 第二层发光节点 for (const p of particles) { const glowRatio p.glowTime 0 ? Math.min(1, p.glowTime / 15) : 0; const baseAlpha 0.35 glowRatio * 0.65; if (glowRatio 0) { ctx.shadowColor rgba(${COLOR_RGB}, 1); ctx.shadowBlur 18 * glowRatio; } else { ctx.shadowBlur 0; } ctx.fillStyle rgba(255, 255, 255, ${baseAlpha}); ctx.beginPath(); ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur 0; } } function animate() { update(); draw(); requestAnimationFrame(animate); } window.addEventListener(resize, () { resize(); createParticles(); }); resize(); createParticles(); animate(); })();这段代码的核心逻辑是每一帧先更新节点位置和发光计时器再清空画布先画连线、再画节点。发光节点使用shadowBlur模拟光晕为了避免shadowBlur影响连线的绘制效率连线部分不设置阴影节点部分绘制完成后立即把shadowBlur重置为 0。4.5 本地运行与验证完成以上三个文件后启动本地静态服务器cd saas-ai-web3-landing python3 -m http.server 8080浏览器访问http://localhost:8080预期效果如下页面首屏是深蓝黑渐变背景标题带青色到紫色的渐变文字。背景中有约 50 个白色小节点缓慢移动节点之间距离小于 130px 时出现青色连线。节点会随机依次点亮点亮瞬间外圈出现青色光晕随后慢慢熄灭形成“轮番发光”的组网效果。产品架构区域有四个子节点围绕中心平台节点排列整体结构清晰。如果页面没有动画按 F12 打开开发者工具在 Console 面板查看是否有 JavaScript 报错。最常见的错误是文件路径不对导致network.js没有被加载或者是 Canvas 节点不在预期位置导致getContext返回空值。4.6 AE 模板在落地页中的衔接方式如果你的项目希望像标题描述的那样把 AE 模板做好的宣传动画也接入页面推荐按下面两种方式处理。第一种是视频方式。在 AE 中完成片头合成后通过 Adobe Media Encoder 导出 H.264 编码的 MP4 文件放到项目的assets目录然后在 HTML 的hero-mock区块里用video标签引入。需要提醒的是MP4 体积往往较大落地页首屏加载应尽量控制在 2MB 以内否则会明显拖慢首屏速度。建议使用工具压缩或者把视频改为点击播放而不是自动循环播放。第二种是 Lottie 方式。如果你只需要轻量的 Logo 动画、图标动画可以在 AE 中安装 Bodymovin 插件导出 JSON 文件
返回列表