您当前的位置:首页 > 电脑百科 > 程序开发 > 开源程序 > 源码

全网疯传的前端量子纠缠效果,源码来了!

时间:2023-11-24 12:13:06  来源:微信公众号  作者:前端充电宝

昨天,很多群里都在疯传一个视频,视频演示了纯前端实现的“量子纠缠”效果,不少前端er表示:“前端白学了”。

全网疯传的前端量子纠缠效果,源码来了!

视频作者昨晚开源一个简化版的实现源码(截止发文,该项目在 Github 上已获得超过 1k Star),本文就来看看他是怎么实现的!

简化版

根据作者的描述,该项目是使用 three.js 和 localStorage 实现的在同一源上设置跨窗口的 3D 场景。

 

全网疯传的前端量子纠缠效果,源码来了!

虽然没有原视频那么炫酷,但基本原理应该差不多。

源码包含多个文件,最主要的文件如下:

  • index.html
  • mAIn.js:主文件
  • WindowManager.js:窗口管理

 

在线体验:https://bgstaal.github.io/multipleWindow3dScene/

源码

index.html 文件中引入了 three.js 的压缩包,以及main.js:

<!DOCTYPE html>
<html lang="en">
  <head>
  	<title>3d example using three.js and multiple windows</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>

这没啥可说的,下面就来看看 main.js 中都写了点啥。代码如下:

import WindowManager from './WindowManager.js'

const t = THREE;
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();
let windowManager;
let initialized = false;

// // 获取从一天开始以来的秒数(以便所有窗口使用相同的时间)
function getTime () {
	return (new Date().getTime() - today) / 1000.0;
}

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)并将当前窗口添加到窗口池中。
		windowManager.init(metaData);

		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;// + i * .2;

			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 );
	}
}

这段代码主要实现以下几点:

  • 初始化场景和渲染器:在 setupScene 函数中,设置了一个正交相机、场景和渲染器,并将渲染器的 DOM 元素添加到页面中。
  • 初始化窗口管理器:在 setupWindowManager 函数中,创建了一个窗口管理器实例,并初始化了窗口并添加到窗口池中。
  • 更新立方体数量和位置:通过 updateNumberOfCubes 函数,根据窗口管理器中窗口的数量和位置信息,动态创建立方体并根据窗口位置更新其在场景中的位置。
  • 渲染循环:在 render 函数中,使用 requestAnimationFrame 不断循环渲染场景,并根据窗口管理器中窗口的位置更新立方体的位置和旋转。
  • 响应窗口大小变化:通过 resize 函数,在窗口大小变化时重新设置相机的宽高比和渲染器的大小,以适应新的窗口尺寸。

 

接下来看看最核心的实现:WindowManager,代码如下:

class WindowManager {
	#windows;
	#count;
	#id;
	#winData;
	#winShapeChangeCallback;
	#winChangeCallback;
	
