前端小练习——星辰宇宙(JS没有上限!!!)

前端小练习——星辰宇宙(JS没有上限!!!)

        前言:在刚开始学习前端的时候,我们会学习到前端三件套中的JavaScript,可能那时候读者没有觉得JavaScript这个语言有多么的牛逼,本篇文章将会使用一个炫酷的案例来刷新你对JavaScript这个语言的认知与理解。




✨✨✨这里是秋刀鱼不做梦的BLOG

✨✨✨想要了解更多内容可以访问我的主页
秋刀鱼不做梦-ZEEKLOG博客

先让我们看一下最终的结果:

在开始讲解这个炫酷的案例之前,先让我们了解一下本案例所需的前置知识:

Three.js:一个用于创建和显示3D图形的JavaScript库。代码中导入了Three.js的核心库和轨道控制库(OrbitControls),用于处理3D场景的创建和相机控制。WebGL:用于在网页中绘制3D图形的底层API。Three.js封装了WebGL,使得3D渲染变得更简单。模块化 JavaScript:使用 ES6 的模块导入语法 (import) 引入外部库,使代码结构更加清晰。着色器编程:自定义顶点和片段着色器,通过 onBeforeCompile 方法替换默认着色器,控制点的大小、颜色和运动效果。缓冲几何体:使用 BufferGeometry 来管理大量点的性能,提升渲染效率。动画循环:利用 setAnimationLoop 实现流畅的渲染动画,每帧更新控制器状态和场景渲染。响应式设计:通过 resize 事件监听器,动态调整相机和渲染器的尺寸,以适应浏览器窗口的变化。

        ——在文章的最后,我们给出了实现这个案例的全部代码,读者可以直接复制后在自己的编译器上执行一下!!!

        那么现在正式开始我们的讲解:

目录

1.导入库和清理控制台

2.创建场景和相机

3.创建渲染器

4. 处理窗口大小变化

5. 控制器设置

6. 创建全局uniform变量和点的属性

7. 创建点的顶点数组

 8. 添加围绕的点

9. 创建几何体和材质

10. 创建点云并添加到场景

11. 渲染循环

——全部代码


1.导入库和清理控制台

        开始我们要先导入库和清理控制台:

import * as THREE from "https://cdn.skypack.dev/[email protected]"; // 导入三维模型库 import { OrbitControls } from "https://cdn.skypack.dev/[email protected]/examples/jsm/controls/OrbitControls"; // 导入轨道控制库 console.clear(); // 清除控制台 

        注释:这部分代码导入了Three.js库及其轨道控制功能,并清理了控制台,以便后续输出信息更清晰。

2.创建场景和相机

        在导入库和清理控制台后,我们就需要创建场景和相机:

let scene = new THREE.Scene(); // 创建场景 scene.background = new THREE.Color(0x160016); // 设置场景背景颜色 let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000); // 创建相机 camera.position.set(0, 4, 21); // 设置相机位置 

        注释:这部分代码创建了一个场景和一个透视相机,设置了相机的位置和背景颜色,为后续的渲染准备基础环境。

3.创建渲染器

        其后我们需要为其创建一个渲染器:

let renderer = new THREE.WebGLRenderer(); // 创建渲染器 renderer.setSize(innerWidth, innerHeight); // 设置渲染器大小 document.body.appendChild(renderer.domElement); // 把渲染器加入到页面中 

        注释:这里创建了一个WebGL渲染器,并设置其大小为窗口的宽高,最后将渲染器的DOM元素添加到网页中,以显示渲染结果。

4. 处理窗口大小变化

        在创建完渲染器之后,我们需要对后续的窗口的大小的变化进行处理:

