ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

three.js Inspector 扩展指南:深入解析 Tab 标签页基类与自定义面板开发

three.js Inspector 扩展指南:深入解析 Tab 标签页基类与自定义面板开发 three.js Inspector 扩展指南深入解析 Tab 标签页基类与自定义面板开发【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsthree.js 官方示例中内置了一个可直接叠加到渲染器上的 Inspector性能剖析器UI 套件位于examples/jsm/inspector。本文以其 API 参考文档docs/pages/Tab.html.md为主线深入剖析该套件中所有标签页Performance、Memory、Console、Viewer 等的共同基类Tab的构造函数、选项参数、状态机与生命周期方法并结合 Tab.js、Profiler.js、Inspector.js 等源码讲清楚如何利用Tab派生自定义标签页、内置builtin迷你面板以及拖拽分离成独立窗口的完整机制。读完本文你将能够编写并注册属于自己的 Inspector 扩展标签页。一、Tab 在整个 Inspector 架构中的位置在深入构造函数之前先明确Tab的调用关系。从源码结构看Inspector 的 UI 体系是三层嵌套Inspectorexamples/jsm/inspector/Inspector.js对外暴露的入口类提供addTab()、removeTab()、setActiveTab()等高层 APIProfilerexamples/jsm/inspector/ui/Profiler.js负责管理 DOM 外壳切换按钮、标签栏、内容面板、mini-panel维护tabs字典与activeTabIdTabexamples/jsm/inspector/ui/Tab.js单个标签页的抽象基类负责创建自己的标签按钮与内容容器并实现显示/隐藏、内置builtin迷你化、拖拽分离等行为。在 Inspector.js 的构造函数中七个默认标签页被依次创建并注册const parameters new Parameters( { builtin: true, icon: svg.../svg } ); parameters.hide(); profiler.addTab( parameters ); const viewer new Viewer(); viewer.hide(); profiler.addTab( viewer ); const performance new Performance(); profiler.addTab( performance ); const memory new Memory(); profiler.addTab( memory ); const timeline new Timeline(); profiler.addTab( timeline ); const consoleTab new Console(); profiler.addTab( consoleTab ); const settings new Settings(); profiler.addTab( settings );其中 Performance.js、Memory.js、Console.js、Timeline.js、Viewer.js、Parameters.js 中的各个标签页类均直接以class X extends Tab方式派生可见Tab就是整个 Inspector 扩展生态的基座。二、构造函数new Tab( title, options )与选项参数构造签名如下对应文档 Tab.html 的 Constructor 章节完整实现见 Tab.jsnew Tab( title : string, options : Object )2.1title—— 标签标题标签的显示标题类型为字符串。构造函数会根据标题自动派生标签的内部唯一 IDthis.id title.toLowerCase().replace( /\s/g, - );即标题转小写、空白替换为连字符例如My Stats会得到id my-stats。该 id 同时被用作Profiler.tabs字典的键profiler.tabs[ tab.id ]内容容器的附加 classthis.content.classList.add( \${ this.id }-content )。2.2options—— 选项options为可选对象所有字段均有默认值。构造函数通过options.x ! undefined ? options.x : defaultValue的方式读取因此显式传入undefined或省略都会回退到默认值参数类型默认值说明allowDetachbooleantrue标签是否允许被拖拽分离为独立的浮窗detached windowbuiltinbooleanfalse该标签是否出现在 profiler-toggle 切换按钮中作为内置快捷标签使用iconstringnull当builtin: true时切换按钮上使用的 SVG 图标 HTML三者的作用机制在源码中有明确对应allowDetach直接决定 Profiler.js 的addTab与拖拽逻辑为false时按钮会加上no-detachclass且setupTabDragAndDrop会提前返回、禁用拖拽detachTab方法内部也有if ( tab.allowDetach false ) return;的保护判断Profiler.jsbuiltin决定标签按钮是否加入主面板标签栏非 builtin 标签的按钮被追加进.profiler-tabsbuiltin 标签则走addBuiltinTab流程其按钮.builtin-tab-btn被放进 profiler-toggle 内的.builtin-tabs-containerProfiler.jsicon供 builtin 按钮使用有icon时将其直接写入按钮innerHTML否则退化为取标题首字母大写作为按钮内容。2.3 构造时创建的 DOM 结构构造函数会为每个 Tab 创建一对 DOM 节点并挂到实例上this.button document.createElement( button ); // class: tab-btn this.content document.createElement( div ); // class: profiler-content {id}-contentbutton承载标签标题文本用于点击切换与拖拽分离content是标签页内容的容器所有自定义 UI 都应追加到this.content上。二者真正进入文档树则是在Profiler.addTab()中被分别挂到.profiler-tabs与.profiler-content-wrapper之下。三、Tab 的运行时状态与只读属性构造函数还会初始化一组状态字段Tab.js理解它们对正确使用show/hide/isActive至关重要字段初始值含义_isActivefalse是否处于激活态真正的可读状态由下方 getter 决定isVisibletrue标签是否对用户可见isDetachedfalse标签是否已被拖拽为独立浮窗detachedWindownull分离后的浮窗对象引用builtinButtonnull指向 profiler-toggle 中内置按钮的引用miniContentnull指向 mini-panel 中对应迷你内容容器的引用profilernull所属 Profiler 实例引用addTab时注入onVisibilityChangenull可见性变化回调addTab时被赋值为Profiler.updatePanelSize值得特别注意的isActive访问器Tab.jsget isActive() { if ( this.isDetached this.isVisible ) return true; const isProfilerVisible this.profiler this.profiler.panel.classList.contains( visible ); if ( ! isProfilerVisible ) return false; return this._isActive; }它有两层语义分离态的标签只要isDetached isVisible即使主面板折叠也视为激活因为内容显示在浮窗中常规态的标签主面板不可见没有visibleclass时一律视为非激活只有主面板可见时才返回内部_isActive。因此诸如 Console.js 中! this.isActive判断是否有未读消息、Timeline.js 中仅激活时采样/绘图等逻辑都会自动兼容面板折叠与标签分离两种场景。实例上还暴露了inspector便捷 getterreturn this.profiler.inspector;用于从标签内直接访问 Inspector 主对象。四、生命周期方法init / update / setActive / disposeTab提供了两个默认空实现的可覆写钩子与一组公开方法init( /*inspector*/ ) { } update( /*inspector*/ ) { } dispose() { }它们的调用时机来自 Inspector 的帧循环。在 Inspector.js 的updateTabs()中每一帧都会遍历profiler.tabsconst tabs Object.values( this.profiler.tabs ); for ( const tab of tabs ) { let tabData this.extensionsData.get( tab ); if ( tabData undefined ) { tab.init( this ); // 首次注册时只调用一次 tabData {}; this.extensionsData.set( tab, tabData ); } tab.update( this ); // 每帧调用 }即init每个标签只执行一次首次进入更新循环时update每帧执行一次。派生类通常在init中做一次性 DOM/数据装配在update中按isActive条件刷新数据避免后台标签空耗性能。例如内置的 Parameters.js 在构造函数里就建好List而各性能类标签在update中刷新图表。dispose()默认也为空实现在Inspector.removeTab()中会先调用它再做 DOM 清理Inspector.js派生类如 Extension.js 会用它移除事件监听、释放引用。setActive( isActive )切换激活态并同步 CSS classsetActive( isActive ) { this.button.classList.toggle( active, isActive ); this.content.classList.toggle( active, isActive ); this.isActive isActive; }注意它写的是_isActive通过 setter而读取方应使用isActivegetter 以获得考虑面板可见性与分离态后的真实值。派生类如 ColorGrading.js 与 Console.js通常在覆写时先super.setActive( isActive )再处理自身逻辑如激活时才启动采样。五、显示 / 隐藏show()、hide() 与内置迷你面板5.1 常规 show / hideshow()Tab.js与hide()Tab.js成对出现行为包括切换content与button的display样式并更新isVisible若标签处于分离态同步显示/隐藏其detachedWindow.panel触发onVisibilityChange()回调即Profiler.updatePanelSize让主面板在所有标签都隐藏时收缩到最小尺寸末尾调用showBuiltin()/hideBuiltin()同步内置按钮状态。5.2 builtin 标签与迷你面板的换入换出这是builtin: true标签特有的行为。当主面板被折叠后builtin 标签的内容会被搬到 profiler-toggle 按钮旁边的 mini-panel.profiler-mini-panel中实现轻量级快捷面板。showBuiltin()Tab.js做五件事让profiler.builtinTabsContainer位于 toggle 按钮内可见显示自己的builtinButton隐藏 mini-panel 中其他所有.mini-panel-content并清除所有.builtin-tab-btn的activeclass将this.content的子节点逐个子树整体搬移到miniContent仅在miniContent为空时执行while ( this.content.firstChild ) miniContent.appendChild( ... )显示miniContent并给 mini-panel 加上visibleclass。hideBuiltin()Tab.js执行相反操作隐藏按钮、把子节点从miniContent搬回content、移除activeclass并且当 mini-panel 中已无可见内容或 toggle 中已无可见内置按钮时自动隐藏对应容器。这种内容在主面板与迷你面板之间搬家的设计保证同一份 DOM 内容不会在两个位置重复渲染。Profiler.show(tab)/Profiler.hide()Profiler.js是 builtin 按钮点击后的实际调度方点击.builtin-tab-btn时会在展开该迷你面板与收起之间切换并通过e.stopPropagation()避免误触发整个面板的 toggle。六、与 Profiler 的协作注册、拖拽分离与移除6.1Profiler.addTab注册流程无论是内置标签还是扩展标签最终都要经由Profiler.addTab( tab )Profiler.js完成注册其内部顺序值得关注以tab.id为键存入this.tabs并分配tab.originalIndex记录添加顺序供布局持久化排序用allowDetach false时给按钮加no-detachclassUI 上的锁定视觉指示把tab.onVisibilityChange绑定为() this.updatePanelSize()调用setupTabDragAndDrop( tab )注册点击与拖拽事件非 builtin 标签按钮追加到.profiler-tabsbuiltin 标签按钮与迷你内容容器由addBuiltinTab创建并挂到 toggle / mini-panel无论哪种类型tab.content都统一追加到contentWrapper并同步当前isVisible状态到 DOM注入tab.profiler this若tab.id与上次持久化布局中记录的activeTabId一致立即setActiveTab。作为入口封装Inspector.addTab( tab )Inspector.js直接委托给profiler.addTab并返回this支持链式调用。6.2 拖拽分离detach与回嵌reattach分离交互同样由 Profiler 承载。setupTabDragAndDrop中设置了10px 位移阈值指针按下后移动超过阈值才判定为拖拽否则视为普通点击切换激活。拖拽过程中会创建一个半透明预览窗口跟随指针Profiler.js松手后调用detachTab( tab, x, y )Profiler.js再次校验allowDetach双保险若被分离的是当前激活标签则按先左邻后右邻的策略选择新的激活标签把按钮与内容从主面板 DOM 中摘除调用createDetachedWindow生成独立浮窗默认约 400×300初始位置会被约束在视口内并推入detachedWindows数组管理置tab.isDetached true、tab.detachedWindow detachedWindow随后updatePanelSize()与saveLayout()持久化布局。浮窗标题栏上会提供 reattach 按钮reattachTab将标签重新放回主面板。此外 Profiler 在window.resize时会对所有分离浮窗执行constrainWindowToBounds允许浮窗最多一半越出屏幕边缘。6.3removeTab与清理Profiler.removeTab会移除 tabs 字典项、按钮、miniContent、内容容器若该标签当前激活会尝试激活剩余的第一个可见非分离标签最后清空onVisibilityChange与profiler引用Profiler.js。完整的先释放再移除顺序由Inspector.removeTab保证先tab.dispose()再由 Profiler 做 DOM 清理。七、内置标签如何用这三个选项以 Parameters 为例Parameters标签是builtin icon选项组合的典型实例Inspector.jsconst parameters new Parameters( { builtin: true, icon: svg xmlnshttp://www.w3.org/2000/svg width24 height24 viewBox0 0 24 24 fillnone strokecurrentColor stroke-width2 ....../svg } ); parameters.hide(); profiler.addTab( parameters );含义解读builtin: true它的标签按钮不占用主标签栏而是作为一个带图标的快捷按钮常驻在profiler-toggleFPS 切换按钮内即使主面板收起也能点击展开迷你面板快速查看/编辑参数icon自定义的滑块类 SVG 图标会直接作为该 builtin 按钮的 HTML 内容若缺省则会退化为显示标题首字母parameters.hide()注册前先隐藏使布局恢复时保持收起状态而 builtin 按钮仍可被随时唤起Profiler.addTab会把isVisible false同步为隐藏 builtin 按钮与迷你内容见 Profiler.js。这也是 Tab.js 注释中展示的推荐用法// 可分离标签默认行为 const tab1 new Tab( My Tab ); // 不可分离的固定标签 const tab2 new Tab( Fixed Tab, { allowDetach: false } ); // 出现在 profiler-toggle 中的内置标签 const tab3 new Tab( Builtin Tab, { builtin: true } ); // 带自定义图标的内置标签 const tab4 new Tab( Settings, { builtin: true, icon: svg.../svg } ); // 控制内置标签的可见性 tab3.showBuiltin(); tab3.hideBuiltin();八、编写自己的扩展标签页扩展标签页有两种层次直接extends Tab的自定义标签以及基于 Inspector 扩展机制的Extension。8.1 最简单的自定义 Tab参考 Memory.js、Console.js 的写法import { Tab } from three/addons/inspector/ui/Tab.js; export class MyStatsTab extends Tab { constructor( options {} ) { super( options.name || My Stats, options ); this.output document.createElement( div ); this.content.appendChild( this.output ); } update( inspector ) { // 面板折叠或分离态之外的真激活判断 if ( this.isActive ) { this.output.textContent active tab update at frame ${ inspector.frameId }; } } }使用方拿到 Inspector 实例后即可注册import { Inspector } from three/addons/inspector/Inspector.js; import { MyStatsTab } from ./MyStatsTab.js; const inspector new Inspector( renderer, app ); // 按 Inspector 实际构造方式初始化 const tab new MyStatsTab( { allowDetach: false } ); inspector.addTab( tab );要点回顾DOM 一律挂在this.content首次装配逻辑放init每帧刷新放update读激活状态用isActivegetterallowDetach: false可做成不可分离的固定页签。8.2 带持久化的 Extension 扩展需要随 Inspector 布局一起持久化配置的扩展应继承 examples/jsm/inspector/Extension.js 中export class Extension extends Tab。Extension在init阶段自动接入 Inspector 的resize/layoutchange/orientationchange事件并从 localStoragegetItem(this.name)恢复数据子类覆写serialize()/deserialize()即可获得配置记忆save()负责写回。仓库中的两个官方扩展即遵循此模式调色扩展 color-grading/ColorGrading.jsTSL 图编辑器 tsl-graph/TSLGraphEditor.js。扩展的装载与卸载由 Settings.js 的_loadExtension/_unloadExtension完成通过动态import解析扩展模块new ExtensionClass()创建标签再走inspector.addTab( extensionTab )卸载则调用inspector.removeTab( extension.tab )触发 Tab.js 中的dispose()钩子做资源清理。九、小结Tab虽名为标签页基类实际承担了 three.js Inspector 中从 DOM 创建、ID 派生、状态判定到分离浮窗协作的整套基础能力。本文覆盖的要点可归纳为构造参数title含自动派生id、options.allowDetach默认true控制可否分离、options.builtin默认false控制是否进入 profiler-toggle、options.iconbuiltin 按钮的 SVG 图标派生约定UI 挂this.content装配放init()仅一次刷新放update()每帧销毁放dispose()激活判断用isActivegetter与 Profiler 的关系addTab完成 DOM 挂载与状态注入拖拽超 10px 触发分离为独立浮窗mini-panel 负责主面板收起时 builtin 内容的换入换出扩展两种形态extends Tab适合纯自绘标签extends Extension额外获得配置序列化与 Inspector 事件联动。如需查阅更精确的 API 签名可对照官方生成的类文档 docs/pages/Tab.html动手实验时可直接阅读 examples/jsm/inspector/ui/Tab.js 以及各内置标签实现作为编写自定义面板的参照模板。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表