自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

全網(wǎng)瘋傳的前端量子糾纏效果,源碼來了!

開發(fā) 前端
作者使用 window.screenLeft、window.screenTop、window.innerWidth和 window.innerHeight 這些屬性來計(jì)算立方體的位置和大小信息,通過 localstorage 來在不同窗口之間共享不同的位置信息。

昨天,很多群里都在瘋傳一個(gè)視頻,視頻演示了純前端實(shí)現(xiàn)的“量子糾纏”效果,不少前端er表示:“前端白學(xué)了”。

圖片圖片

視頻作者昨晚開源一個(gè)簡(jiǎn)化版的實(shí)現(xiàn)源碼(截止發(fā)文,該項(xiàng)目在 Github 上已獲得超過 1k Star),本文就來看看他是怎么實(shí)現(xiàn)的!

簡(jiǎn)化版

根據(jù)作者的描述,該項(xiàng)目是使用 three.js 和 localStorage 實(shí)現(xiàn)的在同一源上設(shè)置跨窗口的 3D 場(chǎng)景。

圖片圖片

雖然沒有原視頻那么炫酷,但基本原理應(yīng)該差不多。

源碼包含多個(gè)文件,最主要的文件如下:

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

在線體驗(yàn):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 中都寫了點(diǎn)啥。代碼如下:

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;

// // 獲取從一天開始以來的秒數(shù)(以便所有窗口使用相同的時(shí)間)
function getTime () {
	return (new Date().getTime() - today) / 1000.0;
}

if (new URLSearchParams(window.location.search).get("clear")) {
	localStorage.clear();
}
else {	
	// 在某些瀏覽器中避免在實(shí)際點(diǎn)擊URL之前預(yù)加載頁(yè)面內(nèi)容
	document.addEventListener("visibilitychange", () => {
		if (document.visibilityState != 'hidden' && !initialized) {
			init();
		}
	});
  // 確保在窗口完全加載后,只有在頁(yè)面可見時(shí)才執(zhí)行初始化邏輯
	window.onload = () => {
		if (document.visibilityState != 'hidden') {
			init();
		}
	};

  // 初始化操作
	function init () {
		initialized = true;

		// 短時(shí)間內(nèi)window.offsetX屬性返回的值可能不準(zhǔn)確,需要添加一個(gè)短暫的延遲,等待一段時(shí)間后再執(zhí)行相關(guān)操作。
		setTimeout(() => {
			setupScene();
			setupWindowManager();
			resize();
			updateWindowShape(false);
			render();
			window.addEventListener('resize', resize);
		}, 500)	
	}

  // 設(shè)置場(chǎng)景相關(guān)的配置
	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 );
	}

  // 設(shè)置窗口管理器的相關(guān)配置
	function setupWindowManager () {
		windowManager = new WindowManager();
		windowManager.setWinShapeChangeCallback(updateWindowShape);
		windowManager.setWinChangeCallback(windowsUpdated);

		let metaData = {foo: "bar"};

		// 初始化窗口管理器(windowmanager)并將當(dāng)前窗口添加到窗口池中。
		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();

		// 根據(jù)當(dāng)前位置和新位置之間的偏移量以及一個(gè)平滑系數(shù)來計(jì)算出窗口的新位置
		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();


		// 遍歷立方體對(duì)象,并根據(jù)當(dāng)前窗口位置的變化更新它們的位置。
		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);
	}


	// 調(diào)整渲染器大小以適合窗口大小
	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 );
	}
}

這段代碼主要實(shí)現(xiàn)以下幾點(diǎn):

  • 初始化場(chǎng)景和渲染器:在 setupScene 函數(shù)中,設(shè)置了一個(gè)正交相機(jī)、場(chǎng)景和渲染器,并將渲染器的 DOM 元素添加到頁(yè)面中。
  • 初始化窗口管理器:在 setupWindowManager 函數(shù)中,創(chuàng)建了一個(gè)窗口管理器實(shí)例,并初始化了窗口并添加到窗口池中。
  • 更新立方體數(shù)量和位置:通過 updateNumberOfCubes 函數(shù),根據(jù)窗口管理器中窗口的數(shù)量和位置信息,動(dòng)態(tài)創(chuàng)建立方體并根據(jù)窗口位置更新其在場(chǎng)景中的位置。
  • 渲染循環(huán):在 render 函數(shù)中,使用 requestAnimationFrame 不斷循環(huán)渲染場(chǎng)景,并根據(jù)窗口管理器中窗口的位置更新立方體的位置和旋轉(zhuǎn)。
  • 響應(yīng)窗口大小變化:通過 resize 函數(shù),在窗口大小變化時(shí)重新設(shè)置相機(jī)的寬高比和渲染器的大小,以適應(yīng)新的窗口尺寸。

