前端量子纠缠源码公布!效果炸裂!

2023-11-28 17:02:36 浏览数 (1)

近日见闻

  1. RuoYi-Vue-Plus 发布 4.8.2 正式进入维护状态,由于 springboot 2.X 与 vue 2.X 官方均宣布停止维护,故而 框架 4.X 版本 进入维护状态 (只处理问题不更新功能),停止维护时间预计: 2024 年 6-10 月具体根据使用人数动态决定,此版本已经相当稳定 即便不更新功能也不影响使用。如果依旧选择使用 jdk8 或者 jdk11 可以放心使用此版本,如果希望使用 jdk17 或者 jdk21 可以选择使用 5.X 分支。
  2. Spring Boot 3.2.0 现已发布,更新版本添加了大量新功能和改进。

前端量子纠缠效果实现

最近,程序员群里流行一个比较火的一个视频,尤其是很多前端程序员都不由的赞叹此视频。视频内容是一个完全由前端完成的类似"量子纠缠"效果的项目。看完之后很多人表示前端白学了。原视频如下,作者为nonfigurativ。想象一下,当你在多个显示器前操作,每个显示器就像是一个窗口,通过这些窗口你可以观察到同一个3D场景的不同部分,而这一切都实现了无缝连接。这不仅仅是技术上的创新,更是用户体验上的大跃进!

先直接看视频:

看完后是不是会由衷的说出那两个表示惊叹的词语。没错效果就是这么炫酷。

作者是这么介绍自己的:独立艺术家、程序员和研究员。生成艺术、互动装置和拓展思维的技术。前@voidtm的创意总监。禅宗男士。

长这样的:

那么作者也快速的开源了简易版代码,仓库地址:https://github.com/bgstaal/multipleWindow3dScene

项目中是这么介绍的:

  • 使用three.js和localStorage跨多个窗口设置3D场景
  • 一个简单的例子展示了如何使用three.js和localStorage在同一源上跨窗口设置一个3D场景。代码应该是不言自明的。

那么我们如何在本地运行查看效果呢,首先Git clone仓库到本地,使用编辑器打开,运行你自己的live server插件即可。

看看我这边运行的效果,虽然只是一个正方体,但还是很炫酷啊!

然后看看代码部分:

代码语言:javascript复制
<!DOCTYPE html>
<html lang="en">
<head>
 <title>跨多个窗口设置3D场景</title>
 <script type="text/javascript" src="three.r124.min.js"></script>
 <style type="text/css">
  
  *
  {
   margin: 0;
   padding: 0;
  }

 </style>
</head>
<body>
 
 <script type="module" src="main.js"></script>
</body>
</html>

index文件引入了three.js的压缩包,版本号是124。Three.js是一个强大的3D图形库,用于在网页上创建和显示3D图形。以及main.js,实际的3D场景和逻辑都包含在main.js文件中。并且定义了所有元素(*)的默认边距(margin)和内边距(padding)都是0。

说明关键在于main.js,这里借助AI进行详细的解释,我们一块看看代码:

代码语言:javascript复制
// 引入 WindowManager 类
import WindowManager from './WindowManager.js'

// 设置 Three.js 的别名为 t 用于简化代码
const t = THREE;
// 定义一些用于3D场景的变量
let camera, scene, renderer, world;
let near, far;
// 获取设备的像素比率
let pixR = window.devicePixelRatio ? window.devicePixelRatio : 1;
// 用于存储所有立方体的数组
let cubes = [];
// 场景偏移目标和当前场景偏移量
let sceneOffsetTarget = {x: 0, y: 0};
let sceneOffset = {x: 0, y: 0};

// 初始化日期为今天,并清零小时,分钟,秒和毫秒
let today = new Date();
today.setHours(0);
today.setMinutes(0);
today.setSeconds(0);
today.setMilliseconds(0);
// 将今天的日期转换为时间戳
today = today.getTime();

