HTML旋转爱心

系列文章

|----|------------------------------------------------------------------------------------------------------------|
| 序号 | 目录 |
| 1 | HTML满屏跳动的爱心(可写字) |
| 2 | HTML五彩缤纷的爱心 |
| 3 | HTML满屏漂浮爱心 |
| 4 | HTML情人节快乐 |
| 5 | HTML蓝色爱心射线 |
| 6 | HTML跳动的爱心(简易版) |
| 7 | HTML粒子爱心 |
| 8 | HTML蓝色动态爱心 |
| 9 | HTML跳动的爱心(双心版) |
| 10 | HTML橙色动态粒子爱心 |
| 11 | HTML旋转爱心 |
| 12 | HTML爱情树 |
| 13 | HTML3D相册 |
| 14 | HTML旋转相册 |
| 15 | HTML基础烟花秀 |
| 16 | HTML炫酷烟花秀 |
| 17 | HTML粉色烟花秀 |
| 18 | HTML新春烟花 |
| 19 | HTML龙年大吉 |
| 20 | HTML音乐圣诞树 |
| 21 | HTML大雪纷飞 |
| 22 | HTML想见你 |
| 23 | HTML元素周期表 |
| 24 | HTML飞舞的花瓣 |
| 25 | HTML星空特效 |
| 26 | HTML黑客帝国字母雨 |
| 27 | HTML哆啦A梦 |
| 28 | HTML流星雨 |
| 29 | HTML沙漏爱心 |
| 30 | HTML爱心字母雨 |
| 31 | HTML爱心流星雨 |
| 32 | HTML生日蛋糕 |
| 33 | HTML3D旋转相册 |
| 34 | HTML流光爱心 |
| 35 | HTML满屏飘字 |
| 36 | HTML飞舞爱心 |
| 37 | HTML雪花圣诞树 |

目录

系列文章

写在前面

完整代码

代码分析

写在后面


写在前面

HTML语言实现旋转爱心的完整代码。

完整代码

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Love</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <style>
        body {
            font-family: Monospace;
            background-color: #f0f0f0;
            margin: 0px;
            overflow: hidden;
        }
    </style>
</head>

