当前位置: 首页 > news >正文

Three.js 多浏览器窗口连接教程

多浏览器窗口连接 ·Mult Window· ▶ 在线运行案例

  • 案例合集:三维可视化功能案例(threehub.cn)
  • 开源仓库github地址:https://github.com/z2586300277/three-cesium-examples
  • 400个案例代码:网盘链接

你将学到什么

  • requestAnimationFrame渲染循环与resize自适应

效果说明

Three.js 接第三方库或扩展能力。入口在WindowManager

核心概念

  • CubeTexture六面贴图作scene.backgroundscene.environment供 PBR 材质反射。
  • Reflector/Water基于 renderTarget 的平面反射或动态水面法线。

实现步骤

  • 搭建 Scene / Camera / Renderer 与 OrbitControls
  • rAF 循环中 update 并 render
  • 代码要点

    import * as THREE from 'three'

    confirm("是否打开多窗口?") ? window.open(

    HOST + '#/codeMirror?navigation=ThreeJS&classify=expand&id=multWindowScene',

    '_blank',

    width=800,height=600,left=${Math.random()500},top=${Math.random()200}) : null

    class WindowManager { #windows; #count = localStorage.getItem("count") || 0; #id; #winData; #winShapeChangeCallback; #winChangeCallback;

    constructor() { addEventListener("storage", (event) => { if (event.key == "windows") { let newWindows = JSON.parse(event.newValue); let winChange = this.#didWindowsChange(this.#windows, newWindows); this.#windows = newWindows; if (winChange && this.#winChangeCallback) this.#winChangeCallback(); } });

    window.addEventListener('beforeunload', () => { let index = this.getWindowIndexFromId(this.#id); this.#windows.splice(index, 1); this.updateWindowsLocalStorage(); }); }

    #didWindowsChange(pWins, nWins) { if (pWins.length != nWins.length) return true; return pWins.some((pWin, i) => pWin.id != nWins[i].id); }

    init(metaData) { this.#windows = JSON.parse(localStorage.getItem("windows")) || []; 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() { return { x: window.screenLeft, y: window.screenTop, w: window.innerWidth, h: window.innerHeight }; }

    getWindowIndexFromId(id) { return this.#windows.findIndex(win => win.id == id); }

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

    update() { let winShape = this.getWinShape(); if (Object.values(winShape).some((value, i) => value != Object.values(this.#winData.shape)[i])) { 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; } }

    let camera, scene, renderer, world, near, far, pixR = window.devicePixelRatio || 1, cubes = [], sceneOffsetTarget = { x: 0, y: 0 }, sceneOffset = { x: 0, y: 0 }; let today = new Date().setHours(0, 0, 0, 0), internalTime = (new Date().getTime() - today) / 1000.0, windowManager, initialized = false;

    if (new URLSearchParams(window.location.search).get("clear")) localStorage.clear(); else { document.addEventListener("visibilitychange", () => { if (document.visibilityState != 'hidden' && !initialized) init(); }); window.onload = () => { if (document.visibilityState != 'hidden') init(); };

    function init() { initialized = true; setTimeout(() => { setupScene(); setupWindowManager(); resize(); updateWindowShape(false); render(); window.addEventListener('resize', resize); }, 500) }

    function setupScene() { camera = new THREE.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 THREE.Scene(); scene.background = new THREE.Color(0.0); scene.add(camera); renderer = new THREE.WebGLRenderer({ antialias: true, depthBuffer: true }); renderer.setPixelRatio(pixR); world = new THREE.Object3D(); scene.add(world); renderer.domElement.setAttribute("id", "scene"); document.body.appendChild(renderer.domElement); }

    function setupWindowManager() { windowManager = new WindowManager(); windowManager.setWinShapeChangeCallback(updateWindowShape); windowManager.setWinChangeCallback(updateNumberOfCubes); windowManager.init({ foo: "bar" }); updateNumberOfCubes(); }

    function updateNumberOfCubes() { cubes.forEach((c) => { world.remove(c); }); cubes = []; windowManager.getWindows().forEach((win, i) => { let c = new THREE.Color(); c.setHSL(i * .1, 1.0, .5); let s = 100 + i * 50, cube = new THREE.Mesh(new THREE.BoxGeometry(s, s, s), new THREE.MeshBasicMaterial({ color: c , transparent: true, opacity: 0.5 })); 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 = (new Date().getTime() - today) / 1000.0, falloff = .05; windowManager.update(); sceneOffset.x += (sceneOffsetTarget.x - sceneOffset.x)falloff; sceneOffset.y += (sceneOffsetTarget.y - sceneOffset.y)falloff; world.position.x = sceneOffset.x; world.position.y = sceneOffset.y; cubes.forEach((cube, i) => { let win = windowManager.getWindows()[i], _t = t, posTarget = { x: win.shape.x + (win.shape.w.5), y: win.shape.y + (win.shape.h.5) }; cube.position.x += (posTarget.x - cube.position.x)falloff; 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, height = window.innerHeight; camera = new THREE.OrthographicCamera(0, width, 0, height, -10000, 10000); camera.updateProjectionMatrix(); renderer.setSize(width, height); } }

    完整源码:GitHub

    小结

    • 本文提供多浏览器窗口连接完整 Three.js 源码与在线 Demo,建议先运行案例再改 uniform/参数做二次实验
    • 更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库
http://www.jsqmd.com/news/1108777/

相关文章:

  • 为Chatbox构建端到端加密:从原理到工程实践
  • Sonnet 5能自主用浏览器和终端了,Agent AI的临界点到了
  • 嵌入式EEPROM应用:M95M02与PIC18LF46K40的SPI通信优化
  • 简历自我评价别再写“积极向上“了!实测6款AI工具,3分钟生成HR想看的版本
  • KLayout完整指南:从零开始掌握专业版图设计与验证
  • Claude Code vs Copilot vs Cursor:三款顶级 AI 编程工具的实测对比与场景化选型
  • 三步解锁WeMod Pro:Wand-Enhancer开源增强工具全攻略
  • 一次陪家人看牙的简单记录
  • 新手如何用skills
  • ComfyUI-Manager:3步打造你的AI绘画工作流管理神器
  • 国学语录可用 API 接口(分三类:无需 KEY 免费、平台付费古籍、本地自建)
  • 魔兽争霸3现代化改造指南:让你的经典RTS游戏重获新生
  • 不想数据过第三方服务器?本地开源 AI 网关 OmniRoute,自动调度大模型API
  • Wand-Enhancer终极指南:如何免费解锁WeMod完整功能的5大技巧
  • 题解:洛谷 B4500 [GESP202603 三级] 凯撒密码
  • 儿童近视防控眼科机构咋选择
  • 2026超一线城市小程序开发公司深度评测:定制开发、交付能力与企业口碑全景解析,含零代码SAAS、AI编程、源码定制
  • 5大核心优化技巧:让老旧Android电视流畅播放高清直播的终极方案
  • 从Kali工具使用到EXP开发:安全测试源码分析与实战指南
  • 6款实用AI降重软件推荐,合规改写不踩学术红线
  • 被问到为何中间有一段长达半年的求职空窗期?留学生用积极事实消除疑虑「蒸汽求职分享」
  • 腾讯会议多端接入音视频稳定技术方案
  • 修复WSL2的PATH变量:解决交叉编译RK3506环境问题的首选方案
  • ICM-42688-P与PIC18F2680在运动控制与传感融合中的应用
  • 如何使用C++标准输入流cin读取字符串?
  • 【Vibe Coding从入门到精通】第13篇:团队协作中的Vibe Coding——从个人利器到团队武器
  • 构建小程序全自动安全审计体系:从原理到实践
  • 为什么机电维修师傅都在换 18KV 塑钢头绝缘鞋?轻便防护两不误
  • 2026年中盘点:什么八字排盘软件好用?第三方测评拆到排盘底层
  • OpenCore Legacy Patcher:让旧Mac重获新生,体验最新macOS的终极指南