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

这篇具有很好参考价值的文章主要介绍了全网疯传的前端量子纠缠效果,源码来了!。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

原视频如下:

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

体验地址:3d example using three.js and multiple windows

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

简化版

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

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

把源码克隆到本地,用 Live Server 启动一下,简化版的效果是这样的:

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

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

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

  • index.html

  • main.js:主文件

  • WindowManager.js:窗口管理

源码

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.screenLeftwindow.screenTopwindow.innerWidthwindow.innerHeight这些属性来计算立方体的位置和大小信息,通过localstorage来在不同窗口之间共享不同的位置信息。

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

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

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

这里就不再详细解释了,可以查看完整源码:https://github.com/bgstaal/multipleWindow3dScene文章来源地址https://www.toymoban.com/news/detail-754468.html

到了这里,关于全网疯传的前端量子纠缠效果,源码来了!的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 前端接口联调时传的参数是array数组形式处理

    情景:使用van-uolaoder组件上传多个图片后调用保存按钮,这时由于存在多张图片,调用接口时前端传的参数就是数组形式。(前端传的数组要先转为字符串) 处理方法是: 1. 先确定后端使用什么注解,如果是@requestBody注解,则请求体内容类型一般要为application/json(字符串格

    2023年04月26日
    浏览(42)
  • java_web接收前端传的excel文件读取数据

    #本次做一个将患者数据导入到某个模块的功能,前期集成的代码时不时出现异常,本次进行修改记录 前端代码

    2024年02月04日
    浏览(51)
  • 前端接收后端传的文件流并下载解决乱码问题

    两种情况: 1.如果这个接口是get的请求: 后端返回文件流,前端可能会导出txt或者excel的时候,里面的中文会出现乱码 就可以直接通过以下方式直接下载: window.location.href = \\\"请求文件流的接口地址\\\" 2.如果这个接口是post的请求:         因为存在多种场景,可能需要通过

    2024年02月11日
    浏览(48)
  • 前端框架Layui实现动态表格效果用户管理实例(对表格进行CRUD操作-附源码)

    目录 一、前言 1.什么是表格 2.表格的使用范围 二、案例实现 1.案例分析 ①根据需求找到文档源码 ②查询结果在实体中没有该属性 2.dao层编写 ①BaseDao工具类 ②UserDao编写 3.Servlet编写 ①R工具类的介绍 ②Useraction编写 4.jsp页面搭建 ①userManage.jsp  ②userEdit.jsp ③userManage.js ④us

    2024年02月16日
    浏览(48)
  • 全网最详细的TVBOX带会员版二开图文教程:一、tvbox如意前端后台搭建教程;二、tvbox后台配置教程;三、tvbox源码Android Studio配置修改教程;四、tvbox源码as打包教程

    一、TVBOX管理后台源码网站搭建; 搭建测试环境:PHP7.0、Nginx、按照好宝塔、配置解析好域名 1、请将下载好的tvbox源码压缩包进行解压,解压后得到的问题件如图所示 2、请将压缩包内的如图所指文件(1)上传到你的网站跟目录(记得是网站跟目录)并解压  3、请把以下文件

    2024年02月12日
    浏览(52)
  • 这怕不是全网最小的深度相机?近距离测距相机OAK-D-SR来了!

    编辑:OAK中国 首发:oakchina.cn 喜欢的话,请多多👍⭐️✍ 内容可能会不定期更新,官网内容都是最新的,请查看首发地址链接。 Hello,大家好,这里是OAK中国,我是助手君。 从去年就开始期待的OAK-D-SR,我们可算是成功跑起来了。最新的这批产品在设计和功能上基本达到我

    2024年02月11日
    浏览(47)
  • 全网最详细的鼠标点击效果与禁用页面缩小放大

    🌏 博客首页: 水香木鱼 📑 文章摘要: 鼠标点击效果   vue2 💌 春波寄语: 故木秀于林,风必摧之;堆出于岸,流必湍之;行高于人,众必非之。 在public的index.html 内 body 下 添加即可。 在public的index.html 内 body 下 添加即可。 在utils文件夹下创建 coreSocialistValues.js 文件,并

    2024年01月23日
    浏览(60)
  • 3dmax怎么渲染?超详细的效果图渲染VR文字版教程来了

    第一节 1、VRay调用 VRay在使用之前,需要调用一下,具体方法如下: 按F10键,出现对话框(如下图) 单击渲染器右侧的下拉菜单,从下拉菜单中选择VRay5即可。 VRay调用后我们还要调个渲染流程,渲染流程很重要,千万千万不要调错,调错一步就有可能渲染不出图来。具体调

    2024年02月04日
    浏览(43)
  • 最强AI软件教程来了!教你如何使用stable diffusion快速出景观建筑效果图

    Stable Diffusion效果图教程 要说哪款AI软件最适合建筑设计类?那必然是midjourney和Stable Diffusion!之前我们也看到了他们生成的图虽然很漂亮,但现有阶段md生成图对我们建筑景观类把控不是很友好,而且md属于收费软件,所以今天我们主要介绍Stable Diffusion(后简称SD)的一些用法。

    2024年04月10日
    浏览(86)
  • 史上最全前端八股文来了

    由于最近比较忙活没时间学习新东西,现在得空想着能不能好好整理出一些有用的东西,让记忆深刻一点,免得到时候实习找工作面试的时候一问三不知,也希望大家能指正出错误和对大家有点帮助,一起进步,加油奥里给!!! 那么废话不多说直接进入正题,如果觉得可以

    2024年02月08日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包