接下來看看最核心的實(shí)現(xiàn):WindowManager,代碼如下:

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

		// 監(jiān)聽 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();
				}
			}
		});

		// 監(jiān)聽當(dāng)前窗口是否即將關(guān)閉
		window.addEventListener('beforeunload', function (e) {
			let index = that.getWindowIndexFromId(that.#id);

			// 從窗口列表中移除當(dāng)前窗口并更新 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;
		}
	}

	// 初始化當(dāng)前窗口(添加元數(shù)據(jù)以將自定義數(shù)據(jù)存儲(chǔ)在每個(gè)窗口實(shí)例中)
	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;

這段代碼定義了一個(gè) WindowManager 類,用于管理窗口的創(chuàng)建、更新和刪除等操作,并將其作為模塊導(dǎo)出。

該類包含以下私有屬性:

  • #windows: 存儲(chǔ)所有窗口的數(shù)組。
  • #count: 記錄窗口的數(shù)量。
  • #id: 當(dāng)前窗口的唯一標(biāo)識(shí)符。
  • #winData: 當(dāng)前窗口的元數(shù)據(jù),包括窗口的形狀、自定義數(shù)據(jù)等。
  • #winShapeChangeCallback: 當(dāng)窗口形狀發(fā)生變化時(shí)調(diào)用的回調(diào)函數(shù)。
  • #winChangeCallback: 當(dāng)窗口列表發(fā)生變化時(shí)調(diào)用的回調(diào)函數(shù)。

該類包含以下公共方法:

  • init(metaData): 初始化當(dāng)前窗口,并添加到窗口列表中。
  • getWindows(): 獲取所有窗口的數(shù)組。
  • getThisWindowData(): 獲取當(dāng)前窗口的元數(shù)據(jù)。
  • getThisWindowID(): 獲取當(dāng)前窗口的標(biāo)識(shí)符。
  • setWinShapeChangeCallback(callback): 設(shè)置窗口形狀變化時(shí)的回調(diào)函數(shù)。
  • setWinChangeCallback(callback): 設(shè)置窗口列表變化時(shí)的回調(diào)函數(shù)。
  • update(): 更新當(dāng)前窗口的形狀信息,并將更新后的窗口列表存儲(chǔ)到本地存儲(chǔ)中。

可以看到,作者使用 window.screenLeft、window.screenTop、window.innerWidth和 window.innerHeight 這些屬性來計(jì)算立方體的位置和大小信息,通過 localstorage 來在不同窗口之間共享不同的位置信息。

當(dāng)新增一個(gè)窗口時(shí),就將其保存到 localstorage 中,每個(gè)窗口使用唯一的 id 進(jìn)行標(biāo)記,并儲(chǔ)存立方體的位置和大小信息。不同瀏覽器窗口都可以獲得所有的窗口信息,以確保實(shí)時(shí)更新。

圖片圖片

當(dāng)窗口的位置,即screenTop、screenLeft 發(fā)生變化時(shí),就更新立方體。

這里就不再詳細(xì)解釋了,可以查看完整源碼:https://github.com/bgstaal/multipleWindow3dScene

責(zé)任編輯:武曉燕 來源: 前端充電寶
相關(guān)推薦

2023-11-24 08:00:42

量子糾纏屏幕坐標(biāo)系

2017-08-01 15:39:34

2021-01-08 10:25:51

編程面試項(xiàng)目

2024-09-11 14:48:00

2019-01-23 17:21:11

量子芯片網(wǎng)絡(luò)

2018-03-12 06:51:05

量子計(jì)算量子糾纏傳統(tǒng)計(jì)算機(jī)

2016-04-01 09:33:56

阿里云量子計(jì)算

2018-11-09 09:15:14

2018-04-25 10:45:07

量子存儲(chǔ)

2025-03-31 09:27:03

2020-04-24 13:03:10

工具開源黑科技

2021-01-14 09:34:35

量子量子網(wǎng)絡(luò)量子通信

2018-10-10 13:40:15

量子芯片超算

2021-11-26 10:12:10

量子AI計(jì)算機(jī)

2022-02-11 15:30:55

量子科學(xué)技術(shù)

2023-12-18 15:08:00

GPTOpenAI泄露

2020-01-18 15:16:11

量子芯片網(wǎng)絡(luò)

2022-01-25 15:27:08

麻省理工計(jì)算機(jī)量子

2023-04-27 15:34:53

量子研究

2018-07-03 09:37:12

量子計(jì)算機(jī)編碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)