// ==UserScript== // @name InfiniteCanvas Turbo // @namespace https://github.com/hero8152/Infinite-Canvas // @version 11.1.1 // @icon https://raw.githubusercontent.com/lonely814/Messy/refs/heads/main/InfiniteCanvas%20Turbo/icon.png // @description FPS+节点统计 · 菜单开关 · G网格 T性能 H隐藏图 L连线 O列表 C链高亮 F搜索 X专注 · 主题 · 圆角 · DOM虚拟化 // @author oocc00 // @license MIT // @match *://*/static/canvas.html* // @match *://*/static/smart-canvas.html* // @match *://*/canvas.html* // @match *://*/canvas* // @exclude *://*.iblib.tv/canvas* // @exclude *://*.liblib.tv/canvas* // @run-at document-idle // @grant GM_registerMenuCommand // @grant unsafeWindow // ==/UserScript== (function(){ 'use strict'; /* ———— 智能画布检测 ———— */ var _pfSmart = location.pathname.indexOf('smart-canvas') !== -1; /* ========================================================= * 1. CSS 注入 * ========================================================= */ var style = document.createElement('style'); style.id = 'ict-css'; var css = [ /* ———— 可视化自定义(改这里调整颜色/粗细) ———— */ ':root {', ' --pf-link-width: 2.2; /* 连线粗细 */', ' --pf-link-opacity: 0.7; /* 连线透明度 */', '}', /* content-visibility 按节点类型 */ '.image-node', ' { content-visibility: auto; contain-intrinsic-size: auto 260px 336px; }', '.node:not(.image-node)', ' { content-visibility: visible; }', '#world { will-change: transform; }', /* 拖拽时隐藏图片 */ 'body.canvas-board-pan .node img, body.canvas-node-drag .node img,', 'body.canvas-board-pan .node video, body.canvas-node-drag .node video,', 'body.canvas-board-pan .image-node img, body.canvas-node-drag .image-node img,', 'body.canvas-board-pan .image-node video, body.canvas-node-drag .image-node video', ' { display: none !important; }', /* 网格背景开关 — 兼容两种画布 */ '.board.perf-no-grid, .shell.perf-no-grid { background-image: none !important; }', /* 隐藏所有图片(H 键) */ 'body.perf-hide-imgs .node img, body.perf-hide-imgs .node video,', 'body.perf-hide-imgs .image-node img, body.perf-hide-imgs .image-node video,', 'body.perf-hide-imgs .media-card', ' { display: none !important; }', /* 隐藏连线(L 键) — 兼容两种画布 */ '#links.perf-hide-links, .connection-layer.perf-hide-links { display: none !important; }', /* ———— 选中节点 ———— */ /* ———— 输出节点列表模式(O 键切换) ———— */ 'body.perf-output-list .output-grid {', ' display: flex !important; flex-direction: column !important;', ' gap: 1px !important; grid-template-columns: none !important;', ' justify-content: start !important;', '}', 'body.perf-output-list .output-img-wrap {', ' max-width: none !important; aspect-ratio: auto !important;', ' display: flex !important; flex-direction: row !important;', ' align-items: center !important; gap: 8px !important;', ' padding: 2px 6px !important; cursor: pointer !important;', '}', 'body.perf-output-list .output-img-wrap img,', 'body.perf-output-list .output-img-wrap video {', ' width: 36px !important; height: 36px !important;', ' object-fit: cover !important; border-radius: 4px !important;', ' flex: 0 0 auto !important; min-width: 36px !important;', '}', 'body.perf-output-list .output-img-wrap .output-del {', ' position: static !important;', ' margin: 0 0 0 8px !important;', ' opacity: 0.6 !important; flex: 0 0 auto !important;', '}', 'body.perf-output-list .output-img-wrap .output-del:hover {', ' opacity: 1 !important;', '}', 'body.perf-output-list .output-img-wrap .canvas-video-play,', 'body.perf-output-list .output-img-wrap .output-video-badge {', ' display: none !important;', '}', /* 列表模式拖动时保持缩略图可见,防止布局突然紧凑 */ 'body.perf-output-list.canvas-board-pan .output-img-wrap img,', 'body.perf-output-list.canvas-node-drag .output-img-wrap img,', 'body.perf-output-list.canvas-board-pan .output-img-wrap video,', 'body.perf-output-list.canvas-node-drag .output-img-wrap video {', ' display: block !important;', '}', 'body.perf-output-list .output-file-card,', 'body.perf-output-list .output-audio-card {', ' flex-direction: row !important; gap: 6px !important;', ' padding: 2px 0 !important; border: none !important;', '}', 'body.perf-output-list .output-audio-card audio {', ' display: none !important;', '}', '.pf-label {', ' font: 11px/1.4 -apple-system, sans-serif;', ' color: var(--text, #333);', ' overflow: hidden; text-overflow: ellipsis;', ' white-space: nowrap; min-width: 0; flex: 1;', '}', '.theme-dark .pf-label { color: var(--text, #ccc); }', /* ———— 连线样式 ———— */ '.link {', ' stroke: var(--faint) !important;', ' stroke-width: 2.2 !important;', ' opacity: 0.7 !important;', ' stroke-linecap: round !important;', ' stroke-linejoin: round !important;', '}', '.link.link-active {', ' stroke: var(--strong) !important;', ' opacity: 1 !important;', '}', '.theme-dark .link.link-active {', ' filter: drop-shadow(0 0 6px rgba(96,165,250,0.25));', '}', /* ———— 性能模式 ———— */ 'body.perf-mode .node, body.perf-mode .image-node {', ' box-shadow: none !important; border-radius: 0 !important;', ' opacity: 0.85; contain: content !important;', ' will-change: auto !important; isolation: auto !important;', ' mix-blend-mode: normal !important;', ' border-color: rgba(255,255,255,0.06) !important;', '}', 'body.perf-mode .node:hover, body.perf-mode .image-node:hover { opacity: 1; }', 'body.perf-mode .node.selected, body.perf-mode .image-node.selected { opacity: 1; }', 'body.perf-mode .node::before { display: none !important; }', 'body.perf-mode .panel,', 'body.perf-mode .library,', 'body.perf-mode .log-panel,', 'body.perf-mode .prompt-template-panel,', 'body.perf-mode .canvas-asset-panel,', 'body.perf-mode .workflow-transfer-panel', ' { box-shadow: none !important;', ' backdrop-filter: none !important;', ' background: var(--card-solid) !important;', ' border-radius: 0 !important; resize: none !important; }', 'body.perf-mode .topbar .panel { background: var(--card-solid) !important; }', 'body.perf-mode .node-run-status.running .dot { animation: none !important; }', 'body.perf-mode * { backdrop-filter: none !important; animation: none !important; transition: none !important; filter: none !important; cursor: default !important; }', 'body.perf-mode .minimap, body.perf-mode .smart-minimap { box-shadow: none !important; }', 'body.perf-mode .node-head { border-bottom-color: var(--line-2) !important; }', 'body.perf-mode ::-webkit-scrollbar { width: 4px !important; height: 4px !important; }', 'body.perf-mode ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1) !important; }', 'body.perf-mode * { scrollbar-width: thin !important; scrollbar-color: rgba(255,255,255,0.1) transparent !important; }', /* LOD */ '.world.zoom-lod-1 .node img, .world.zoom-lod-1 .node video,', '.world.zoom-lod-1 .image-node img, .world.zoom-lod-1 .image-node video', ' { filter: blur(5px); opacity: 0.25; }', '.world.zoom-lod-1 .node .image-caption,', '.world.zoom-lod-1 .node .node-run-status { display: none !important; }', '.world.zoom-lod-2 .node img, .world.zoom-lod-2 .node video,', '.world.zoom-lod-2 .image-node img, .world.zoom-lod-2 .image-node video,', '.world.zoom-lod-2 .media-card { display: none !important; }', '.world.zoom-lod-2 .node .node-body, .world.zoom-lod-2 .image-node .node-body', ' { min-height: 24px !important; padding: 3px 6px !important; }', '.world.zoom-lod-2 .node .image-caption,', '.world.zoom-lod-2 .node .node-run-status { display: none !important; }', '.world.zoom-lod-3 .node .node-body, .world.zoom-lod-3 .image-node .node-body { display: none !important; }', '.world.zoom-lod-3 .node, .world.zoom-lod-3 .image-node', ' { min-width: 48px !important; min-height: 22px !important;', ' border-radius: 5px !important; }', '.world.zoom-lod-3 .node-head', ' { height: 22px !important; flex-basis: 22px !important;', ' border-radius: 4px 4px 0 0 !important; padding: 0 4px !important; }', '.world.zoom-lod-3 .node-title { font-size: 5px !important; }', '.world.zoom-lod-3 .node-head button { display: none !important; }', '.world.zoom-lod-3 .links path, .world.zoom-lod-3 .links line,', '.world.zoom-lod-3 .connection-layer path', ' { stroke-width: 0.3 !important; opacity: 0.1 !important; }', '.node:not(.selected) img, .image-node:not(.selected) img { fetchpriority: low; }', /* FPS 药丸 — 紧凑悬浮药丸 (与 libtv-boost 同步) */ '#perf-fps {', ' position: fixed; left: 14px; bottom: 14px; z-index: 99999;', ' padding: 5px 12px 5px 10px; border-radius: 20px;', ' background: rgba(0,0,0,0.4); color: #aaa;', ' font: 12px/1.5 -apple-system, "SF Mono", monospace;', ' user-select: none;', ' backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);', ' border: 1px solid rgba(255,255,255,0.05);', ' white-space: nowrap;', ' display: inline-flex; align-items: center; gap: 4px;', ' cursor: grab;', ' transition: all 0.2s ease;', '}', '#perf-fps:hover {', ' background: rgba(0,0,0,0.55);', ' border-color: rgba(255,255,255,0.1);', ' padding: 5px 12px 5px 10px;', '}', '#perf-fps:active { cursor: grabbing; }', '#perf-fps .pf-fps-val { color: #8f8; font-weight: 600; }', '#perf-fps .pf-mem { color: #7fc; }', '#perf-fps .pf-lod { color: #88f; }', '#perf-fps .pf-flag { color: #fa0; }', '#perf-fps .pf-sep { color: rgba(255,255,255,0.12); }', '#perf-fps .pf-nodes { color: #aaa; }', '#perf-fps .pf-cnt { color: #aaa; }', /* 快捷键提示 — 底部居中,无背景无边框 */ '#perf-help {', ' position:fixed; left:50%; bottom:12px; z-index:99998;', ' transform:translateX(-50%);', ' color:rgba(255,255,255,0.3);', ' font:10px/1.7 -apple-system,"SF Mono",monospace;', ' pointer-events:none; user-select:none;', ' white-space:nowrap; letter-spacing:0.04em;', ' transition:opacity .3s;', '}', '#perf-help.pf-hide { opacity:0; }', /* ———— 链高亮模式 ———— */ 'body.pf-chain #nodes .node { opacity:0.06 !important; }', 'body.pf-chain #nodes .node.selected,', 'body.pf-chain #nodes .node.pf-chain-node', ' { opacity:1 !important; }', 'body.pf-chain #links .link, body.pf-chain .connection-layer path { opacity:0.03 !important; }', 'body.pf-chain #links .link.pf-chain-edge', ' { opacity:0.8 !important; stroke:var(--strong,#818cf8) !important; }', /* ———— 专注模式 ———— */ 'body.pf-focus .topbar,', 'body.pf-focus .panel-group,', 'body.pf-focus .library,', 'body.pf-focus .canvas-asset-panel,', 'body.pf-focus .workflow-transfer-panel,', 'body.pf-focus .log-panel,', 'body.pf-focus .property-panel,', 'body.pf-focus .minimap,', 'body.pf-focus .smart-minimap,', 'body.pf-focus .smart-back,', 'body.pf-focus .smart-title,', 'body.pf-focus #composer', ' { display:none !important; }', 'body.pf-focus #board, body.pf-focus #shell {', ' width:100vw !important; height:100vh !important;', ' max-width:none !important; left:0 !important; top:0 !important;', '}', /* ———— 搜索框 ———— */ '#pf-search {', ' position:fixed; top:60px; right:20px; z-index:99999;', ' background:rgba(0,0,0,0.9); backdrop-filter:blur(20px);', ' -webkit-backdrop-filter:blur(20px);', ' border:1px solid rgba(255,255,255,0.08);', ' border-radius:10px; padding:10px; width:280px;', ' max-height:60vh; overflow:auto;', ' font:12px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;', '}', /* ———— DOM 虚拟化 — 含 150% 边距,拖动时自动全显 ———— */ '.node.pf-vhide, .node.pf-vhide-force, .image-node.pf-vhide, .image-node.pf-vhide-force { display:none !important; }', '.node.pf-vhide img, .node.pf-vhide-force img, .image-node.pf-vhide img, .image-node.pf-vhide-force img { display:none !important; }', /* board 背景由应用自身 --page 变量处理 */ ]; style.textContent = css.join('\n'); document.head.appendChild(style); /* ========================================================= * 2. FPS + 内存 + 节点统计 显示 * ========================================================= */ var fpsEl = document.createElement('div'); fpsEl.id = 'perf-fps'; fpsEl.textContent = 'FPS: --'; document.body.appendChild(fpsEl); var helpEl = document.createElement('div'); helpEl.id = 'perf-help'; helpEl.textContent = _pfSmart ? 'G网格 T性能 H隐藏 L连线 F搜索 X专注 ?帮助' : 'G网格 T性能 H隐藏 L连线 O列表 C链高亮 F搜索 X专注 ?帮助'; document.body.appendChild(helpEl); var _frameCnt = 0; var _lastFpsT = performance.now(); var _fps = 0; var TYPE_LABELS = { image: '图片', video: '视频', prompt: '提示词', llm: 'LLM', generator: '生成', msgen: 'MS', comfy: 'Comfy', rh: 'RH', ltxDirector: 'LTX', output: '输出', loop: '循环', group: '分组', promptGroup: '提示组', 'prompt-smart-node': '提示词', 'loop-smart-node': '循环', 'smart-group-node': '分组', 'history-group': '历史分组', }; function fmtNodeTypes(nodesArr){ if (!nodesArr || !nodesArr.length) return ''; var map = {}; for (var i = 0; i < nodesArr.length; i++){ var t = nodesArr[i].type || '?'; map[t] = (map[t] || 0) + 1; } var parts = []; var order = ['image','video','prompt','llm','generator','msgen','comfy','rh','ltxDirector','output','loop','group','promptGroup','prompt-smart-node','loop-smart-node','smart-group-node','history-group']; for (var o = 0; o < order.length; o++){ if (map[order[o]]) parts.push(TYPE_LABELS[order[o]] + map[order[o]]); } for (var k in map){ if (order.indexOf(k) === -1) parts.push(k+':'+map[k]); } return parts.join(' '); } function fmtMem(){ try { var m = performance.memory; if (m && m.usedJSHeapSize) return Math.round(m.usedJSHeapSize / 1048576) + 'MB'; } catch(e){} return ''; } function fpsLoop(now){ _frameCnt++; if (now - _lastFpsT >= 1000){ _fps = Math.round(_frameCnt * 1000 / (now - _lastFpsT)); var nodeArr = typeof nodes !== 'undefined' ? nodes : []; var nTotal = nodeArr.length; var types = fmtNodeTypes(nodeArr); var mem = fmtMem(); var lod = ''; var w = document.getElementById('world'); if (w){ if (w.classList.contains('zoom-lod-3')) lod = 'L3'; else if (w.classList.contains('zoom-lod-2')) lod = 'L2'; else if (w.classList.contains('zoom-lod-1')) lod = 'L1'; } var flags = ''; var b = document.getElementById('board') || document.getElementById('shell'); if (b && b.classList.contains('perf-no-grid')) flags += ''; if (document.body.classList.contains('perf-mode')) flags += ''; if (document.body.classList.contains('perf-hide-imgs')) flags += ''; if (document.body.classList.contains('perf-output-list')) flags += ''; var _lk = document.getElementById('links') || document.querySelector('.connection-layer'); if (_lk && _lk.classList.contains('perf-hide-links')) flags += ''; if (document.body.classList.contains('pf-chain')) flags += ''; if (document.body.classList.contains('pf-autochain')) flags += ''; if (document.body.classList.contains('pf-focus')) flags += ''; fpsEl.innerHTML = '' + _fps + 'fps' + (mem ? '|' + mem + '' : '') + (lod ? '|' + lod + '' : '') + '|' + nTotal + '节点' + (types ? '|' + types + '' : '') + (flags ? '|' + flags : ''); _frameCnt = 0; _lastFpsT = now; } requestAnimationFrame(fpsLoop); } requestAnimationFrame(fpsLoop); /* ========================================================= * 3. 删除节点时清理图片内存 * ========================================================= */ function clearNodeImages(el){ if (!el || el.nodeType !== 1) return; if (el.tagName === 'IMG') { el.src = ''; return; } var imgs = el.querySelectorAll ? el.querySelectorAll('img') : []; for (var i = 0; i < imgs.length; i++) imgs[i].src = ''; } var _pfNodeSel = _pfSmart ? '.image-node' : '.node'; var _pfContainerSel = _pfSmart ? '#world' : '#nodes'; var _clearObs = new MutationObserver(function(records){ for (var r = 0; r < records.length; r++){ var removed = records[r].removedNodes; for (var n = 0; n < removed.length; n++){ if (removed[n].nodeType === 1){ if (removed[n].matches && removed[n].matches(_pfNodeSel)) clearNodeImages(removed[n]); else if (removed[n].querySelectorAll){ var nodes = removed[n].querySelectorAll(_pfNodeSel); for (var i = 0; i < nodes.length; i++) clearNodeImages(nodes[i]); } } } } }); var _initNodes = setInterval(function(){ var el = document.getElementById(_pfSmart ? 'world' : 'nodes'); if (el){ clearInterval(_initNodes); _clearObs.observe(el, { childList: true, subtree: true }); } }, 100); /* ========================================================= * 4. LOD * ========================================================= */ var _lastScale = 1; function updateLOD(s){ if (_pfSmart) return; var w = document.getElementById('world'); if (!w) return; var add = s < 0.12 ? 'zoom-lod-3' : s < 0.25 ? 'zoom-lod-2' : s < 0.45 ? 'zoom-lod-1' : ''; var L = ['zoom-lod-1','zoom-lod-2','zoom-lod-3']; var cur = w.className, need = false; for (var i = 0; i < 3; i++){ if ((add === L[i]) !== (cur.indexOf(L[i]) !== -1)){ need = true; break; } } if (!need) return; w.classList.remove.apply(w.classList, L); if (add) w.classList.add(add); } var _world = document.getElementById('world'); if (_world){ new MutationObserver(function(){ var s = 1, m = (_world.style.transform || '').match(/scale\(([^)]+)\)/); if (m) s = parseFloat(m[1]); if (Math.abs(s - _lastScale) < 0.005) return; _lastScale = s; updateLOD(s); }).observe(_world, { attributes: true, attributeFilter: ['style'] }); } /* ========================================================= * 5. 图片后优化 * ========================================================= */ var _pfImgNodeSel = _pfSmart ? '.image-node img:not([data-pfO])' : '.node img:not([data-pfO])'; function optimizeImages(){ var c = document.getElementById(_pfSmart ? 'world' : 'nodes'); if (!c) return; var list = c.querySelectorAll(_pfImgNodeSel); for (var i = 0; i < list.length; i++){ list[i].setAttribute('loading', 'lazy'); list[i].setAttribute('decoding', 'async'); list[i].setAttribute('fetchpriority', 'low'); list[i].dataset.pfO = '1'; } } var nodesEl = document.getElementById(_pfSmart ? 'world' : 'nodes'); if (nodesEl){ new MutationObserver(function(){ optimizeImages(); }).observe(nodesEl, { childList: true, subtree: true }); } /* ========================================================= * 6.