// 获取内部时间
let internalTime = getTime();
// 声明 WindowManager 变量
let windowManager;
// 初始化标志变量
let initialized = false;

// 定义一个获取从当天开始的秒数的函数,确保所有窗口都使用相同的时间
function getTime() {
 return (new Date().getTime() - today) / 1000.0;
}

// 如果URL参数包含 clear,则清空 localStorage
if (new URLSearchParams(window.location.search).get("clear")) {
 localStorage.clear();
} else {
 // 一些浏览器在实际点击 URL 之前会预加载页面内容,所以需要特殊处理
 document.addEventListener("visibilitychange", () => {
  if (document.visibilityState != 'hidden' && !initialized) {
   init();
  }
 });

 window.onload = () => {
  if (document.visibilityState != 'hidden') {
   init();
  }
 };

 // 初始化函数,设置场景、窗口管理器、调整大小、更新窗口形状并开始渲染
 function init() {
  initialized = true;

  // 添加短暂延时,因为 window.offsetX 在短时间内可能报告错误的值
  setTimeout(() => {
   setupScene();
   setupWindowManager();
   resize();
   updateWindowShape(false);
   render();
   window.addEventListener('resize', resize);
  }, 500) 
 }

 // 设置场景函数,定义相机、场景、渲染器和世界对象
 function setupScene() {
  // 创建一个正交相机
  camera = new t.OrthographicCamera(0, 0, window.innerWidth, window.innerHeight, -10000, 10000);
  
  camera.position.z = 2.5;
  near = camera.position.z - .5;
  far = camera.position.z   0.5;

  scene = new t.Scene();
  scene.background = new t.Color(0.0);
  scene.add(camera);

  renderer = new t.WebGLRenderer({antialias: true, depthBuffer: true});
  renderer.setPixelRatio(pixR);
     
    world = new t.Object3D();
  scene.add(world);

  renderer.domElement.setAttribute("id", "scene");
  document.body.appendChild(renderer.domElement);
 }

 // 设置窗口管理器函数
 function setupWindowManager() {
  windowManager = new WindowManager();
  windowManager.setWinShapeChangeCallback(updateWindowShape);
  windowManager.setWinChangeCallback(windowsUpdated);

  // 可以添加自定义元数据到每个窗口实例
  let metaData = {foo: "bar"};

  // 初始化窗口管理器并将此窗口添加到集中的窗口池中
  windowManager.init(metaData);

  // 最初调用更新窗口函数(稍后将由 win 更改回调调用)
  windowsUpdated();
 }

 // 更新窗口函数,用于更新立方体数量
 function windowsUpdated() {
  updateNumberOfCubes();
 }

 // 更新立方体数量的函数
 function updateNumberOfCubes() {
  // 获取所有窗口
  let wins = windowManager.getWindows();

  // 移除所有立方体
  cubes.forEach((c) => {
   world.remove(c);
  })

  cubes = [];

  // 根据当前窗口设置添加新立方体
  for (let i = 0; i < wins.length; i  ) {
   let win = wins[i];

   let c = new t.Color();
   c.setHSL(i * .1, 1.0, .5);

   let s = 100   i * 50;
   let cube = new t.Mesh(new t.BoxGeometry(s, s, s), new t.MeshBasicMaterial({color: c , wireframe: true}));
   cube.position.x = win.shape.x   (win.shape.w * .5);
   cube.position.y = win.shape.y   (win.shape.h * .5);

   world.add(cube);
   cubes.push(cube);
  }
 }

 // 更新窗口形状的函数
 function updateWindowShape(easing = true) {
  // 存储实际偏移量
  sceneOffsetTarget = {x: -window.screenX, y: -window.screenY};
  if (!easing) sceneOffset = sceneOffsetTarget;
 }

 // 渲染函数,更新和渲染场景
 function render() {
  let t = getTime();

  windowManager.update();

  // 计算新位置并应用平滑效果
  let falloff = .05;
  sceneOffset.x = sceneOffset.x   ((sceneOffsetTarget.x - sceneOffset.x) * falloff);
  sceneOffset.y = sceneOffset.y   ((sceneOffsetTarget.y - sceneOffset.y) * falloff);

  // 设置世界位置到偏移量
  world.position.x = sceneOffset.x;
  world.position.y = sceneOffset.y;

  let wins = windowManager.getWindows();

  // 遍历所有立方体并更新它们的位置和旋转
  for (let i = 0; i < cubes.length; i  ) {
   let cube = cubes[i];
   let win = wins[i];
   let _t = t;

   let posTarget = {x: win.shape.x   (win.shape.w * .5), y: win.shape.y   (win.shape.h * .5)}

   cube.position.x = cube.position.x   (posTarget.x - cube.position.x) * falloff;
   cube.position.y = cube.position.y   (posTarget.y - cube.position.y) * falloff;
   cube.rotation.x = _t * .5;
   cube.rotation.y = _t * .3;
  };

  // 渲染场景
  renderer.render(scene, camera);
  // 请求动画帧
  requestAnimationFrame(render);
 }

 // 调整渲染器以适应窗口大小的函数
 function resize() {
  let width = window.innerWidth;
  let height = window.innerHeight
  
  camera = new t.OrthographicCamera(0, width, 0, height, -10000, 10000);
  camera.updateProjectionMatrix();
  renderer.setSize(width, height);
 }
}