<body>


    <script src="js/nb.js"></script>

    <script src="js/Projector.js"></script>
    <script src="js/CanvasRenderer.js"></script>

    <script src="js/tween.min.js"></script>
    <script src="js/Sparks.js"></script>

    <!-- load the font file from canvas-text -->

    <script src="js/helvetiker_regular.typeface.js"></script>


    <script>

        var container;

        var camera, scene, renderer;

        var group, text, plane;

        var targetRotation = 0;
        var targetRotationOnMouseDown = 0;

        var mouseX = 0;
        var mouseXOnMouseDown = 0;

        var windowHalfX = window.innerWidth / 2;
        var windowHalfY = window.innerHeight / 2;

        var heartShape, particleCloud, sparksEmitter, emitterPos;
        var _rotation = 0;
        var timeOnShapePath = 0;

        init();
        animate();

        function init() {

            container = document.createElement('div');
            document.body.appendChild(container);


            //相机
            camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
            camera.position.set(0, 150, 800);

            //场景
            scene = new THREE.Scene();

            group = new THREE.Group();
            scene.add(group);

            // Get text from hash

            var string = "Love";
            var hash = document.location.hash.substr(1);

            if (hash.length !== 0) {

                string = hash;

            }

            var text3d = new THREE.TextGeometry(string, {
                size: 80,
                height: 20,
                curveSegments: 2,
                font: "helvetiker"

            });

            text3d.computeBoundingBox();
            var centerOffset = -0.35 * (text3d.boundingBox.max.x - text3d.boundingBox.min.x);

            var textMaterial = new THREE.MeshBasicMaterial({ color: Math.random() * 0xffffff, overdraw: 0.5 });

            text = new THREE.Mesh(text3d, textMaterial);

            // Potentially, we can extract the vertices or faces of the text to generate particles too.
            // Geo > Vertices > Position

            text.position.x = centerOffset;
            text.position.y = 100;
            text.position.z = 0;

            text.rotation.x = 0;
            text.rotation.y = Math.PI * 2;

            group.add(text);


            particleCloud = new THREE.Object3D(); // Just a group
            particleCloud.y = 800;
            group.add(particleCloud);

            // Create Particle Systems

            // Heart

            var x = 0, y = 0;

            heartShape = new THREE.Shape();

            heartShape.moveTo(x + 25, y + 25);
            heartShape.bezierCurveTo(x + 25, y + 25, x + 20, y, x, y);
            heartShape.bezierCurveTo(x - 30, y, x - 30, y + 35, x - 30, y + 35);
            heartShape.bezierCurveTo(x - 30, y + 55, x - 10, y + 77, x + 25, y + 95);
            heartShape.bezierCurveTo(x + 60, y + 77, x + 80, y + 55, x + 80, y + 35);
            heartShape.bezierCurveTo(x + 80, y + 35, x + 80, y, x + 50, y);
            heartShape.bezierCurveTo(x + 35, y, x + 25, y + 25, x + 25, y + 25);

            var hue = 0;

            var hearts = function (context) {

                context.globalAlpha = 0.5;
                var x = 0, y = 0;
                context.scale(0.05, -0.05); // Scale so canvas render can redraw within bounds
                context.beginPath();
                // From http://blog.burlock.org/html5/130-paths
                context.bezierCurveTo(x + 2.5, y + 2.5, x + 2.0, y, x, y);
                context.bezierCurveTo(x - 3.0, y, x - 3.0, y + 3.5, x - 3.0, y + 3.5);
                context.bezierCurveTo(x - 3.0, y + 5.5, x - 1.0, y + 7.7, x + 2.5, y + 9.5);
                context.bezierCurveTo(x + 6.0, y + 7.7, x + 8.0, y + 5.5, x + 8.0, y + 3.5);
                context.bezierCurveTo(x + 8.0, y + 3.5, x + 8.0, y, x + 5.0, y);
                context.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
                context.fill();
                context.lineWidth = 0.5; //0.05
                context.stroke();

            }

            var setTargetParticle = function () {

                var material = new THREE.SpriteCanvasMaterial({
                    program: hearts
                });

                material.color.setHSL(hue, 1, 0.75);
                hue += 0.001;
                if (hue > 1)
                    hue -= 1;

                particle = new THREE.Sprite(material);

                particle.scale.x = particle.scale.y = Math.random() * 40 + 40;
                particleCloud.add(particle);

                return particle;

            };

            var onParticleCreated = function (p) {

                p.target.position.copy(p.position);

            };

            var onParticleDead = function (particle) {

                particle.target.visible = false;
                particleCloud.remove(particle.target);

            };

            sparksEmitter = new SPARKS.Emitter(new SPARKS.SteadyCounter(160));

            emitterpos = new THREE.Vector3();

            sparksEmitter.addInitializer(new SPARKS.Position(new SPARKS.PointZone(emitterpos)));
            sparksEmitter.addInitializer(new SPARKS.Lifetime(0, 2));
            sparksEmitter.addInitializer(new SPARKS.Target(null, setTargetParticle));

            sparksEmitter.addInitializer(new SPARKS.Velocity(new SPARKS.PointZone(new THREE.Vector3(0, -50, 10))));

            // TOTRY Set velocity to move away from centroid

            sparksEmitter.addAction(new SPARKS.Age());
            //sparksEmitter.addAction(new SPARKS.Accelerate(0.2));
            sparksEmitter.addAction(new SPARKS.Move());
            sparksEmitter.addAction(new SPARKS.RandomDrift(50, 50, 2000));

            sparksEmitter.addCallback("created", onParticleCreated);
            sparksEmitter.addCallback("dead", onParticleDead);
            sparksEmitter.addCallback("updated", function (particle) {

                particle.target.position.copy(particle.position);

            });

            sparksEmitter.start();

            // End Particles


            renderer = new THREE.CanvasRenderer();
            renderer.setClearColor(0xf0f0f0);
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize(window.innerWidth, window.innerHeight);
            container.appendChild(renderer.domElement);

            document.addEventListener('mousedown', onDocumentMouseDown, false);
            document.addEventListener('touchstart', onDocumentTouchStart, false);
            document.addEventListener('touchmove', onDocumentTouchMove, false);

            //

            window.addEventListener('resize', onWindowResize, false);

        }

        function onWindowResize() {

            windowHalfX = window.innerWidth / 2;
            windowHalfY = window.innerHeight / 2;

            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();

            renderer.setSize(window.innerWidth, window.innerHeight);

        }

        //

        document.addEventListener('mousemove', onDocumentMouseMove, false);

        function onDocumentMouseDown(event) {

            event.preventDefault();

            mouseXOnMouseDown = event.clientX - windowHalfX;
            targetRotationOnMouseDown = targetRotation;

            if (sparksEmitter.isRunning()) {

                sparksEmitter.stop();

            } else {

                sparksEmitter.start();

            }

        }

        function onDocumentMouseMove(event) {

            mouseX = event.clientX - windowHalfX;

            targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.02;

        }

        function onDocumentTouchStart(event) {

            if (event.touches.length == 1) {

                event.preventDefault();

                mouseXOnMouseDown = event.touches[0].pageX - windowHalfX;
                targetRotationOnMouseDown = targetRotation;

            }

        }

        function onDocumentTouchMove(event) {

            if (event.touches.length == 1) {

                event.preventDefault();

                mouseX = event.touches[0].pageX - windowHalfX;
                targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.05;

            }

        }

        //

        function animate() {//更新场景

            requestAnimationFrame(animate);

            render();

        }

        function render() {

            timeOnShapePath += 0.0337;

            if (timeOnShapePath > 1)
                timeOnShapePath -= 1;

            // TODO Create a PointOnShape Action/Zone in the particle engine
            var pointOnShape = heartShape.getPointAt(timeOnShapePath);

            emitterpos.x = pointOnShape.x * 5 - 100;
            emitterpos.y = -pointOnShape.y * 5 + 400;

            // Pretty cool effect if you enable this
            // particleCloud.rotation.y += 0.05;

            group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
            renderer.render(scene, camera);
        }

    </script>

