ARTICLE DETAIL

资讯详情

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

HTML+CSS+JS实现动态背景登录界面:Canvas粒子与毛玻璃卡片实战

HTML+CSS+JS实现动态背景登录界面:Canvas粒子与毛玻璃卡片实战 简介这是一款基于HTML、CSS与JavaScript实现的动态背景登录界面模板面向网页设计初学者、大学生课程设计与期末作业制作者以及需要快速搭建个性化登录页的前端开发人员。模板通过JS粒子动效让背景富有变化搭配CSS美化和清晰的结构布局可快速融入个人站点或课程项目。压缩包共3个文件包括一个主页面HTML、一个样式表CSS与一个动态效果JS文件结构简洁整个资源包仅5KB适合直接运行与二次修改整个模板轻量易用解压即可查看源码方便教学演示与个人练习。目前已有691人学习使用适合作为网页设计课程设计、期末大作业或毕业设计的参考案例。资源包内文件分工明确HTML负责页面骨架CSS控制视觉风格JS驱动背景动态效果便于对照理解前端三层架构同时也提供了一套可直接套用的登录表单结构方便扩展验证功能与用户交互。 如果你在网上搜过“htmlcssjs 动态背景登录界面”大概率会看到两类结果要么是早年那种粉色渐变配圆角按钮的“上古模板”要么是贴了一整屏源码、复制下来却跑不起来的炫酷片段。我早期做后台项目时没少被这类模板坑过后来花了一个周末把背后的原理捋清楚才发现动态背景这事没有看上去那么玄。这篇文章就用一套可以直接跑起来的登录页模板带你完整走一遍canvas 粒子连线背景、CSS 光斑氛围、毛玻璃登录卡片以及我实际调试时踩过的坑。传统方案里“模板”是复制答案我这里尽量给解题思路。适合刚学完 HTML/CSS/JS 基础、想独立完成一个完整小项目的同学也适合经常被后勤页面压榨的全栈工程师。1. 先别急着复制动态背景的几种方案各自代价是什么1.1 方案选型对照做动态背景看起来选择很多但剥开外壳主流方案就这么几类。我在动手之前习惯先列个表把每个方案的代价搞清楚后面才不会写着写着后悔。方案视觉效果实现难度性能成本典型场景纯 CSS 渐变动画柔和、大气低极低品牌官网、落地页CSS 浮动元素 JS 随机漂移活泼、可爱低低但元素多时 DOM 开销大儿童应用、天气类页面Canvas 粒子连线科技感强中中但可控登录页、数据大屏WebGL / Three.js3D、震撼高高产品展示、活动页视频背景真实感强低文件体积大流量费钱影视类、游戏官网1.2 我为什么选“canvas 粒子 CSS 光斑”登录页这个场景我最推荐的是 canvas 粒子连线再配两个 CSS 光斑做底色。原因很实在登录页画幅小40 到 60 个粒子就能撑起整块背景canvas 以 60fps 跑毫无压力。粒子连线天然带一种“神经网格”的暗示很容易让用户觉得这个产品有点技术含量视觉上比纯渐变更耐看。而 CSS 光斑负责氛围两个径向渐变放大缩小几乎不占 JS 开销。更关键的是这套组合不需要引任何第三方库原生三件套就能吃完对网络环境不友好、需要离线部署的后台系统尤其适用。方案选择永远跟着需求走你要是做儿童应用泡泡和云朵反而比粒子更合适做官网视频背景也完全可以但代价要是提前想清楚。2. 页面骨架HTML 把背景层和登录卡片分开摆好2.1 三层结构写任何页面我习惯先把结构摆清楚。动态背景登录页的结构可以分成三层背景层负责动态视觉内容层负责承载卡片卡片里面再放表单。背景层单独用一个 div 包起来而不是直接给 body 加背景原因后面会讲到。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title动态背景登录界面/title link relstylesheet hrefstyle.css /head body div classbg aria-hiddentrue div classbg-glow bg-glow--one/div div classbg-glow bg-glow--two/div canvas idparticles/canvas /div main classlogin-wrap section classlogin-card div classlogin-header h1欢迎回来/h1 p请输入你的账号信息/p /div form idloginForm classlogin-form div classform-group label forusername账号/label input typetext idusername nameusername placeholder邮箱或手机号 autocompleteusername required /div div classform-group label forpassword密码/label div classpassword-box input typepassword idpassword namepassword placeholder请输入密码 autocompletecurrent-password required button typebutton classpwd-toggle idpwdToggle显示/button /div /div div classform-options label classcheckbox input typecheckbox checked 记住我 /label a href# classforgot忘记密码/a /div button typesubmit classlogin-btn登 录/button /form /section /main script srcscript.js/script /body /html这套结构的核心是把背景层和内容层彻底分开背景层里的两个光斑 div 和 canvas 各自负责一部分视觉统一归在.bg容器中.login-wrap用 flex 把卡片居中层级压在背景上面。后面无论你想换粒子颜色、加一层流动光效还是换卡片布局都只改对应那一层不会牵一发动全身。2.2 容易被忽略的表单可访问性细节表单部分我踩过一个很典型的坑typepassword的输入框只写了placeholder没写label。视觉上看不出问题但浏览器自动填充密码时经常不给力屏幕阅读器更是完全读不到输入框含义。所以我在结构里给每个输入框都配了label forfor的值和输入框的id一一对应点击 label 就能聚焦到对应输入框。autocomplete也要写清楚用户名框写username密码框写current-password这样 Chrome、Edge 记住密码后回填才正常。“显示密码”按钮用typebutton不然它会被当作提交按钮点一下整个表单就刷新了。3. 粒子系统才是主角canvas 背后到底在画什么3.1 初始化DPR 一上来就是关键粒子背景的 JS 代码核心就是为一个 canvas 建立坐标系、维护一组粒子、然后每帧画一遍。先看完整代码我再逐段拆开讲为什么要这么写。const canvas document.getElementById(particles); const ctx canvas.getContext(2d); let particles []; let mouse { x: null, y: null }; let w, h, dpr; const CONFIG { count: 55, color: 255, 255, 255, linkDistance: 150, speed: 0.4 }; function init() { dpr Math.max(window.devicePixelRatio || 1, 2); w window.innerWidth; h window.innerHeight; canvas.width w * dpr; canvas.height h * dpr; canvas.style.width w px; canvas.style.height h px; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); createParticles(); } function createParticles() { const num w 768 ? Math.floor(CONFIG.count * 0.5) : CONFIG.count; particles []; for (let i 0; i num; i) { particles.push({ x: Math.random() * w, y: Math.random() * h, vx: (Math.random() - 0.5) * CONFIG.speed, vy: (Math.random() - 0.5) * CONFIG.speed, r: Math.random() * 1.6 0.6 }); } } function draw() { ctx.clearRect(0, 0, w, h); for (let i 0; i particles.length; i) { const p particles[i]; p.x p.vx; p.y p.vy; if (p.x 0 || p.x w) p.vx * -1; if (p.y 0 || p.y h) p.vy * -1; ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fillStyle rgba(${CONFIG.color}, 0.8); ctx.fill(); for (let j i 1; j particles.length; j) { const q particles[j]; const dx p.x - q.x; const dy p.y - q.y; const dist Math.hypot(dx, dy); if (dist CONFIG.linkDistance) { const alpha (1 - dist / CONFIG.linkDistance) * 0.6; ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(q.x, q.y); ctx.strokeStyle rgba(${CONFIG.color}, ${alpha}); ctx.lineWidth 0.6; ctx.stroke(); } } } if (mouse.x ! null) { for (const p of particles) { const dx p.x - mouse.x; const dy p.y - mouse.y; const dist Math.hypot(dx, dy); if (dist CONFIG.linkDistance * 1.2) { const alpha (1 - dist / (CONFIG.linkDistance * 1.2)) * 0.8; ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(mouse.x, mouse.y); ctx.strokeStyle rgba(${CONFIG.color}, ${alpha}); ctx.lineWidth 0.8; ctx.stroke(); } } } requestAnimationFrame(draw); } window.addEventListener(resize, () init()); window.addEventListener(mousemove, (e) { mouse.x e.clientX; mouse.y e.clientY; }); window.addEventListener(mouseout, () { mouse.x null; mouse.y null; }); init(); draw();第一件事就是处理 DPR。canvas 的width和height属性是物理像素CSS 里的width是逻辑像素。如果不处理高分屏上 canvas 只有一个 CSS 像素对应的绘制区域画出来全是糊的。所以这里canvas.width w * dpr再ctx.setTransform(dpr, 0, 0, dpr, 0, 0)把坐标系放大到逻辑像素尺寸绘制的数值就和 CSS 一致了。3.2 渲染主循环移动、连线、画点粒子系统的循环逻辑不复杂就是反复做三件事清屏、更新位置、画点连线。清屏用ctx.clearRect(0, 0, w, h)不清的话上一帧的图形会残留变成一条条拖尾。更新位置就是给每个粒子的x加vxy加vy碰壁时把速度取反让粒子在屏幕边缘弹回来。画点时用ctx.arc画一个小圆半径r控制在 0.6 到 2.2 像素之间太小看不见太大显得笨重。连线部分是整个效果的灵魂。外层循环遍历每个粒子内层循环从i 1开始避免重复计算同一对粒子。Math.hypot(dx, dy)算的是两个粒子的欧氏距离如果距离小于设定的 150 像素就画一条线。线的透明度按1 - dist / linkDistance衰减也就是说越近越亮越远越淡视觉上就呈现出一张疏密有致的网络。这里用的是线性衰减实测效果比较干净如果你想要那种“突然收紧”的感觉可以把衰减改成平方关系。循环开销要心里有数。双重循环是 O(n²) 复杂度55 个粒子一轮要算 1400 多次距离毫无压力但如果你把粒子数调到 500一轮就是 12 万次计算帧率会明显往下掉。这就引出后面性能优化的关键。3.3 鼠标交互和 resize鼠标交互是这套模板里性价比最高的功能。监听mousemove记录鼠标坐标在每帧绘制时把鼠标当成一个额外的“锚点”和附近粒子连线。用户移动鼠标时周围的粒子会被“牵”出一条条亮线整个页面立刻有了互动感而代码只是多了十行左右。resize监听必须做但它的实现有个容易踩的坑直接调用init()重建整个画布和粒子数组而不是对旧粒子做平移或缩放。窗口大小一变旧的坐标范围已经失效保留旧粒子只会得到一堆画在画布外面的幽灵点。登录页这个场景重建成本极低所以我从来不做防抖直接重建。4. 让卡片“浮”起来毛玻璃、渐变和表单细节4.1 背景层的光斑动画CSS 部分才是拉开观感差距的地方。先看关键代码:root { --primary: #6c5ce7; --primary-2: #a29bfe; --text: #e8eaf0; --text-muted: #9aa0b5; --card-bg: rgba(255, 255, 255, 0.06); --radius: 18px; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: PingFang SC, Microsoft YaHei, sans-serif; color: var(--text); overflow: hidden; background: #0d0f1f; } .bg { position: fixed; inset: 0; z-index: 0; } .bg-glow { position: absolute; border-radius: 50%; filter: blur(90px); opacity: 0.55; } .bg-glow--one { width: 420px; height: 420px; left: -10%; top: -20%; background: radial-gradient(circle, #6c5ce7, transparent 70%); animation: breathe 12s ease-in-out infinite alternate; } .bg-glow--two { width: 360px; height: 360px; right: -5%; bottom: -15%; background: radial-gradient(circle, #0abde3, transparent 70%); animation: breathe 14s ease-in-out infinite alternate-reverse; } keyframes breathe { from { transform: translate(0, 0) scale(1); } to { transform: translate(50px, 30px) scale(1.15); } } #particles { position: absolute; inset: 0; display: block; }光斑的原理是用大尺寸的径向渐变配合filter: blur(90px)把锐利的渐变边缘柔化成大面积的色晕。两个光斑颜色不同、动画时长不同、方向相反一个 12 秒一个 14 秒组合出的效果就不会像复制粘贴一样呆板。这里我把背景颜色定得很深接近藏蓝黑。深色底是粒子白线和彩色光斑的最佳搭配对比度足够又能压住卡片。如果你把 body 背景改成浅色粒子线的白点会看不清整个模板的观感会直接垮掉。4.2 卡片毛玻璃与兼容兜底.login-wrap { position: relative; z-index: 1; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; } .login-card { width: 100%; max-width: 400px; padding: 44px 38px 36px; background: var(--card-bg); border: 1px solid rgba(255, 255, 255, 0.14); border-radius: var(--radius); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35); animation: cardIn 0.6s ease both; } supports not (backdrop-filter: blur(1px)) { .login-card { background: rgba(20, 22, 45, 0.92); } } keyframes cardIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }毛玻璃效果背后是backdrop-filter: blur(14px)它会模糊卡片背后真实渲染出来的内容。这里有个前提卡片背后必须有动态内容可模糊如果把背景做成纯白毛玻璃就是一块白板这也是我坚持单独分一个背景层的原因。backdrop-filter的兼容性比想象中差一些部分安卓浏览器不支持。所以我用了supports not做降级不支持时背景色从低透明度的白色改为接近深色的高不透明度文字依然能看清。另外注意-webkit-backdrop-filter也要写上iOS 的 Safari 认这个前缀。卡片入场动画cardIn时间控制在 0.6 秒只做淡入和上移不做花哨的旋转缩放。登录是一个高频操作动画过长会让人烦躁。4.3 输入框、按钮与自动填充的坑输入框的 focus 状态是用户最先感知到的交互反馈。这里我的做法是边框变色加外发光box-shadow: 0 0 0 3px rgba(108, 92, 231, 0.25)比单纯改边框颜色更有“聚焦感”配合transition让颜色在 0.2 秒内过渡手感会顺很多。这一类细节最容易翻车的反而是浏览器自动填充。Chrome 一旦自动填充输入框会被强行铺一层黄色背景CSS 普通写法根本盖不住。我的处理方案是给.form-group input加上这么一段input:-webkit-autofill { -webkit-box-shadow: 0 0 0 1000px #171a33 inset; -webkit-text-fill-color: var(--text); transition: background-color 9999s ease-in-out 0s; }原理是用巨大的内阴影盖住黄色底再把文字颜色改成主题色。这个方法我在 Chrome 和 Edge 上都验证过稳定有效。按钮部分就是常规渐变加 hover 上移和阴影active 时按下去一点点交互反馈自然。5. 从能跑到能上线我调这个模板时踩过的坑5.1 第一版 canvas 是糊的DPR 处理这是我最早犯的错。第一次写完在 2K 显示器上看粒子边缘发虚还以为是 canvas 画圆精度问题。后来才知道是 DPR 没处理canvas 的物理像素和 CSS 像素不一致系统把一张小图拉伸成大图自然模糊。解决方式就是前面代码里的canvas.width w * dpr加ctx.setTransform(dpr, 0, 0, dpr, 0, 0)。建议把dpr上限设成 2Math.max(window.devicePixelRatio || 1, 2)防止极端设备上画布过大、内存吃紧。5.2 粒子数量失控风扇开始转第二个坑是粒子数量和连线距离。我把粒子数调到 120linkDistance调到 220立刻发现笔记本风扇声音变大帧率从 60 掉到 40 左右。问题出在双重循环的计算次数增长太快还有连线太多、每帧要画几百条线。后来我做了两件事一是把粒子数压回 55并且用w 768判断移动端移动端直接减半到 27 个左右二是限制linkDistance不要超过屏幕宽度的 0.2 倍否则粒子网络会糊成一团。现在桌面端全程稳定 60fps。5.3 backdrop-filter 和 iOS 高度问题兼容性问题的集中爆发点有两处。一处是backdrop-filter某国产浏览器直接不渲染卡片变透明白字直接怼在深色背景上可读性尚可但奇怪。加supports降级后解决。另一处是移动端 Safari 的100vh偏差问题地址栏收起、弹出时视口高度会变化卡片位置会跳。我的方案是登录容器用min-height: 100vh配合 flex 居中必要时再加一行min-height: -webkit-fill-available兜底。5.4 兼容与性能问题速查问题现象根因处理方式粒子模糊未处理 DPRcanvas 尺寸乘 dpr并 setTransform帧率暴跌粒子数过多、连线过密限制数量移动端减半linkDistance 设上限毛玻璃不生效浏览器不支持 backdrop-filtersupports 降级为高不透明度实底自动填充黄底Chrome 强制样式inset box-shadow 覆盖 文字颜色修正卡片位置跳动vh 在移动端变化min-height 替代 height加 webkit-fill-availableresize 后粒子错乱旧坐标残留resize 时重建画布和粒子数组最后再分享一个我自己常用的检查习惯模板跑通后打开 DevTools 的 Rendering 面板勾选 Paint Flashing看哪些区域在被反复重绘。粒子只在 canvas 区域绘制光斑由 GPU 合成卡片不参与动画这个层次一旦分开性能问题基本就控制住了。模板本身不是终点借这个模板把三种语言如何分工、动画如何驱动搞明白后面遇到再花哨的动态效果你也能一眼拆开看它的骨架。本文还有配套的精品资源点击获取
返回列表