--- title: "第五篇:视听多媒体与产物交互物理学" part: 5 category: cds-manual domain: "Audiovisual, Lightbox & Artifacts Experience" ---
[⬅️ 上一篇:第四篇:动效物理学、着色器与环境光影](04-动效物理学、着色器与环境光影.md) · [📚 返回总目录](Claude%20Design%20System%20(CDS).md) · [下一篇:第六篇:系统级桌面架构与协作拓扑 ➡️](06-系统级桌面架构与协作拓扑.md)
--- ## 第五篇:视听多媒体与产物交互物理学 > **技术领域与英文指代**:`Audiovisual, Lightbox & Artifacts Experience` > **逆向工程溯源**:基于 **REA (Reverse Engineer Anything) v6.5.0** 提取自 macOS 生产包 `/Applications/Claude.app`(`@ant/desktop v2.37337.1`,Build 时间戳 `2026-10-13T22:16:06Z`),深度解构核心音频驱动 `/Applications/Claude.app/Contents/Resources/ion-dist/audio/voice/`、核心样式资产 `c6a992d55-sXiDGMFQ.css`、画廊与缩放平移算法 `c4b054ed5-BGjH-Uu6.js`、View Transitions 无缝形变 `cd5136de8-BDys1vAN.js`、Epitaxy 隔离沙箱 `c58115fda-BKnbLmi3.js` 以及代码流式高亮引擎 `c19e90103-CvUj-0ZH.js`。 --- ### 5.1 语音模式与 Web Audio 物理声学系统 > **技术领域与英文指代**:`Voice Mode & AudioWorklet Architecture` #### 5.1.1 官方流式音频架构逆向解密:AudioWorklet 与 SharedArrayBuffer 环形缓冲 (`audioSinkWorklet.js`) 在语音大模型实时对话(如 Claude Voice / Realtime API)场景下,传统的 `AudioContext.decodeAudioData` 或单次 `AudioBufferSourceNode` 调度会因为主线程垃圾回收(GC)或长任务阻塞而产生致命的爆音、破音或断断续续。 #### 1. 核心生产反编译源码取证 (`audioSinkWorklet.js`) Anthropic 采用现代浏览器的 `AudioWorkletProcessor` 在独立的高优先级音频渲染线程中运行,并通过 `SharedArrayBuffer` 与 `Atomics` 原子操作实现主线程与音频线程之间的**无锁零拷贝生产-消费者队列(RingBuffer)**(完整取证自 `/Applications/Claude.app/Contents/Resources/ion-dist/audio/voice/audioSinkWorklet.js`): ```javascript // audioSinkWorklet.js: Atomics 驱动的环形无锁缓冲区 var RD = 0; var WR = 1; var UNDERRUNS = 2; var PLAYED = 3; var HEADER_SLOTS = 4; var HEADER_BYTES = HEADER_SLOTS * Uint32Array.BYTES_PER_ELEMENT; var RingBuffer = class { constructor(sab) { this.indices = new Uint32Array(sab, 0, HEADER_SLOTS); this.storage = new Float32Array(sab, HEADER_BYTES); this.capacity = this.storage.length; } static allocate(capacity) { const bytes = HEADER_BYTES + capacity * Float32Array.BYTES_PER_ELEMENT; const Ctor = typeof SharedArrayBuffer !== "undefined" ? SharedArrayBuffer : ArrayBuffer; return new Ctor(bytes); } // 生产者(主线程 / Opus 解码器写入 PCM 样本) push(input) { const wr = Atomics.load(this.indices, WR); const rd = Atomics.load(this.indices, RD); const n = Math.min(input.length, this._availableWrite(rd, wr)); if (n === 0) return 0; const tail = this.capacity - wr; if (n <= tail) { this.storage.set(input.subarray(0, n), wr); } else { this.storage.set(input.subarray(0, tail), wr); this.storage.set(input.subarray(tail, n), 0); } Atomics.store(this.indices, WR, (wr + n) % this.capacity); return n; } // 消费者(AudioWorklet 高优先级线程以约 375Hz 频率读取,每帧 128 样本) pop(output) { const rd = Atomics.load(this.indices, RD); const wr = Atomics.load(this.indices, WR); const n = Math.min(output.length, this._availableRead(rd, wr)); if (n === 0) return 0; const tail = this.capacity - rd; if (n <= tail) { output.set(this.storage.subarray(rd, rd + n)); } else { output.set(this.storage.subarray(rd, this.capacity)); output.set(this.storage.subarray(0, n - tail), tail); } Atomics.store(this.indices, RD, (rd + n) % this.capacity); return n; } _availableRead(rd, wr) { return (wr - rd + this.capacity) % this.capacity; } _availableWrite(rd, wr) { return this.capacity - 1 - this._availableRead(rd, wr); } }; // 音频帧处理器 (以 128 样本为一量度,48kHz 下每秒执行 375 次) var AUDIO_SINK_PROCESSOR_NAME = "audio-sink-processor"; class AudioSinkProcessor extends AudioWorkletProcessor { process(_inputs, outputs) { const channel = outputs[0]?.[0]; if (!channel) return true; const read = this.ring.pop(channel); if (read < channel.length) { // 欠载保护:不足部分用 0 填充静音,并触发欠载检测,绝不输出爆音 channel.fill(0, read); if (this.hasStarted && this.gapReady) { this.dryGapPending = true; if (read === 0 && !this.drainPosted) { this.notifyDrained(); } } } return true; } } registerProcessor(AUDIO_SINK_PROCESSOR_NAME, AudioSinkProcessor); ``` --- #### 5.1.2 四大官方 SFX 交互音效与 30% 舒适声学规范 (`shared-8-4Jj8qhmU.js`) 在桌面端与语音模式中,过度刺耳的蜂鸣提示音会破坏 Claude 温润的人文调性。Anthropic 在 `shared-8-4Jj8qhmU.js` 中确立了严格的**30% 舒缓音量原则**与硬件路由: ```javascript // shared-8-4Jj8qhmU.js: 官方音效调度器 function createSoundElement(url, volume = 0.3) { if (typeof window === "undefined") return null; let audio = new Audio(url); // 核心声学标准:全局固定 30% 音量,温和舒适,绝不产生惊吓感 audio.volume = volume; return audio; } async function initClaudeSoundPipeline() { let AudioContextClass = window.AudioContext || window.webkitAudioContext; let ctx = new AudioContextClass(); // 四大官方交互音效矩阵(实际存储于 /audio/voice/sfx/) let sfxMap = { "enter_voice_mode": createSoundElement("/audio/voice/sfx/enter_voice_mode.mp3", 0.3), "exit_voice_mode": createSoundElement("/audio/voice/sfx/exit_voice_mode.mp3", 0.3), "disconnected": createSoundElement("/audio/voice/sfx/disconnected.mp3", 0.3), "tool_approval_needed": createSoundElement("/audio/voice/sfx/tool_approval_needed.mp3", 0.3) }; // 通过 AudioContext 目的节点进行直通低延迟连接 Object.values(sfxMap).forEach(audio => { if (audio && ctx) { ctx.createMediaElementSource(audio).connect(ctx.destination); audio.load(); } }); // 支持 setSinkId 独立选择外接会议扬声器或耳机 let selectedSpeakerId = getSelectedSpeakerDevice(); if (selectedSpeakerId && typeof ctx.setSinkId === "function") { await ctx.setSinkId(selectedSpeakerId); } } ``` | 音效标识 | 物理文件路径 | 声学特征与设计动机 | 触发时机 | | :--- | :--- | :--- | :--- | | `enter_voice_mode` | `sfx/enter_voice_mode.mp3` | 双音阶上扬泛音,清脆柔和(9.7KB) | 用户点击麦克风开启语音交互通道 | | `exit_voice_mode` | `sfx/exit_voice_mode.mp3` | 单音阶下沉泛音,平缓收束(10.4KB) | 用户主动挂断或语音闲置超时 | | `tool_approval_needed` | `sfx/tool_approval_needed.mp3` | 温暖的木质敲击共振双音(9.2KB) | 模型触发高风险电脑使用或 Bash 待确认 | | `disconnected` | `sfx/disconnected.mp3` | 微弱的电平降调衰减音(10.7KB) | WebSocket / SSE 连接中断 | > **开源落地推荐**:本仓库推荐采用基于原生 Web Audio API 振荡器的零外部资源合成方案:[`code-examples/ClaudeAudioPlayer.ts`](../code-examples/ClaudeAudioPlayer.ts)。 --- #### 5.1.3 6 频段非线性 Gamma 幂律波形动效 (`LiveMicActivityBars`) 在语音交互中,麦克风波形如果直接绑定分贝线,会因为低音轰鸣导致视觉上总是“顶格”,而说话停顿时的背景噪音又会导致微小抖动。 Anthropic 在 `c4a1e6da6-B9dxf4-D.js` 中构建了 **LiveMicActivityBars** 算法: ```mermaid flowchart LR Mic["麦克风音频流"] --> FFT["512 点 FFT 频域分析 (smoothing=0.5)"] FFT --> DecibelClamp["动态分贝截断 (-70dB 至 -10dB)"] NoiseGate["8% 噪声门限过滤 (Math.max(0, u - 0.08) / 0.92)"] DecibelClamp --> NoiseGate NoiseGate --> GammaComp["0.7 次幂非线性 Gamma 压缩 (f^0.7 * 1.5 * weight)"] GammaComp --> MotionInterpolate["Framer Motion 弹性高度映射 (4px - 24px)"] ``` #### 核心数学公式与生产反编译算法 ```javascript // c4a1e6da6-B9dxf4-D.js // 1. Web Audio AnalyserNode 配置 analyser.fftSize = 512; analyser.smoothingTimeConstant = 0.5; // 消除帧间视觉毛刺 analyser.minDecibels = -70; // 静音底噪基准线 analyser.maxDecibels = -10; // 正常说话饱和上限 // 2. 核心 6 频段能量提取与 Gamma 幂律映射公式 for (let b = 0; b < 6; b++) { let rawEnergy = computeAverageBinEnergy(dataArray, b); // [0.0, 1.0] let noiseFloor = 0.08; // 8% 噪声门限:彻底消除空调背景杂音引发的虚假微动 let gated = Math.max(0, rawEnergy - noiseFloor) / (1 - noiseFloor); // 核心视觉物理学:0.7 次幂非线性 Gamma 压缩 // 放大微弱的人声细语,同时压制高分贝爆音,使波形动态范围分布极度均匀 let compressed = Math.min(1, Math.pow(gated, 0.7) * 1.5 * bandWeights[b]); // 映射至 4px - 24px 物理高度 let targetHeight = 4 + (24 - 4) * compressed; motionValues[b].set(targetHeight); } ``` > **生产级组件落地**:[`code-examples/LiveMicActivityBars.tsx`](../code-examples/LiveMicActivityBars.tsx)。 --- #### 5.1.4 5 大官方语音人格与声学特征解构 Claude 语音模式提供了 5 种经过严格声学调优与情绪定位的官方音色人格(实际存储于 `/Applications/Claude.app/Contents/Resources/ion-dist/audio/voice/selection/`): ``` voice-personas/ ├── airy # 空灵柔和 (Airy): 呼吸感强、语速适中,适合深度思考与陪伴 ├── buttery # 醇厚顺滑 (Buttery): 磁性饱满、亲和力强,默认主推人格 ├── glassy # 清澈明朗 (Glassy): 咬字清晰、高频透亮,适合代码讲解与教学 ├── mellow # 沉稳温润 (Mellow): 语调舒缓、低频共鸣,适合夜间阅读与长文本 └── rounded # 饱满圆润 (Rounded): 节奏轻快、干脆利落,适合效率型问答 ``` --- #### 5.1.5 双向流式声学拓扑:STT 语音听写 (`openDictation`) 与 24kHz 宽频带 TTS 朗读 (`openReading`) 在 Claude Desktop 2.0 中,语音能力不仅限于被动的音效播放或单向录音,而是构建了完整的**全双工实时流式声学管线**(源码取证自 `frame-shell-dictate-C9D4b04p.js` 与 `frame-shell-speak-CBGDxR0k.js`): ##### 1. 1 小时连续差分语音听写引擎 (`openDictation`) ```javascript // frame-shell-dictate-C9D4b04p.js var WORKLET_URL = "/assets/v1/cb2b919a8-BamlKQVF.js"; var MAX_DICTATION_DURATION = 36e5; // 3,600,000ms = 严格 1 小时持续听写上限 // 核心前缀差分增量结算算法:仅向输入框发射新增 Delta,消除光标闪烁 function computeDictationDelta(lastSettled, currentText) { if (!currentText.startsWith(lastSettled)) { return { add: "", settled: currentText }; } let delta = currentText.slice(lastSettled.length); return delta.trim() === "" ? { add: "", settled: lastSettled } : { add: delta, settled: currentText }; } async function openDictation(options) { let session = audioPipeline.dictation({ language: options.language, maxDuration: MAX_DICTATION_DURATION, settleAtPauses: true, // 核心声学断句:说话停顿时自动触发物理沉降 }); let settledText = ""; session.on("result", (event) => { // 分离暂态推测文本 (interim) 与已定稿文本 (settled) let { add, settled } = computeDictationDelta(settledText, event.settled); settledText = settled; if (add !== "") options.onFinal(add); options.onPartial(event.interim); }); session.on("state", (state) => { if (state === "idle") { options.onEnded("completed"); } }); await session.start(); return { stop: async () => session.stop(), cancel: () => session.cancel(), }; } ``` ##### 2. 24kHz 宽频带 TTS 与实时字词级高亮跟随 (`openReading`) ```javascript // frame-shell-speak-CBGDxR0k.js var TTS_WIDEBAND_SAMPLE_RATE = 24000; // 24kHz 超高采样率,达到广播级人声解析度 function openReading(options) { let reader = getVoiceSession(options.organizationId).readAloud({ voice: options.voice, sampleRate: TTS_WIDEBAND_SAMPLE_RATE, }); // 核心视觉与声学联动:实时高频捕获朗读光标位置 (Karaoke 字词高亮) reader.on("position", (pos) => { options.onPosition({ charIndex: pos.charIndex, wordLength: pos.wordLength, }); }); reader.on("state", (state) => { if (state.name === "speaking") { options.onSpeaking(); } }); return { ended: reader.speak(options.text), stop: () => reader.stop(), }; } ``` * **卡拉 OK 式高亮联动 (Karaoke Highlight Sync)**:通过 `onPosition` 事件,前端会话气泡能以毫秒级精度为当前发音的词句附加 `.cds-read-aloud-active` 高亮样式; * **断句自动沉降 (Settle at Pauses)**:听写管道借助麦克风前端静音检测(VAD),在自然呼吸间隙将 `interim` 候选词转为 `settled` 最终文本,实现零卡顿输入。 --- ### 5.2 多模态媒体查看器与画廊转场物理学 > **技术领域与英文指代**:`Lightbox, Pan/Zoom & Shared Element Transitions` #### 5.2.1 物理常数与阻尼阈值矩阵 (Extracted Physical Constants) 在 `c4b054ed5-BGjH-Uu6.js` 头部,Anthropic 确立了一组极其严苛的物理常数: ```javascript // c4b054ed5-BGjH-Uu6.js: 核心物理学与阻尼常数 var ae = 2; // 最小最大缩放倍数 (Math.max(naturalWidth / fitWidth, 2)) var oe = 4; // 手势死区过滤阈值 (Math.hypot(dx, dy) < 4px 判定为手部微抖,不触发拖拽) var se = 0.01; // 滚轮缩放指数因子 (Math.exp(-deltaY * 0.01)) var v = 1.05; // 缩放吸附下限 (当 scale <= 1.05 时自动顺滑吸附回 1.0) var ce = 1.25; // 单次触击/跳变放大默认比例 var y = 0.7; // 视口阻尼溢出系数 var le = 2; // 虚拟画廊跑道预加载跨度 (Math.abs(t - O) <= 2,前后仅预载 2 帧) ``` 在 `c6a992d55-sXiDGMFQ.css` 中提取出对应的弹窗进出场关键帧: ```css .animate-lightbox-backdrop-in { animation: .2s ease-out fade-in; } .animate-lightbox-content-in { animation: .25s ease-out scale-in; } ``` --- #### 5.2.2 View Transitions API 共享元素无缝变形(FLIP Morphing & Object-Fit 逆向计算) 在 Claude 中点击对话气泡或文件卡片里的图片时,图片**从对话流里的缩略图无缝平滑膨胀展开至全屏 Lightbox**;关闭时又能精准原路飞回缩略图。 #### 核心生产源码取证 (`cd5136de8-BDys1vAN.js`) ```javascript // 1. 动态提取生效圆角 (向上穿透 5 层 DOM 捕获真实裁剪半边距) function S(element, fallbackRadius) { let curr = element; for (let i = 0; curr && i < 5; i++) { let style = getComputedStyle(curr); if ((curr.tagName === "IMG" || style.overflow === "hidden" || style.overflow === "clip") && style.borderRadius !== "0px" && style.borderRadius !== "") { return style.borderRadius; } curr = curr.parentElement; } return fallbackRadius; } // 2. 针对 object-fit: contain 图像的精确内切剪裁百分比算法 function T(img) { if (img.tagName !== "IMG") return null; let { naturalWidth: nw, naturalHeight: nh } = img; if (!(nw > 0 && nh > 0)) return null; let style = getComputedStyle(img); if (style.objectFit !== "contain" && style.objectFit !== "scale-down") return null; let rect = img.getBoundingClientRect(); let padL = parseFloat(style.paddingLeft) || 0, padT = parseFloat(style.paddingTop) || 0; let padR = parseFloat(style.paddingRight) || 0, padB = parseFloat(style.paddingBottom) || 0; let availW = rect.width - padL - padR, availH = rect.height - padT - padB; if (!(availW > 0 && availH > 0)) return null; let scale = Math.min(availW / nw, availH / nh); if (style.objectFit === "scale-down") scale = Math.min(scale, 1); let renderW = nw * scale, renderH = nh * scale; let insetTop = (availH - renderH) / 2 + padT; let insetRight = (availW - renderW) / 2 + padR; let insetBottom = (availH - renderH) / 2 + padB; let insetLeft = (availW - renderW) / 2 + padL; let toPercent = (val, total) => `${Number((100 * val / total).toFixed(3))}%`; return { inset: `${toPercent(insetTop, rect.height)} ${toPercent(insetRight, rect.width)} ${toPercent(insetBottom, rect.height)} ${toPercent(insetLeft, rect.width)}` }; } ``` --- #### 5.2.3 仿射变换与以光标为锚点的 Pan / Zoom 物理学数学公式 当图片放大时,如果简单使用中心点缩放,用户想看细节的位置会被推移出屏幕。Claude 的核心是**以当前鼠标指针(或双指中心点)为物理世界不变不动点(Fixed Anchor Point)**: $$\begin{cases} c = \frac{S_{\text{target}}}{S_{\text{current}}} \\ \text{idealTx} = O_x - (O_x - \text{idealTx}_{\text{prev}}) \cdot c \\ \text{idealTy} = O_y - (O_y - \text{idealTy}_{\text{prev}}) \cdot c \\ \text{tx} = \text{clamp}(\text{idealTx}, \text{maxTx}) \\ \text{ty} = \text{clamp}(\text{idealTy}, \text{maxTy}) \end{cases}$$ 其中允许平移的最大物理边界 $\text{maxTx}, \text{maxTy}$ 根据视口与图像尺寸动态解算: $$\text{maxTx} = \max\left(0, \frac{\text{fitWidth} \cdot S - \text{viewportWidth}}{2}\right) \cdot \min\left(1, \max\left(0, \frac{S - 1.05}{0.95}\right)\right)$$ #### 核心生产源码取证 (`c4b054ed5-BGjH-Uu6.js`) ```javascript // 核心锚点平移缩放方程 function pe(fitInfo, currentPos, targetScale, pointerPos) { let maxTx = calcMaxTranslate(fitInfo.fitWidth, fitInfo.viewportWidth, targetScale); let maxTy = calcMaxTranslate(fitInfo.fitHeight, fitInfo.viewportHeight, targetScale); let offsetX = pointerPos ? pointerPos.clientX - fitInfo.fitCenterX : 0; let offsetY = pointerPos ? pointerPos.clientY - fitInfo.fitCenterY : 0; let c = targetScale / currentPos.scale; let idealTx = offsetX - (offsetX - currentPos.idealTx) * c; let idealTy = offsetY - (offsetY - currentPos.idealTy) * c; let tx = clamp(idealTx, maxTx); let ty = clamp(idealTy, maxTy); return { ...fitInfo, scale: targetScale, idealTx, idealTy, tx, ty, maxTx, maxTy }; } // 滚轮指数缩放监听与 4px 拖拽死区捕获 function handleWheel(e) { if (e.ctrlKey) { e.preventDefault(); let zoomRatio = Math.exp(-e.deltaY * 0.01); let newScale = Math.min(Math.max(currentScale * zoomRatio, 1), maxScale); if (newScale <= 1.05) { resetZoom(); // 自动吸附回归 1.0 } else { applyAnchorZoom(newScale, e.clientX, e.clientY); } } } ``` --- #### 5.2.4 75vw 画廊跑道视口与前后两帧预加载架构 在拥有多张图片附件(多轮会话或批量上传)的画廊中,Claude 使用了一套独特的 **75vw 弹性跑道与滑动视口模型**: ```css /* 跑道滑动位移方程 */ .gallery-runway { gap: 32px; transform: translateX(calc(12.5vw - var(--active-index) * 75vw - var(--active-index) * 32px)); transition: transform 300ms cubic-bezier(0.165, 0.84, 0.44, 1); } .gallery-slide { width: 75vw; } .gallery-slide[data-active="true"] { opacity: 1; } .gallery-slide[data-active="false"] { opacity: 0.4; cursor: pointer; } .gallery-slide[data-active="false"]:hover { opacity: 0.75; } ``` * **视口布局精髓**:每个幻灯片宽度恰好为 `75vw`(屏幕宽度的 $\frac{3}{4}$),起始偏移量设为 `12.5vw`(刚好处于屏幕正中央)。两侧各露出 `12.5vw - 32px` 的相邻图片边缘,给用户明确的“画廊可左右滑动”视觉暗示。 * **虚拟预加载机制**:通过 `Math.abs(index - currentIndex) <= 2`,跑道上只保留当前项及前后各两张的真实 DOM 与高清图片,避免在百图大聊天记录中爆掉显存。 > **生产级组件落地**:[`code-examples/MediaLightboxViewer.tsx`](../code-examples/MediaLightboxViewer.tsx) 与 [`code-examples/useClaudeSharedElementTransition.ts`](../code-examples/useClaudeSharedElementTransition.ts)。 --- ### 5.3 双栏弹性分割与折叠物理学 > **技术领域与英文指代**:`Artifacts Split-Pane Resizer & Docking Physics` 在 Artifacts 产物展示、代码编辑与长文档审查场景中,Claude 拥有精密的可拖拽左右双栏分割与折叠交互(源码取证自 `shared-22-DYCF-SVs.js` 与 `c909d93e7-BsSzOQcm.js`)。 #### 5.3.1 核心生产反编译算法取证 (`shared-22-DYCF-SVs.js`, `c909d93e7-BsSzOQcm.js`) ```javascript // 拖拽激活与光标指针跟随 w = e => { if (e.pointerId !== u) return; if (!p.current) { if (Math.abs(t) < qM && Math.abs(r) < qM) return; // qM 为 4px 移动死区 p.current = !0; d(!0); l.setAttribute("data-pane-dragging", String(c.current)); y.showCursor("move"); } // 计算基于容器宽度的浮点比例,默认锚定 0.5 (50%) let i = oN(n, c.current, BM(XM(l), parseFloat(getComputedStyle(l).getPropertyValue("--df-pane-row")) || .5, e.clientX, e.clientY)); m.current = i; iN(i); }; // Escape 键拦截与快速回滚 C = e => { if (e.key === "Escape" && p.current) { e.preventDefault(); e.stopImmediatePropagation(); // 立即终止拖拽并恢复原分割比例 _.abort(); x("cancelled"); } }; ``` #### 5.3.2 连续指针动力学与事件屏蔽护盾 (Pointer Stream & Iframe Shield) - **Iframe 事件防吞噬护盾**:当用户在主窗口拖拽分割条时,一旦光标划过展示 HTML/React 产物的子 iframe,iframe 的内部 DOM 会截断主窗口的 `pointermove` 与 `pointerup` 事件,导致拖拽死锁。Claude 的解决机制是在拖拽激活时向全局注入 `data-pane-dragging="true"`,在预览容器上方强制覆盖一层透明的 `pointer-events: none` 阻断层; - **4px 移动死区与 Escape 恢复**:设定 4px 阈值防止单击或轻微手颤激活拖拽;在拖拽过程中随时按 `Escape` 键可取消操作并瞬间回滚至先前的分割比例。 #### 5.3.3 双击居中吸附 (50/50 Snap) 与边缘折叠门限 (Docking Threshold) - **双击归位**:双击分割条上的胶囊手柄(Pill Handle),立即触发弹性重置,将视口精准平分(50/50 居中吸附); - **边缘折叠门限 (Docking)**:当拖拽比例超过临界值(如右栏宽度 < 18% 或视口像素 < 420px)时,系统自动触发吸附折叠,将右栏收起为侧边微型挂钩,为主聊天流释放完整阅读空间。 - **极值约束钳制**:左栏会话流最小保留 380px,右栏 Artifacts 最小保留 420px。 > **生产级组件落地**:[`code-examples/ArtifactsSplitPaneResizer.tsx`](../code-examples/ArtifactsSplitPaneResizer.tsx)。 --- ### 5.4 工件预览沙箱与隔离通信架构 > **技术领域与英文指代**:`Artifacts Sandbox & Epitaxy Architecture` Claude 拥有强大的动态渲染(React/SVG/Mermaid/HTML)能力,其底层运行在代号为 **Epitaxy** 的隔离容器中。 #### 5.4.1 核心生产反编译代码取证 (`c58115fda-BKnbLmi3.js` / `cd5a31703-B4nKQMBN.js`) ```javascript // c58115fda-BKnbLmi3.js: Epitaxy 权限隔离与通信门禁 // 生产环境严格排除 allow-same-origin,切断同源父 DOM 穿透逃逸路径 var EPITAXY_IFRAME_SANDBOX = [ "allow-scripts", "allow-forms", "allow-popups", "allow-modals" ].join(" "); // 7 种专有 MIME 协议分发器 var EPITAXY_MIME_REGISTRY = { "application/vnd.ant.react": "renderReactComponent", "application/vnd.ant.code": "renderCodeHighlighter", "application/vnd.ant.markdown": "renderMarkdownDoc", "application/vnd.ant.mermaid": "renderMermaidChart", "application/vnd.ant.svg": "renderRawSvg", "application/vnd.ant.html": "renderSandboxedHtml", "application/vnd.ant.latex": "renderKatexMath" }; // 用户手势激活门禁安全过滤器 window.addEventListener("message", function(event) { var data = event.data; if (!data || typeof data !== "object") return; // 严格校验消息来源源域与结构签名 if (data.channel === "epitaxy_mcp_app") { // 核心安全防御:基于浏览器原生 userActivation 拦截未经真实用户手势激发的合成事件 var hasActivation = (event.userActivation && event.userActivation.hasBeenActive) || false; if (!event.isTrusted || !hasActivation) { console.warn("[Epitaxy MCP App] ui/message without iframe-originated user activation blocked"); return; } dispatchAppMessage(data.payload); } }); ``` > **生产级组件落地**:[`code-examples/EpitaxyIframeBridge.tsx`](../code-examples/EpitaxyIframeBridge.tsx)。 --- ### 5.5 代码差异对比与多线程语法高亮系统 > **技术领域与英文指代**:`Diff Viewer, Syntax Highlighter & CodeBlock` 在生成和修改代码文件时,Claude 会在 Artifacts 面板与会话气泡内呼出其专属的代码差异比对引擎(源码取证自 `c19e90103-CvUj-0ZH.js` 与 `c6a992d55-sXiDGMFQ.css`)。 #### 5.5.1 生产级 Diff 样式变量取证 (`.epitaxy-root .epitaxy-diff`) 在官方混淆样式表 `c6a992d55-sXiDGMFQ.css` 中提取出的官方核心 Diff 变量定义: ```css .epitaxy-root .epitaxy-diff { --diffs-font-size: var(--cds-font-size-code); --diffs-line-height: var(--cds-leading-code); --diffs-font-family: var(--cds-font-mono); --diffs-header-font-family: var(--cds-font-sans); --diffs-addition-color-override: var(--cds-text-git-added); --diffs-deletion-color-override: var(--cds-text-git-removed); --diffs-modified-color-override: var(--cds-text-accent); --diffs-gap-inline: var(--cds-pad-md); --diffs-gap-block: var(--cds-pad-xs); } ``` #### 5.5.2 核心生产反编译算法取证 (`c19e90103-CvUj-0ZH.js`, `tree-sitter-Tbdz49RR.js`) 在 `c19e90103-CvUj-0ZH.js` 中提取出其流式高亮调度器 `Jh` 与 Shadow DOM 挂载函数 `Xh`: ```javascript // Shadow DOM 隔离与双栏 Grid 锁定挂载 function Xh(e, t, n, r, i) { let a = e.ownerDocument, o = a.createElement("div"), s = o.shadowRoot ?? o.attachShadow({ mode: "open" }); let c = a.createElement("style"); let l = a.createElement("style"); // 建立双栏网格 (gutter 行号栏与 content 代码内容栏) let f = a.createElement("div"); f.dataset.gutter = ""; let p = a.createElement("div"); p.dataset.content = ""; let v = 0; let b = () => { v += 1; // 左右两栏强制锁定相同网格跨度 f.style.gridRow = p.style.gridRow = `span ${v}`; }; // 增量消费 Worker 传递的 Tokens let x = Kh({ lang: t, themes: n, onTokens: ({ recall: t, tokens: n, caughtUp: r, plain: a }) => { // 增量追加 span 节点,不重绘旧行 } }); return { accepts(e, r, i) { return r === t && e.startsWith(m); // 判定前缀是否承接,承接则增量追加 }, feed(e) { let t = e.slice(m.length); m = e; t && x.push(t); // 仅向 Worker 传递新增 Delta }, settle() { x.closeWhenIdle(); } }; } ``` #### 5.5.3 Shadow DOM 隔离与 CSS Grid 行高几何锁死 - **Shadow DOM 隔离**:高亮语法包含大量的样式规则。通过 `attachShadow({ mode: "open" })`,彻底将代码块语法样式与外层 UI 样式隔离,避免 CSS 选择器层叠膨胀; - **行高物理锁死**:代码与行号分置于两栏,行内换行(Word Wrap)极易导致行号与代码行高不一致。Claude 采用 CSS Grid 布局,将行号栏 `f` 与代码栏 `p` 的网格跨度同步赋值为 `gridRow = span ${totalLines}`,从布局引擎底层抹平错位。 #### 5.5.4 代码块与复制 2.5s 状态机 (`saved-check-decay`) Claude 的代码高亮块具有独立而优雅的头部设计与状态机动效: * **容器外壳**:圆角 `rounded-xl (12px)`,边框 `border border-neutral-800`,背景深色 `#0d0d0c`。 * **头部工具栏**:高度 `36px`,左侧显示编程语言标签与对应图标(`.align-[-0.125em]`),右侧为复制按钮。 * **复制反馈 2.5s 状态机 (`saved-check-decay`)**: 点击复制代码后,复制图标瞬时切换为绿色勾选图标并展示 "Copied";经过 `2.5s` 静默展示期后,通过 CSS keyframes 自动淡隐恢复为默认复制状态: ```css @keyframes saved-check-decay { 0%, 75% { opacity: 1; transform: scale(1); } 100% { opacity: 0; transform: scale(0.92); } } ``` > **生产级组件落地**:[`code-examples/StreamingDiffViewer.tsx`](../code-examples/StreamingDiffViewer.tsx) 与 [`code-examples/CodeBlock.tsx`](../code-examples/CodeBlock.tsx)。 ---[⬅️ 上一篇:第四篇:动效物理学、着色器与环境光影](04-动效物理学、着色器与环境光影.md) · [📚 返回总目录](Claude%20Design%20System%20(CDS).md) · [下一篇:第六篇:系统级桌面架构与协作拓扑 ➡️](06-系统级桌面架构与协作拓扑.md)