window.addEventListener("resize", event => { camera.aspect = innerWidth / innerHeight; // 更新相机的长宽比 camera.updateProjectionMatrix(); // 更新相机的投影矩阵 renderer.setSize(innerWidth, innerHeight); // 设置渲染器大小 }); 

        注释:这一部分代码监听窗口的大小变化,动态调整相机的长宽比和渲染器的大小,确保在窗口大小变化时,场景仍能正确显示。

5. 控制器设置

        处理完窗口大小的变化之后,我们就需要对控制器进行设置了!

let controls = new OrbitControls(camera, renderer.domElement); // 创建控制器 controls.enableDamping = true; // 开启阻尼效果 controls.enablePan; // 禁用平移(此行未完成) 

        注释:这部分代码创建了一个轨道控制器,允许用户通过鼠标控制相机旋转和缩放,增强用户交互体验。注意,这里 enablePan 需要完整设置。

6. 创建全局uniform变量和点的属性

        接下来让哦我们对全局的uniform变量和点的属性进行设置:

let gu = { time: { value: 0 } }; // 创建全局uniform变量 let sizes = []; // 点的大小数组 let shift = []; // 点的移动数组 let pushShift = () => { // 创建移动函数 shift.push( Math.random() * Math.PI, Math.random() * Math.PI * 2, (Math.random() * 0.9 + 0.1) * Math.PI * 0.1, Math.random() * 0.9 + 0.1 ); // 随机生成位置信息 }; 

        注释:定义了一个全局uniform变量 gu 用于时间控制,并初始化了两个数组 sizesshift 用于存储点的大小和位置信息。同时,定义了一个函数 pushShift,用于生成随机的位移数据。

7. 创建点的顶点数组

        接下来让我们创建点的顶点数组:

let pts = new Array(50000).fill().map(p => { sizes.push(Math.random() * 1.5 + 0.5); // 添加随机大小 pushShift(); // 添加位置信息 return new THREE.Vector3().randomDirection().multiplyScalar(Math.random() * 0.5 + 9.5); // 返回随机方向的点 }); 

        注释:这里创建了一个包含5万个点的数组 pts,每个点都有随机的大小和方向。通过 randomDirection() 方法生成随机方向的向量,模拟球体内的点。

 8. 添加围绕的点

        创建完点的顶点数组之后,让我们添加其围绕的点:       

for (let i = 0; i < 100000; i++) { let r = 10, R = 40; // 定义内外半径 let rand = Math.pow(Math.random(), 1.5); // 生成随机数 let radius = Math.sqrt(R * R * rand + (1 - rand) * r * r); // 计算随机半径 pts.push(new THREE.Vector3().setFromCylindricalCoords(radius, Math.random() * 2 * Math.PI, (Math.random() - 0.5) * 2)); // 生成点 sizes.push(Math.random() * 1.5 + 0.5); // 添加随机大小 pushShift(); // 添加位置信息 } 

        注释:这部分代码在已有的点基础上,再添加10万个点。使用极坐标系生成点,使其在一定范围内均匀分布,丰富了场景中的点的数量和分布。

9. 创建几何体和材质

        接下来让我们设置一下几何体和其材质样式:

let g = new THREE.BufferGeometry().setFromPoints(pts); // 创建几何体 g.setAttribute("sizes", new THREE.Float32BufferAttribute(sizes, 1)); // 设置sizes属性 g.setAttribute("shift", new THREE.Float32BufferAttribute(shift, 4)); // 设置shift属性 let m = new THREE.PointsMaterial({ // 创建点材质 size: 0.125, // 点的大小 transparent: true, // 透明材质 depthTest: false, // 禁用深度测试 blending: THREE.AdditiveBlending, // 添加混合模式 onBeforeCompile: shader => { // 自定义着色器 shader.uniforms.time = gu.time; // 设置uniform变量 shader.vertexShader = ` uniform float time; attribute float sizes; attribute vec4 shift; varying vec3 vColor; ${shader.vertexShader} `; // 修改顶点着色器 // 更新点的大小、颜色和移动逻辑 shader.vertexShader = shader.vertexShader .replace(`gl_PointSize = size;`, `gl_PointSize = size * sizes;`) .replace(`#include <color_vertex>`, `#include <color_vertex> float d = length(abs(position)/vec3(40.,10.,40)); d=clamp(d,0.,1.); vColor = mix(vec3(227.,155.,0.),vec3(100.,50.,255.),d)/255.;`) .replace(`#include <begin_vertex>`, `#include <begin_vertex> float t = time; float moveT = mod(shift.x + shift.z * t,PI2); float moveS = mod(shift.y + shift.z * t,PI2); transformed += vec3(cos(moveS) * sin(moveT), cos(moveT), sin(moveS) * sin(moveT)) * shift.w;`); // 修改片元着色器 shader.fragmentShader = ` varying vec3 vColor; ${shader.fragmentShader} `.replace(`#include <clipping_planes_fragment>`, `#include <clipping_planes_fragment> float d = length(gl_PointCoord.xy - 0.5);`) .replace(`vec4 diffuseColor = vec4( diffuse, opacity );`, `vec4 diffuseColor = vec4( vColor, smoothstep(0.5, 0.1, d));`); } }); 

        注释:在这部分代码中,创建了一个缓冲几何体并设置了点的大小和移动信息。还定义了一个点材质,使用自定义着色器来控制点的大小、颜色和移动效果,提供了动态的视觉效果。

10. 创建点云并添加到场景

        接下来让我们创建点云并添加到场景:

let p = new THREE.Points(g, m); // 创建点云 p.rotation.order = "ZYX"; // 设置旋转顺序 p.rotation.z = 0.2; // 设置初始旋转角度 scene.add(p); // 将点云添加到场景中 

        注释:这里将创建的几何体和材质结合成一个点云对象,并设置其旋转顺序和初始旋转角度,最后将其添加到场景中以进行渲染。

11. 渲染循环

        最后让我们渲染一下环境:

let clock = new THREE.Clock(); // 创建时钟对象 renderer.setAnimationLoop(() => { // 设置渲染循环 controls.update(); // 更新控制器 let t = clock.getElapsedTime() * 0.5; // 获取流逝时间 gu.time.value = t * Math.PI; // 更新时间 p.rotation.y = t * 0.05; // 更新点云的旋转角度 renderer.render(scene, camera); // 渲染场景和相机 }); 

        注释:最后,这段代码设置了渲染循环,利用时钟对象控制动画的时间,并不断更新控制器和点云的旋转,最终渲染场景。

        通过上述的分层讲解之后,我们就大致的了解每一步都是如何操作并且为什么这么操作的了,为了使读者能更好的理解上述流程,这里我们在进行总结一下:

导入库:使用Three.js库和OrbitControls模块,准备进行三维场景的创建和交互。场景和相机设置:创建一个三维场景并设置背景颜色。创建透视相机,设定相机的位置,准备从特定角度观察场景。渲染器初始化:创建WebGL渲染器,设置其大小与窗口相同,并将其添加到网页中以显示内容。窗口大小自适应:添加事件监听器,以确保在窗口大小变化时,自动调整相机的长宽比和渲染器的大小,保持渲染效果。控制器设置:创建轨道控制器,允许用户通过鼠标控制相机的旋转和缩放,增强用户交互体验。全局变量和点属性初始化:定义用于控制动画的全局变量和点的大小、位移数组,准备生成点的运动效果。点的生成:生成大量随机位置的点,包括中心球体内的点和周围分布的点,以增加视觉复杂度。几何体和材质设置:创建缓冲几何体,设置点的大小和位移信息。定义点的材质,使用自定义着色器来控制点的大小、颜色和移动效果。点云创建和添加到场景:将几何体和材质组合成点云对象,并设置初始旋转,最后将其添加到场景中以进行渲染。渲染循环:使用时钟对象进行动画控制,在每一帧中更新控制器、点云的旋转和动画时间,并渲染场景。

        ——至此,我们就讲解完了上述案例的所以步骤了!!!

——全部代码

        最后在让我们看一下最终的效果,(JavaScript没有极限!!!)

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>秋刀鱼不做梦</title> <style> body { overflow: hidden; margin: 0; } </style> </head> <body> <script type="module"> // 导入三维模型库 import * as THREE from "https://cdn.skypack.dev/[email protected]"; // 导入轨道控制库 import { OrbitControls } from "https://cdn.skypack.dev/[email protected]/examples/jsm/controls/OrbitControls"; // 清除控制台 console.clear() // 创建场景 let scene = new THREE.Scene() // 设置场景背景颜色 scene.background = new THREE.Color(0x160016) // 创建相机 let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000) // 设置相机位置 camera.position.set(0, 4, 21) // 创建渲染器 let renderer = new THREE.WebGLRenderer() // 设置渲染器大小 renderer.setSize(innerWidth, innerHeight) // 把渲染器加入到页面中 document.body.appendChild(renderer.domElement) // 监听窗口大小变化事件 window.addEventListener("resize", event => { camera.aspect = innerWidth / innerHeight camera.updateProjectionMatrix() renderer.setSize(innerWidth, innerHeight) }) // 创建控制器 let controls = new OrbitControls(camera, renderer.domElement) // 开启阻尼效果 controls.enableDamping = true // 禁用面板 controls.enablePan // 创建全局uniform let gu = { time: { value: 0 } } // 创建点的大小数组和移动数组 let sizes = [] let shift = [] // 创建移动函数 let pushShift = () => { shift.push( Math.random() * Math.PI, Math.random() * Math.PI * 2, (Math.random() * 0.9 + 0.1) * Math.PI * 0.1, Math.random() * 0.9 + 0.1 ) } // 创建点的顶点数组(中间的球体) // 创建一个长度为5万的数组pts并y用Array.prototype.map()方法遍历数组并对每个元素进行操作 let pts = new Array(50000).fill().map(p => { // 每次遍历中,会向sizes数组中添加一个随机大小 sizes.push(Math.random() * 1.5 + 0.5) // 调用pushShift()函数添加位置信息,并返回一个随机方向的THREE.Vector对象 pushShift() return new THREE.Vector3().randomDirection().multiplyScalar(Math.random() * 0.5 + 9.5) // }) // 添加更多的点(旁边围绕的) // 先循环生成十万个点 // 每次循环中生成一个随机数rand,再生成一个随机半径radius for (let i = 0; i < 100000; i++) { let r = 10, R = 40; let rand = Math.pow(Math.random(), 1.5); let radius = Math.sqrt(R * R * rand + (1 - rand) * r * r); // 使用new THREE.Vector3().setFromCylindricalCoords()生成一个点。 pts.push(new THREE.Vector3().setFromCylindricalCoords(radius, Math.random() * 2 * Math.PI, (Math.random() - 0.5) * 2)); sizes.push(Math.random() * 1.5 + 0.5); pushShift(); } // 生成一个点g,同时将点的大小和位置信息添加到sizes和shift数组中 let g = new THREE.BufferGeometry().setFromPoints(pts) // 创建了一个缓冲几何体并设置sizes和shift属性 // 注意这里的F要大写Float32BufferAttribute g.setAttribute("sizes", new THREE.Float32BufferAttribute(sizes, 1)) g.setAttribute("shift", new THREE.Float32BufferAttribute(shift, 4)) // 创建点材质 let m = new THREE.PointsMaterial({ // 表示点的大小 size: 0.125, // 设置材质为透明 transparent: true, // 表示禁用深度测试,使点可以叠加 depthTest: false, // 使用假发混合模式 blending: THREE.AdditiveBlending, // 在材质编译之前修改颜色器,在这里,它用来替换顶点着色器和片元着色器,添加uniform // 和attribute,以鸡自定义颜色和移动 onBeforeCompile: shader => { shader.uniforms.time = gu.time // 首先,它为着色器设置了一个uniform变量time,该变量是在点材质中定义的,用来追踪时间 // 然后它定义了两个attribute变量sizes和shift,这两个变量是在缓冲几何体中定义的,用来控制点的大小和移动 // 最后使用replace方法来替换顶点着色器中的代码 shader.vertexShader = ` uniform float time; attribute float sizes; attribute vec4 shift; varying vec3 vColor; ${shader.vertexShader} ` // 注意上面的 ` 不要漏掉了 // 使用replace来替换着色器中的代码 // 更新点的大小 .replace( `gl_PointSize = size;`, `gl_PointSize = size * sizes;` ) // 更新点的颜色 .replace( `#include <color_vertex>`, `#include <color_vertex> float d = length(abs(position)/vec3(40.,10.,40)); d=clamp(d,0.,1.); vColor = mix(vec3(227.,155.,0.),vec3(100.,50.,255.),d)/255.;` ) // 记得加上分号 // 更新点的移动 .replace( `#include <begin_vertex>`, `#include <begin_vertex> float t = time; float moveT = mod(shift.x + shift.z * t,PI2); float moveS = mod(shift.y + shift.z * t,PI2); transformed += vec3(cos(moveS) * sin(moveT),cos(moveT),sin(moveS)*sin(moveT)) * shift.w; ` ) // 修改片元着色器,用来让点的边缘更加圆滑 // 首先,定义一个varying变量vColor,这个变量是在顶点着色器中定义的,用来传递点的颜色到片段着色器 // 然后使用replace方法来替换片段着色器的代码 shader.fragmentShader = ` varying vec3 vColor; ${shader.fragmentShader} `.replace( `#include <clipping_planes_fragment>`, `#include <clipping_planes_fragment> float d = length(gl_PointCoord.xy - 0.5); ` ).replace( // 记得加上空格 `vec4 diffuseColor = vec4( diffuse, opacity );`, `vec4 diffuseColor = vec4( vColor, smoothstep(0.5, 0.1, d)/* * 0.5+0.5*/);` ); } }) // 创建点云并将其添加到场景中,并设置渲染循环 let p = new THREE.Points(g, m) // 旋转顺序为"ZYX" p.rotation.order = "ZYX" // 旋转角度 0.2 p.rotation.z = 0.2 // 把对象(p)添加到场景(scene)中 scene.add(p) // 创建一个时钟对象clock let clock = new THREE.Clock() // 渲染循环,每次循环中会更新控制器,更新p的旋转角度,更新时间 renderer.setAnimationLoop(() => { // 更新控制器 controls.update() // 获取时钟对象(clock)的已经流逝的时间(t)并将他乘0.5 // 先把时钟关了 let t = clock.getElapsedTime() * 0.5 // 将gu.time.value 设置为t*Math.PI gu.time.value = t * Math.PI // 将对象(p)的旋转角度y设置为t*0.05 p.rotation.y = t * 0.05 // 渲染场景(scene)和相机(camera) renderer.render(scene, camera) }) </script> </body> </html>


以上就是本篇文章的全部内容了!!!

Read more

前端大数据导出优化:解决Chrome内存崩溃的实战方案

前端大数据导出优化:解决Chrome内存崩溃的实战方案

个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[[email protected]] 📱个人微信:15279484656 🌐个人导航网站:www.forff.top 💡座右铭:总有人要赢。为什么不能是我呢? * 专栏导航: 码农阿豪系列专栏导航 面试专栏:收集了java相关高频面试题,面试实战总结🍻🎉🖥️ Spring5系列专栏:整理了Spring5重要知识点与实战演练,有案例可直接使用🚀🔧💻 Redis专栏:Redis从零到一学习分享,经验总结,案例实战💐📝💡 全栈系列专栏:海纳百川有容乃大,可能你想要的东西里面都有🤸🌱🚀 目录 * 前端大数据导出优化:解决Chrome内存崩溃的实战方案 * 引言 * 问题分析 * 1. 为什么 Chrome 会崩溃,而 QQ 浏览器正常? * 2. 常见崩溃场景

详细教程:如何从前端查看调用接口、传参及返回结果(附带图片案例)

详细教程:如何从前端查看调用接口、传参及返回结果(附带图片案例)

目录 1. 打开浏览器开发者工具 2. 使用 Network 面板 3. 查看具体的API请求 a. Headers b. Payload c. Response d. Preview e. Timing 4. 实际操作步骤 5. 常见问题及解决方法 a. 无法看到API请求 b. 请求失败 c. 跨域问题(CORS) 作为一名后端工程师,理解前端如何调用接口、传递参数以及接收返回值是非常重要的。下面将详细介绍如何通过浏览器开发者工具(F12)查看和分析这些信息,并附带图片案例帮助你更好地理解。 1. 打开浏览器开发者工具 按下 F12 或右键点击页面选择“检查”可以打开浏览器的开发者工具。常用的浏览器如Chrome、Firefox等都内置了开发者工具。下面是我选择我的一篇文章,打开开发者工具进行演示。 2. 使用

实战干货】打破次元壁:如何实现 Web 端与 AutoCAD 桌面端的双向通信与自动化绘图

前言 在工程建设与制造业数字化转型的浪潮中,我们经常面临一个架构难题:业务流在 Web 端(SaaS 系统、AI 生成内容),而生产流在桌面端(AutoCAD、Revit)。 如何将 Web 端生成的数据(如设计说明、BOM 表、AI 生成的布局方案)无缝传输到 AutoCAD 并自动生成图纸?传统的做法是“导出 Excel/JSON -> 人工打开 CAD -> 导入插件”,效率低下且割裂。 本文将分享我在最近一个项目中采用的**“本地伴随服务(Local Sidecar Server)”**技术方案。通过在 AutoCAD 插件内部嵌入轻量级 Web Server,实现了 Web 页面点击按钮,

【踩坑记录】使用 Layui 框架时解决 Unity WebGL 渲染在 Tab 切换时黑屏问题

【踩坑记录】使用 Layui 框架时解决 Unity WebGL 渲染在 Tab 切换时黑屏问题

【踩坑记录】使用 Layui 框架时解决 Unity WebGL 渲染在 Tab 切换时黑屏问题 在开发 Web 应用时,尤其是集成了 Unity WebGL 内容的页面,遇到一个问题:当 Unity WebGL 渲染内容嵌入到一个 Tab 中时,切换 Tab 后画面会变黑,直到用户点击黑屏区域,才会恢复显示。 这个问题通常是因为 Unity 渲染在 Tab 切换时被暂停或未能获得焦点所致。 在本文中,我们将介绍如何在使用 Layui 框架时,通过监听 Tab 切换事件并强制 Unity WebGL 渲染恢复,来解决这一问题。 1. 问题描述 当 Unity WebGL 内容嵌入到页面中的多个