</body>

</html>

代码分析

这段代码使用了 Three.jsSparks.js 进行 3D 渲染和粒子特效创建,其核心功能是生成一个动态心形粒子效果,并结合"Love"文本渲染,实现互动的 3D 场景。以下是详细的分解与分析:


HTML 结构

  1. 页面头部:

    • 使用了 <meta> 设置编码为 UTF-8,支持视口适配移动设备。

    • 引入内联样式表,设置 body 背景色和无滚动属性。

  2. 核心 JavaScript 文件:

    • nb.jsProjector.jsCanvasRenderer.js : 提供 Three.js 的扩展功能和基础渲染器(CanvasRenderer 用于非 WebGL 的情况下)。

    • tween.min.js: 动画库,提供平滑过渡的时间控制。

    • Sparks.js: 粒子系统的实现,定义了粒子的生命周期、位置和行为。

    • helvetiker_regular.typeface.js : 引入 Three.js 字体文件,用于生成 3D 文本。


JavaScript 核心逻辑

1. 初始化场景

通过 init() 函数初始化 Three.js 场景,包括以下部分:

  • 相机(camera) : 使用 PerspectiveCamera 设置透视投影,相机位置 (0, 150, 800)

  • 场景(scene) : 通过 Scene() 创建场景对象,添加子对象 group(用于管理场景内的物体)。

  • 文本渲染 : 利用 TextGeometry 和字体 helvetiker 创建 "Love" 文本,并设置:

    • 尺寸:80。

    • 厚度:20。

    • 颜色随机。 文本被添加到 group,并设定其位置 (x: centerOffset, y: 100, z: 0)


2. 粒子系统

代码中定义了一个粒子云 particleCloud,粒子的行为和特效通过 Sparks.js 实现:

  • 心形路径(heartShape) : 使用 Three.Shape() 定义一个心形路径,结合 bezierCurveTo() 创建心形曲线。

  • 粒子行为 :

    • 每个粒子通过 SpriteCanvasMaterial 渲染,附加一个绘制心形的 program

    • 粒子大小随机,范围在 40 至 80。

    • 粒子被添加到 particleCloud

  • 粒子发射器(sparksEmitter) :

    • 使用 SPARKS.SteadyCounter 控制每秒 160 粒子。

    • 初始化器包括:

      • 位置:Position 定义初始点为心形路径点。

      • 生命期:Lifetime 设置粒子存活时间为 0 至 2 秒。

      • 速度:Velocity 定义粒子向下漂移 (0, -50, 10)

    • 行为动作:

      • Age:控制粒子的生命周期。

      • Move:移动粒子。

      • RandomDrift:随机漂移,增强动态效果。

    • 注册回调:

      • created: 每创建一个粒子时更新位置。

      • dead: 粒子死亡后移除。


3. 用户交互

支持鼠标和触摸交互:

  • 鼠标事件 :

    • mousedown: 点击后切换粒子发射器的运行状态(开启/停止)。

    • mousemove: 控制粒子云的旋转角度。

  • 触摸事件 :

    • touchstarttouchmove 控制移动设备上的旋转交互。

通过 targetRotationgroup.rotation.y 实现平滑旋转。


4. 动画更新

animate() 函数使用 requestAnimationFrame() 实现持续更新:

  • 时间路径控制 : timeOnShapePath 变量控制粒子沿心形路径运动,结合 heartShape.getPointAt() 获取路径上的点坐标。

  • 粒子更新: 每一帧调整粒子的位置,使其形成动态效果。

  • 渲染 : 调用 renderer.render() 更新整个场景。


核心亮点

  1. 动态心形路径粒子: 粒子在心形路径上运动,路径通过贝塞尔曲线精确定义。

  2. 三维交互: 利用鼠标、触摸事件调整视角,增强体验感。

  3. 文本渲染: 将 3D 文本和粒子效果结合,提升视觉吸引力。


小结

该代码通过 Three.js 和 Sparks.js 实现了一个视觉效果丰富、互动性强的 3D 粒子场景,展示了动态文本和心形粒子的结合。适合用于情感表达、节日贺卡或动态网页展示场景。

写在后面

我是一只有趣的兔子,感谢你的喜欢!

相关推荐
桂月二二32 分钟前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062062 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb2 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角2 小时前
CSS 颜色
前端·css
浪浪山小白兔3 小时前
HTML5 新表单属性详解
前端·html·html5
lee5763 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579653 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me4 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者4 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架
qq_392794484 小时前
前端缓存策略:强缓存与协商缓存深度剖析
前端·缓存