这段代码是一个用于在同一来源的多个窗口中设置和同步3D场景的JavaScript模块示例。它利用了Three.js图形库来创建3D对象,并通过WindowManager类处理不同窗口间的交互和数据同步。这个模块的主要目的是跨窗口展示和同步立方体的3D图形表示。

实现主要步骤

初始化和设置

代码开始初始化一系列变量,包括Three.js的场景、相机和渲染器。然后,通过getTime函数获取相对于当天开始的时间,这样所有窗口都可以基于相同的时间参考点进行更新。

页面加载和可视状态处理

代码检查页面的URL参数,如果发现clear参数,则会清空localStorage,这是一个可以通过URL触发的重置机制。

接下来,代码监听文档的可见性改变事件(visibilitychange)和页面加载(onload)事件来初始化3D场景。这是为了防止在某些浏览器中,页面内容在用户实际访问URL之前预加载时,可能出现的问题。

初始化3D场景和窗口管理

初始化函数init负责设置场景、窗口管理器、调整渲染器大小以适应窗口,并开始渲染循环。setupScene函数创建了相机、场景、渲染器和3D世界对象,并将渲染器的DOM元素添加到文档体中。

窗口管理器的设置通过setupWindowManager函数完成,它实例化WindowManager,并定义窗口形状变化和窗口更新的回调函数。窗口形状变化用于跟踪和反应窗口位置的移动。

动态3D场景更新

windowsUpdatedupdateNumberOfCubes函数一起工作,根据窗口的数量和状态,动态添加或移除立方体对象。立方体的颜色和大小随着它们在窗口数组中的位置而变化,提供了一种视觉上的区分。

窗口形状更新通过调整sceneOffset来实现,这样可以使3D世界的位置与窗口在屏幕上的位置相匹配,从而实现跨窗口的立体效果。

渲染循环

render函数是这段代码的核心,它不断地更新时间,调用windowManager.update()来处理窗口的变化,并应用新的位置和旋转到立方体对象。通过requestAnimationFrame来创建一个平滑的动画效果。

窗口尺寸调整

最后,resize函数确保当浏览器窗口大小改变时,相机和渲染器也相应地更新,以维持3D场景的正确透视和比例。

到这大致对于这个效果有了解了,但想实现自己的效果,比如改成其他形状,还需要不断学习,这边尝试借助ai编码更改还是有些问题,果然学无止境

0 人点赞