	constructor () {
		let that = this;

		// 监听 localStorage 是否被其他窗口更改
		addEventListener("storage", (event) => {
			if (event.key == "windows") {
				let newWindows = JSON.parse(event.newValue);
				let winChange = that.#didWindowsChange(that.#windows, newWindows);

				that.#windows = newWindows;

				if (winChange) {
					if (that.#winChangeCallback) that.#winChangeCallback();
				}
			}
		});

		// 监听当前窗口是否即将关闭
		window.addEventListener('beforeunload', function (e) {
			let index = that.getWindowIndexFromId(that.#id);

			// 从窗口列表中移除当前窗口并更新 localStorage
			that.#windows.splice(index, 1);
			that.updateWindowsLocalStorage();
		});
	}

	// 检查窗口列表是否有变化
	#didWindowsChange (pWins, nWins) {
		if (pWins.length != nWins.length) {
			return true;
		}
		else {
			let c = false;

			for (let i = 0; i < pWins.length; i++) {
				if (pWins[i].id != nWins[i].id) c = true;
			}

			return c;
		}
	}

	// 初始化当前窗口(添加元数据以将自定义数据存储在每个窗口实例中)
	init (metaData) {
		this.#windows = JSON.parse(localStorage.getItem("windows")) || [];
		this.#count= localStorage.getItem("count") || 0;
		this.#count++;

		this.#id = this.#count;
		let shape = this.getWinShape();
		this.#winData = {id: this.#id, shape: shape, metaData: metaData};
		this.#windows.push(this.#winData);

		localStorage.setItem("count", this.#count);
		this.updateWindowsLocalStorage();
	}

	getWinShape () {
		let shape = {x: window.screenLeft, y: window.screenTop, w: window.innerWidth, h: window.innerHeight};
		return shape;
	}

	getWindowIndexFromId (id) {
		let index = -1;

		for (let i = 0; i < this.#windows.length; i++) {
			if (this.#windows[i].id == id) index = i;
		}

		return index;
	}

	updateWindowsLocalStorage () {
		localStorage.setItem("windows", JSON.stringify(this.#windows));
	}

	update () {
		let winShape = this.getWinShape();
    
		if (winShape.x != this.#winData.shape.x ||
			winShape.y != this.#winData.shape.y ||
			winShape.w != this.#winData.shape.w ||
			winShape.h != this.#winData.shape.h) {
			
			this.#winData.shape = winShape;

			let index = this.getWindowIndexFromId(this.#id);
			this.#windows[index].shape = winShape;

			if (this.#winShapeChangeCallback) this.#winShapeChangeCallback();
			this.updateWindowsLocalStorage();
		}
	}

	setWinShapeChangeCallback (callback) {
		this.#winShapeChangeCallback = callback;
	}

	setWinChangeCallback (callback) {
		this.#winChangeCallback = callback;
	}

	getWindows () {
		return this.#windows;
	}

	getThisWindowData () {
		return this.#winData;
	}

	getThisWindowID () {
		return this.#id;
	}
}

export default WindowManager;

这段代码定义了一个 WindowManager 类,用于管理窗口的创建、更新和删除等操作,并将其作为模块导出。

该类包含以下私有属性:

  • #windows: 存储所有窗口的数组。
  • #count: 记录窗口的数量。
  • #id: 当前窗口的唯一标识符。
  • #winData: 当前窗口的元数据,包括窗口的形状、自定义数据等。
  • #winShapeChangeCallback: 当窗口形状发生变化时调用的回调函数。
  • #winChangeCallback: 当窗口列表发生变化时调用的回调函数。

 

该类包含以下公共方法:

  • init(metaData): 初始化当前窗口,并添加到窗口列表中。
  • getWindows(): 获取所有窗口的数组。
  • getThisWindowData(): 获取当前窗口的元数据。
  • getThisWindowID(): 获取当前窗口的标识符。
  • setWinShapeChangeCallback(callback): 设置窗口形状变化时的回调函数。
  • setWinChangeCallback(callback): 设置窗口列表变化时的回调函数。
  • update(): 更新当前窗口的形状信息,并将更新后的窗口列表存储到本地存储中。

 

可以看到,作者使用 window.screenLeft、window.screenTop、window.innerWidth和 window.innerHeight 这些属性来计算立方体的位置和大小信息,通过 localstorage 来在不同窗口之间共享不同的位置信息。

当新增一个窗口时,就将其保存到 localstorage 中,每个窗口使用唯一的 id 进行标记,并储存立方体的位置和大小信息。不同浏览器窗口都可以获得所有的窗口信息,以确保实时更新。

全网疯传的前端量子纠缠效果,源码来了!

 

当窗口的位置,即screenTop、screenLeft 发生变化时,就更新立方体。

这里就不再详细解释了,可以查看完整源码:https://github.com/bgstaal/multipleWindow3dScene



Tags:源码   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Spring Boot2.0深度实践 核心原理拆解+源码分析
Spring Boot2.0深度实践:核心原理拆解与源码分析一、引言Spring Boot是一个基于Java的轻量级框架,它简化了Spring应用程序的创建过程,使得开发者能够快速搭建一个可运行的应用...【详细内容】
2024-01-15  Search: 源码  点击:(93)  评论:(0)  加入收藏
玩转Spring各种作用域Bean Scope及源码分析
Spring Scope Bean是Spring框架中用于管理Bean的作用域的机制,它定义了Bean的生命周期和实例化策略。通过合理地选择Bean的作用域,可以优化应用的性能和资源利用率。环境:Sprin...【详细内容】
2024-01-05  Search: 源码  点击:(107)  评论:(0)  加入收藏
全方位深度剖析PHP7底层源码
PHP7是一门流行的Web编程语言,以其易学易用和广泛的应用场景而备受欢迎。本文将为您深入讲解PHP7的特性和优势,包括性能提升、语法改进和新特性等方面,帮助您了解和掌握这门灵...【详细内容】
2023-12-06  Search: 源码  点击:(183)  评论:(0)  加入收藏
源码解密协程队列和线程队列的实现原理
本次来聊一聊 Python 的队列,首先队列是一种特殊的线性表,具有先进先出(FIFO)的特性,这意味着元素的入队顺序和出队顺序是一致的。队列通常用于存储需要按顺序处理的数据,例如任务...【详细内容】
2023-12-05  Search: 源码  点击:(144)  评论:(0)  加入收藏
Linux 内核调度器源码解析:从调度入口到挑选下一个进程
在Linux内核中,调度器(scheduler)扮演着至关重要的角色,决定了哪个进程将获得CPU的执行时间。本文将深入剖析内核中调度器的代码实现,从入口函数开始,一步步分析如何选择下一个要...【详细内容】
2023-11-27  Search: 源码  点击:(267)  评论:(0)  加入收藏
全网疯传的前端量子纠缠效果,源码来了!
昨天,很多群里都在疯传一个视频,视频演示了纯前端实现的“量子纠缠”效果,不少前端er表示:“前端白学了”。视频作者昨晚开源一个简化版的实现源码(截止发文,该项目在 Github 上已...【详细内容】
2023-11-24  Search: 源码  点击:(418)  评论:(0)  加入收藏
Nacos配置中心的Pull原理,附源码
在单体服务时代,关于配置信息,管理一套配置文件即可。而拆分成微服务之后,每一个系统都会有自己的配置,并且都各不相同,有些配置还需要动态改变,以达到动态降级、切流量、扩缩容等...【详细内容】
2023-11-17  Search: 源码  点击:(265)  评论:(0)  加入收藏
如何在GitHub上存储源码并保持同步
GitHub是一个广泛使用的基于云的代码托管平台,它为开发者提供了一个便捷的方式来存储、管理和共享他们的源代码。通过GitHub,开发者可以轻松地与团队成员合作,跟踪代码更改,并保...【详细内容】
2023-11-15  Search: 源码  点击:(231)  评论:(0)  加入收藏
源码安全性:如何保护你的源码免受黑客攻击
在当今数字化时代,源码安全性已成为企业和开发者不可忽视的问题。源码泄漏不仅会导致企业商业机密泄露,还可能导致黑客攻击、恶意代码注入等安全问题。因此,保护源码安全已成为...【详细内容】
2023-11-14  Search: 源码  点击:(178)  评论:(0)  加入收藏
SpringCloud OpenFeign整合Ribbon实现负载均衡及源码分析
负载均衡器在分布式网络中扮演着非常重要的角色。通过负载均衡,可以实现更好的性能和可靠性,同时提高系统的可扩展性和弹性。目前,SpringCloud体系中,主要使用的有两种:Netflix的...【详细内容】
2023-11-09  Search: 源码  点击:(234)  评论:(0)  加入收藏
▌简易百科推荐
全网疯传的前端量子纠缠效果,源码来了!
昨天,很多群里都在疯传一个视频,视频演示了纯前端实现的“量子纠缠”效果,不少前端er表示:“前端白学了”。视频作者昨晚开源一个简化版的实现源码(截止发文,该项目在 Github 上已...【详细内容】
2023-11-24  前端充电宝  微信公众号  Tags:源码   点击:(418)  评论:(0)  加入收藏
深入浅出 OkHttp 源码解析及应用实践
一、MBR分区MBR是Master Boot Record的缩写,是一种旧的分区表格式,用于在硬盘上标识和管理分区。MBR分区表可以标识最多4个主分区或3个主分区和1个扩展分区。2TB的限制是指,使...【详细内容】
2023-05-18  雪竹频道  今日头条  Tags:OkHttp   点击:(338)  评论:(0)  加入收藏
用它就够了!开源的驾驶辅助系统
openpilot介绍openpilot是一个开源的驾驶辅助系统。目前,openpilot 执行自适应巡航控制 (ACC)、自动车道居中 (ALC)、前方碰撞警告 (FCW) 和车道偏离警告 (LDW) 的功能,适用...【详细内容】
2022-11-07  GitHub精选  今日头条  Tags:驾驶辅助   点击:(582)  评论:(0)  加入收藏
七爪源码:使用 NodeJs 观看文件系统
监视文件系统意味着监视特定目录或文件的更改。 有时您可能需要持续观察特定文件或目录的更改。出于这个原因,我们使用像 chokidar 这样的文件系统 Watcher 或内置的 NodeJs...【详细内容】
2022-09-17  庄志炎  今日头条  Tags:NodeJs   点击:(478)  评论:(0)  加入收藏
推荐 5 个开源的 yyds 效率神器
01 Wox:效率神器每次重装系统后,都会重新装一些常用的软件,Wox 这个国产开源免费的软件快捷启动工具是首装的效率工具。在 GitHub 上已经获得了 22k 的 Star。Wox 是一个高效的...【详细内容】
2022-09-16  互联网资讯看板  51CTO  Tags:开源   点击:(414)  评论:(0)  加入收藏
「开源精品」 C# im 聊天通讯架构 FreeIM 支持集群、职责分明、高性能
FreeIM 是什么?FreeIM 使用 websocket 协议实现简易、高性能(单机支持5万+连接)、集群即时通讯组件,支持点对点通讯、群聊通讯、上线下线事件消息等众多实用性功能。 ImCore 已...【详细内容】
2022-09-02  IT狂人日记  今日头条  Tags:FreeIM   点击:(510)  评论:(0)  加入收藏
两款「工作流引擎」快速开发框架源码
推荐两款开源的工作流引擎快速开发框架,该工作流平台轻量简洁、美观快速、可扩展,易学习,能够快速上手进行二次开发。有需要的朋友可以去下载看看。(源码地址在文末)▶ 1:开发环境...【详细内容】
2022-08-23   互联网资讯看板  网易  Tags:框架   点击:(367)  评论:(0)  加入收藏
开源:一款开源的一站式SQL审核查询平台 - Archery
Archey介绍Archery是archer的分支项目,定位于SQL审核查询平台,旨在提升DBA的工作效率,支持多数据库的SQL上线和查询,同时支持丰富的MySQL运维功能,所有功能都兼容手机端操作. 功...【详细内容】
2022-08-10  IT搬砖人    Tags:Archery   点击:(770)  评论:(0)  加入收藏
spring源码解析-IOC容器的基本实现
大纲 容器的基本用法 spring启动过程分析(spring生命周期) bean 的生命周期 IOC核心类总结 常用扩展点容器的基本用法spring 是企业级开发框架, 主要功能有 IOC,AOP,Web,ORM...【详细内容】
2022-08-04  javabus    Tags:IOC容器   点击:(371)  评论:(0)  加入收藏
超低成本!自制linux开发板,全开源
这是一款低成本linux开发板&mdash;&mdash;高性能异构边缘AI视觉开发板。作者参考树莓派A版型,将部分硬件替换。它的成本比树莓派低很多!却不一点比树莓派差!具体介绍如下!我还会...【详细内容】
2022-08-03  嘉立创EDA    Tags:开源   点击:(1078)  评论:(0)  加入收藏
站内最新
站内热门
站内头条