用CSS、Canvas和Three.js实现HTML玫瑰花效果的三种方案

发布时间:2026/10/14 18:35:09
用CSS、Canvas和Three.js实现HTML玫瑰花效果的三种方案 “html 玫瑰花”是个很有迷惑性的小项目。看起来只是画一朵花真动手之后才会发现它把 CSS 布局、几何变换、数学曲线、动画循环、3D 点云这些前端图形相关的东西全串在了一起。很多人在搜索框里敲这个词要么是想做点带情绪的小页面要么是练手到瓶颈期想找个视觉反馈足够强的挑战。这篇文章不讲空洞的“灵感”直接拆三种可运行方案纯 CSS 玫瑰、Canvas 粒子玫瑰、Three.js 3D 玫瑰。每个方案我都会给出核心代码和调参思路最后再集中讲讲我实际踩过的坑。1. 从“html 玫瑰花”想到的需求与技术选型1.1 它到底想表达什么先别急着写代码。一个标题越简单越要先把“完成标准”问清楚。你要的玫瑰花是静态图片还是能旋转的立体花是在页面角落一闪而过的小彩蛋还是要作为整个页面的视觉中心需求不同技术选型完全不同。最常见的目标有两种。一种是给喜欢的人做一个表白页这个时候玫瑰花更像一个符号重点在整体氛围花瓣层叠、颜色渐变、缓缓飘落辅助文字出现。另一种是纯粹想验证自己的前端图形能力这时候玫瑰花必须足够“复杂”——层次多、有光影、可交互最终效果要让人觉得“这居然是纯前端写的”。我建议你在动手前先给自己定一个验收标准是截图发出去能让人点赞还是打开页面三秒后自动旋转开屏这个标准决定了你接下来要在代码里投入多少量。不要一上来就折腾 Three.js如果只是需要一个海报级的静态页纯 CSS 完全够用回头你还会觉得它甘甜。1.2 五种常见实现路线对比我把常见的“html 玫瑰花”实现方式整理成了一张表你可以直接照着选。这个表也是我最初在这个项目里纠结时的参考信息量不大但能避免走弯路。方案核心依赖视觉效果实现难度代码量适合场景纯 CSS div 叠加CSS3 transform、渐变扁平、层次感强低少静态展示、快速见效SVG 路径绘制path、渐变精致、可无限放大中中等绘制写实花型、矢量图形Canvas 2D 粒子数学曲线、粒子系统梦幻、动态感强中高中等背景动效、交互页Three.js 3D 模型BufferGeometry、材质立体、可旋转高较多沉浸式展示、个人作品纯 CSS 少量 JS 混合CSS 动画、DOM 操作折中、易定制低中少大多数表白页这里面没有标准答案。你能看到网上很多效果很炫的“HTML 玫瑰花”基本都是 Three.js 的点云因为粒子的规律分布天然能制造出朦胧浪漫的感觉。但如果你把这种效果放到低端移动设备上帧率会直线下滑。所以选型不是看哪个效果最好而是看你的部署环境、浏览器用户和使用场景。我自己的经验是第一版永远先选最笨的方案先把“一朵玫瑰”从无到有做出来再考虑要不要往上升级。直接从 3D 粒子入手的后果是你可能花了三四个小时调相机角度最后花不像花一个雪球。2. 先学会在脑子里“拆花”几何建模是共同基础2.1 先把玫瑰拆成四个部件不管用哪种技术栈玫瑰花在浏览器里最终都要被拆成几个可以重复的元素。我在设计图形时习惯把一朵花拆成这四个部分花瓣、花蕊、花萼、枝叶。其中花萼和枝叶在大多数小页面特效里不会出现因为它们会拉低“中心视觉”的聚焦感但花瓣和花蕊几乎是必须具备的。花瓣的核心特征是“层叠”。从正上方看玫瑰花的斜式曲线其实是一系列围绕中心旋转的椭圆弧每一层比上一层略大、略向外翻。理解这一点之后你就知道为什么许多人用“多个圆形 div 旋转排列”来做玫瑰——它是在用正多边形模拟层叠效果。看起来好像很简单但真正做得好的人会在每一层加轻微的错位角度让花瓣之间像瓦片一样咬合而不是规则得像雪花。所以落到代码层面你只需要回答三个问题每片花瓣长什么样它在哪个角度它距离中心多远所有方案的核心都在回答这三个问题。2.2 用旋转和缩放理解花瓣层叠CSS 里的transform: rotate()和transform-origin是一个很好的切入点。我刚开始做的时候没太理解transform-origin结果花瓣永远绕着自己的中心旋转而不是绕“花的中心”旋转出来的效果就像撒了一把碎纸片。后来我用一个类比才转过弯来每片花瓣都是一扇门门轴transform-origin是花瓣靠近中心的一端手柄在花瓣外侧。你推动手柄的时候门会绕着门轴弧线运动。CSS 中设置transform-origin: bottom center就是把这扇门的铰链定在底部中心。然后再配合rotate()让门打开配合translateY()把门向外推这样层叠感就出来了。这不只是 CSS 专用。到了 Canvas 和 Three.js 里你依然要计算旋转角度和偏移量。区别只是 CSS 由浏览器帮你算变换矩阵Canvas 或 Three.js 要自己在数学上做。先把这两层逻辑理清后面才不会看到正弦余弦就发懵。2.3 二维坐标到三维空间的过渡从纯 CSS 到 Canvas图形坐标还停留在二维平面。但是点阵玫瑰想要有“立体感”一般会做两件事给粒子分层让每一层的半径或高度不同或者直接把点从二维极坐标扩展到三维坐标。这里不需要构造初级曲面很多视觉作品都只是把玫瑰线公式放在球面上然后让粒子随高度收缩。如果你懂一点极坐标知道玫瑰线公式r a·cos(kθ)那恭喜你你已经拿到 Canvas 和 Three.js 两套方案的关键钥匙。k 控制花瓣数量a 控制整体大小θ 从 0 到 2π 扫一圈就会留下一条像花瓣一样的封闭曲线。这种数学结果搬到 3D 之后绕着 Y 轴或 Z 轴旋转就能形成花苞结构。后面我会具体讲这段公式怎么用。3. 入门实操纯CSS玫瑰花从结构到动画3.1 书写HTML骨架一朵花就是一组div先写一个最简单的静态版本。纯 CSS 玫瑰的优势是不需要引入任何库打开浏览器就能看。我在本地测试时的 HTML 骨架是这样的div classrose div classlayer div classpetal/div div classpetal/div div classpetal/div div classpetal/div div classpetal/div /div div classlayer div classpetal/div div classpetal/div div classpetal/div div classpetal/div div classpetal/div div classpetal/div /div div classlayer div classpetal/div div classpetal/div div classpetal/div div classpetal/div div classpetal/div div classpetal/div /div div classbud/div /div这里用了三层花瓣外层和内层数量不一样。外层层数是 5 片内层 6 片目的就是打破规律感。真实玫瑰的花瓣数量并不是对称排列的越是模仿自然的外形越应该有这种错位。不要小看这个细节许多初学作品一眼就能被看出“这是电脑画的”多半就是因为每层旋转角度机械地均匀分布导致整朵花成了一个卫星天线模型。3.2 CSS形状与位置调参接下来是 CSS 里最关键的一块花瓣形状。我试过很多种方案最简单的花瓣形状是用椭圆加圆角捏出来.petal { width: 70px; height: 110px; background: linear-gradient(145deg, #ff6b7d 0%, #c41248 100%); border-radius: 70% 30% 70% 30% / 60% 40% 60% 40%; transform-origin: bottom center; position: absolute; left: 50%; bottom: 50%; }这个border-radius组合是重点。70% 30%是四个角的水平半径交叉设置60% 40%是垂直半径交叉设置一组合就会产生一端尖、一端圆的花瓣轮廓。如果你觉得花瓣太宽就把宽度调小如果你想让它更尖就把第二个数字往 20% 附近压。定位上每个.petal都挂在同一个参考点然后用不同的rotate()让它们散开。比如外层.layer:nth-child(1) .petal:nth-child(1) { transform: rotate(0deg) translateY(-32px); } .layer:nth-child(1) .petal:nth-child(2) { transform: rotate(72deg) translateY(-32px); } .layer:nth-child(1) .petal:nth-child(3) { transform: rotate(144deg) translateY(-32px); } .layer:nth-child(1) .petal:nth-child(4) { transform: rotate(216deg) translateY(-32px); } .layer:nth-child(1) .petal:nth-child(5) { transform: rotate(288deg) translateY(-32px); }这里的translateY(-32px)是让花瓣沿着旋转后的方向“向外推”一段距离。注意顺序先旋转再位移浏览器会按照 transform 列表从左到右执行所以位移的方向是旋转后的坐标方向。如果你把位移写在 rotate 前面花瓣就会往原始方向偏移最后聚成一个怪物。花蕊部分我用了一个深红色小圆.bud { position: absolute; left: 50%; top: 50%; width: 32px; height: 32px; background: radial-gradient(circle at 35% 35%, #ffeb8f, #d99600); border-radius: 50%; transform: translate(-50%, -50%); box-shadow: 0 0 18px rgba(255, 200, 60, 0.45); }这套代码加在一起就能在页面上看到一个扁平但很有层次感的玫瑰。静态版本不需要 JS适合放在展示页的角落。我在实际做的时候还加了background-color: #0d1117这种深色底因为深色背景会让荧光色渐变跳出来视觉冲击力明显更强。3.3 开放动画和飘落花瓣的取舍纯 CSS 玫瑰如果要加“开放动画”最简单的方式是用keyframes让花瓣的rotate()角度从收起状态过渡到展开状态。比如.petal { animation: bloom 2.4s ease-out forwards; opacity: 0; } keyframes bloom { 0% { transform: rotate(0deg) translateY(0); opacity: 0; } 60% { opacity: 1; } 100% { transform: rotate(var(--angle)) translateY(-32px); opacity: 1; } }但这里有个隐藏麻烦每片花瓣的旋转角度不同你不能写死一个动画最好用 CSS 变量--angle或者针对每片花瓣单独写关键帧。我通常维护少量变量用内联样式传给每一片花瓣省得写 20 组重复代码。动画时长也要配合从花蕊向外的展开顺序可以用animation-delay依次延迟 0.05 秒实现层层开放的感觉。我一般不推荐在同一个页面里又做开放动画又做飘落花瓣。两个效果都会抢占视觉注意力飘落的碎片会把视线从中心玫瑰上拉走。如果你要的是温暖安静的页面静态玫瑰加上呼吸光晕就够了如果你要的是动感页面那么飘落花瓣做三个方向就行别做成暴风雪。4. 进阶实操Canvas粒子玫瑰花动效4.1 粒子玫瑰的底层逻辑如果你想做出比 CSS 玫瑰更梦幻的效果Canvas 粒子是最好的下一站。粒子玫瑰的基本思路很简单在 Canvas 上创建成百上千个微小圆点把这些圆点分布在玫瑰曲线轨迹附近再让它们按规律运动。这样用户看到的不是离散的点集而是由粒子构成的花型。做这个过程的时候我建议先忘掉“玫瑰”这个名字把自己当成一个绘制器。你需要两样东西每个粒子的坐标以及每一帧它的坐标增量。坐标来源可以是一个数学公式也可以是一张图片的像素位置。用数学公式的好处是你可以任意改变花瓣数量、大小和粒子密度实时预览效果。我目前最常用的坐标生成方式是先按玫瑰线公式随机生成粒子原点再加入一个小的随机抖动让花瓣边缘不至于太锐利多一层“手绘感”。每个粒子颜色取红色渐变配合透明度差异就能模拟出阴影。4.2 玫瑰曲线的数学味道这里需要一点点极坐标知识。最经典的玫瑰线公式是r a * cos(k * θ)a控制花的大小k控制花瓣数量。k是奇数时整条曲线有 k 个花瓣k如果是偶数就会产生 2k 个花瓣。我写了一个小 Demo 用的就是k 6效果类似多重花瓣的重叠。为了把平面曲线变成有纵深的“花朵”我会叠加一个 y 轴基数或者用多层缩放来模拟花瓣的重叠高度。实际代码可以这样生成粒子初始位置const canvas document.getElementById(roseCanvas); const ctx canvas.getContext(2d); canvas.width 600; canvas.height 600; const particles []; const petalSize 160; const count 2200; for (let i 0; i count; i) { const theta Math.random() * Math.PI * 2; const layer Math.floor(Math.random() * 3); const k 5 - layer * 0.6; const r petalSize * Math.abs(Math.cos(k * theta / 2)) * (0.3 layer * 0.35); const jitter (Math.random() - 0.5) * 4; particles.push({ x: canvas.width / 2 (r jitter) * Math.cos(theta), y: canvas.height / 2 (r jitter) * Math.sin(theta) * 0.75, radius: Math.random() * 2 0.6, baseAlpha: Math.random() * 0.5 0.3, color: hsl(${350 Math.random() * 20}, ${70 Math.random() * 20}%, ${60 Math.random() * 15}%) }); }这里我故意把y方向乘以 0.75也就是在纵向上压缩一点让粒子玫瑰看起来更圆润、更接近俯视视角下的花冠。r的计算里用了Math.abs(Math.cos(k * theta / 2))这是为了让花瓣边缘有起伏而不是普通圆环。你别指望一条数学公式真的长出电影特效里的玫瑰但作为粒子视觉它足以让人一眼认出这是花。渲染的时候很简单function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(p { ctx.globalAlpha p.baseAlpha Math.sin(Date.now() * 0.002 p.x) * 0.05; ctx.fillStyle p.color; ctx.beginPath(); ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2); ctx.fill(); }); requestAnimationFrame(draw); } draw();这段代码会让每个粒子的透明度像脉搏一样轻微浮动花朵整体有了呼吸感。如果觉得粒子太散就把jitter从 4 改为 1.5如果觉得花瓣数太少就调大k的基数。4.3 渲染循环、交互与性能Canvas 粒子做交互也很顺手。常见的交互效果是鼠标靠近时周围粒子被推开形成“吹散”效果。做法是在每一帧计算鼠标位置然后遍历粒子根据距离施加一个小位移向量let mouseX -9999; let mouseY -9999; canvas.addEventListener(mousemove, e { mouseX e.clientX; mouseY e.clientY; }); function updateParticles() { particles.forEach(p { const dx p.x - mouseX; const dy p.y - mouseY; const dist Math.sqrt(dx * dx dy * dy); if (dist 120) { const force (120 - dist) / 300; p.x dx * force; p.y dy * force; } }); }这种“斥力”会让玫瑰在鼠标扫过时像被风吹起一块松开后又慢慢回弹。不过这里有个性能隐患粒子数量超过 3000 之后每帧做距离判断会卡顿。我通常的做法是把粒子数量压在 2500 以内或者只对鼠标附近矩形区域做计算。移动端则直接用touchmove事件并把devicePixelRatio考虑进 Canvas 尺寸否则屏幕会发虚。我自己的经验是粒子玫瑰最好设置成“先静态展示 2 秒再启动动画”这样页面打开时不会立刻疯狂闪烁。许多人一上来就开动画结果用户还没看清花的形状只看到一团飞蚊。5. 硬核实操Three.js里做一朵可旋转的3D玫瑰5.1 搭好3D舞台如果你想让玫瑰真正立起来用户还能用鼠标拖拽旋转那就需要 Three.js 了。先说清楚Three.js 里做一朵高保真的写实玫瑰难度极高涉及参数曲面、UV 贴图和次表面散射但做一个“一眼能认出是玫瑰、动态足够惊艳”的视觉效果其实没有想象中复杂。先用一段基础代码把场景搭起来import * as THREE from three; import { OrbitControls } from three/examples/jsm/controls/OrbitControls.js; const scene new THREE.Scene(); const camera new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100); camera.position.set(3, 2, 5); camera.lookAt(0, 0.5, 0); const renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); const controls new OrbitControls(camera, renderer.domElement); controls.target.set(0, 0.6, 0); controls.enableDamping true;这里OrbitControls可以让用户通过鼠标拖拽旋转视角视觉上花是 3D 的。如果你只是要做自动旋转的花不要引入控制器直接让花模型本身旋转或者让相机绕 Y 轴转圈也行代码更轻。5.2 用粒子或网格生成花瓣3D 玫瑰最简单的两种建模路线粒子点云和表面网格。粒子点云的好处是代码量少且天生带朦胧感。上一节讲的玫瑰线公式可以直接扩展为三维const points []; const N 6000; for (let i 0; i N; i) { const theta Math.random() * Math.PI * 2; const phi Math.random() * Math.PI * 2; const r 1.2 * Math.abs(Math.sin(4 * theta)) 0.35; const x r * Math.cos(theta) * (0.8 Math.sin(phi) * 0.3); const y Math.cos(phi) * 0.8 Math.random() * 0.4; const z r * Math.sin(theta) * (0.8 Math.sin(phi) * 0.3); points.push(x, y * 0.5, z); } const geometry new THREE.BufferGeometry(); geometry.setAttribute(position, new THREE.Float32BufferAttribute(points, 3)); const material new THREE.PointsMaterial({ color: 0xff5d7b, size: 0.02, transparent: true, opacity: 0.9 }); const rose new THREE.Points(geometry, material); scene.add(rose);这一段代码生成的粒子会形成一个花瓣状的点云层次感靠一个简单的深度错觉维持。如果你想要硬边花瓣就把Points换成Mesh并使用SphereGeometry压扁后复制摆放同样能拼出不错的玫瑰。比如const petalGeo new THREE.SphereGeometry(0.4, 16, 12); petalGeo.scale(1, 0.25, 1.3); const group new THREE.Group(); for (let layer 0; layer 4; layer) { const count 6 - layer; const radius 0.45 layer * 0.15; const y 0.1 layer * 0.12; for (let i 0; i count; i) { const angle (i / count) * Math.PI * 2; const petal new THREE.Mesh(petalGeo, material); petal.position.set( Math.cos(angle) * radius, y, Math.sin(angle) * radius ); petal.rotation.y angle; petal.rotation.x -0.3 - layer * 0.1; group.add(petal); } } scene.add(group);这种“压扁球体当花瓣”的做法其实是很多 3D 表白页的偷懒方案。你不需要专门建模实时生成的几何体就能在旋转时展示出层叠关系。缺点是花瓣厚度一致不够写实但在展示动效完全够用。5.3 颜色、光照和镜头运动要让 3D 玫瑰有质感灯光很关键。我常用的配置很简单一个暖色点光源从斜上方打下来一个冷色半球光补充轮廓光。玫瑰材质如果用MeshPhongMaterial会有高光反射花瓣像丝绸如果用MeshStandardMaterial质感更真实但需要EnvironmentMap支持否则暗部会发黑。const pointLight new THREE.PointLight(0xffaa88, 1.2, 10); pointLight.position.set(2, 3, 3); scene.add(pointLight); const ambientLight new THREE.AmbientLight(0x404060); scene.add(ambientLight);镜头运动方面如果不想让用户手动操作可以用小段正弦函数控制相机位置let t 0; function animate() { t 0.01; rose.rotation.y 0.005; camera.position.x 4 * Math.sin(t); camera.position.z 4 * Math.cos(t); camera.lookAt(0, 0.5, 0); renderer.render(scene, camera); requestAnimationFrame(animate); } animate();这样玫瑰会自行旋转镜头绕花一周展示效果非常唬人。实际做表白页的时候可以先让镜头慢慢靠近再让花瓣粒子从底部飞入最后停到正面视角比直接无限旋转更有情绪。性能上Three.js 场景里粒子数量超过 1 万就会给移动端带来很大压力。我建议把粒子数控制在 6000 以内同时开启renderer.setPixelRatio不超过 2否则高分屏上画面会变飘。桌面端可以适当增加粒子数量移动端最好降到 4000。6. 高频问题速查与实战心得6.1 我做这个项目时踩过的坑这个项目看起来不大实际调起来坑很多。我把最常遇到的问题整理成了一张表每一条都是自己反复折腾过的不是从文档里抄来的。问题现象根本原因解决方案CSS 花瓣旋转错乱花瓣没有围绕花心散开transform-origin没设置在最底层或不正确把transform-origin设为花瓣底部中心并确保父容器定位稳定Canvas 玫瑰粒子只显示半圈缺一侧角度循环写成了 0 到 π让theta从 0 到Math.PI * 2完整扫描粒子玫瑰颜色灰暗没有渐变感全部粒子用同一个 HSL 色值给每个粒子随机hue和lightness高低差在 15%-20% 之间Three.js 页面打开是全黑的没加灯光或相机看向坐标原点但物体不在原点添加点光源和半球光把花放在坐标原点后先camera.lookAt(0,0,0)移动端页面卡死粒子数量太多或devicePixelRatio过高粒子数降到 4000 以下渲染器像素比控制在 2 以内CSS 动画结束后花瓣又回到闭合状态忘记forwards填充模式使用默认none在animation属性末尾加forwards玫瑰花看起来像一堆圆形花瓣圆角过于规则形状类似椭圆调整border-radius组合制造一头尖一头圆的不对称轮廓6.2 经验清单如何把“玫瑰花”做得比平均值更好看如果前面的代码你已经跑通了剩下就是审美层面的调参。这部分没有绝对正确但我总结出一套自己的经验可以让玫瑰花的视觉效果上限更高。第一色彩尽量不要用纯红。纯#ff0000在屏幕上容易显得生硬像宣传单上的色块。我更习惯用深红、玫红、珊瑚红做渐变从花瓣边缘往里加一点暗色模拟光照遮挡。Canvas 粒子可以用 HSL 的hue从 340 到 360 之间随机饱和度控制在 70% 到 90%明度稍微错开整体会有一种丝绸感。第二阴影和光晕比花瓣本身更重要。不管是 CSS 的box-shadow还是在 Canvas 里用半透明粒子叠加玫瑰花的边缘能散发出柔光会让画面立刻变得高级。我经常在没有必要灯光的 CSS 页面里加一个暗淡的radial-gradient背景然后把玫瑰中心提亮效果立竿见影。第三控制动画节奏。玫瑰开放效果最适合的速度是 2 到 3 秒太快像按下快门太慢用户的耐心会被耗尽。粒子玫瑰的呼吸频率也不要用一样的正弦周期每个粒子的相位差一点整体才有“活物”感。Three.js 的旋转速度控制在每秒 0.3 到 0.5 弧度即可转太快看起来像大型灾难片特效。最后分享一个我自己的小习惯先做静态结构再动画最后加交互。如果你一上来就把鼠标交互、自动旋转、飘落花瓣全堆在一起出了问题你根本不知道是几何计算错了还是控制器初始化顺序错了。把玫瑰花当成一个洋葱一层一层从中心往外构建哪一层出了问题都能单独剥离调试。这个习惯也让我后来写其他图形项目时省了很多时间。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询