diff --git a/README.md b/README.md index 07c90fc..46190bd 100644 --- a/README.md +++ b/README.md @@ -56,13 +56,13 @@ SpineViewer/ - 从 atlas 中解析它引用的贴图页面,再到库中定位实际贴图文件。 2. **版本检测**:读取骨骼数据里的版本号(`.json` 读 `skeleton.spine`,`.skel` 读二进制头部),自动选择匹配的本地运行时。 3. **预览**:查看器通过 Node `fs` 直接读取分离在各 item 文件夹中的骨骼/atlas/贴图文件(无需网络、无 CORS),用 `spine-canvas` 渲染器在 `` 上实时播放动画,并支持: - - 动画列表切换 + - 画布左上方自适应的浮动动画列表,动画较多时独立滚动,点击条目立即切换;没有动画数据时自动隐藏 - 皮肤切换 - 播放 / 暂停(空格) - 循环开关 - 播放速度调节 - 预乘 Alpha(PMA)默认读取配套 `.atlas` / `.atlas.txt` 的 `pma:true/false`;用户手动切换后保存为该素材的显式覆盖值,重新打开时继续沿用 - - 时间轴拖动、动画时长、秒刻度与关键帧标记 + - 浮动时间轴支持拖动、动画时长、秒刻度与关键帧标记;顶部控制栏同样采用浮动布局 - 零时长动画按单帧静态姿势预览 - 自动适配并居中 - 将当前帧设为自定义缩略图,并通知 Eagle 立即刷新列表缓存 diff --git a/viewer/spine.html b/viewer/spine.html index 8fc1f3f..a48a7af 100644 --- a/viewer/spine.html +++ b/viewer/spine.html @@ -32,11 +32,18 @@ background: var(--bg); color: var(--text); font-size: 13px; } - #app { display: flex; flex-direction: column; height: 100%; } + #app { + position: relative; height: 100%; + --toolbar-height: 46px; --timeline-height: 68px; + } #toolbar { + position: absolute; top: 12px; left: 12px; right: 12px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; - padding: 8px 12px; background: var(--panel); - border-bottom: 1px solid var(--border); z-index: 10; + padding: 8px 12px; + background: color-mix(in srgb, var(--panel) 92%, transparent); + border: 1px solid var(--border); border-radius: 8px; + box-shadow: 0 5px 18px rgba(0, 0, 0, .25); + backdrop-filter: blur(8px); z-index: 10; } #toolbar .spacer { flex: 1; } #toolbar label { color: var(--text-dim); display: flex; align-items: center; gap: 4px; } @@ -51,16 +58,66 @@ } button:hover { background: var(--border); } button.active { background: var(--accent); color: #fff; border-color: var(--accent); } - #stage { position: relative; flex: 1; overflow: hidden; } + #stage { position: absolute; inset: 0; display: flex; overflow: hidden; } + #animationPanel { + position: absolute; + top: calc(22px + var(--toolbar-height)); left: 12px; z-index: 9; + width: max-content; min-width: 128px; max-width: min(260px, 34vw); + max-height: calc(100% - var(--toolbar-height) - var(--timeline-height) - 56px); + display: none; flex-direction: column; overflow: hidden; + background: color-mix(in srgb, var(--panel) 92%, transparent); + border: 1px solid var(--border); border-radius: 7px; + box-shadow: 0 5px 18px rgba(0, 0, 0, .28); + backdrop-filter: blur(8px); + } + #animationPanelTitle { + padding: 8px 10px; color: var(--text-dim); font-size: 12px; + border-bottom: 1px solid var(--border); flex: none; + } + #animationList { + width: max-content; min-width: 100%; max-width: 100%; min-height: 0; + overflow: auto; padding: 5px; + scrollbar-width: thin; + scrollbar-color: color-mix(in srgb, var(--text-dim) 58%, transparent) transparent; + } + #animationList::-webkit-scrollbar { width: 8px; height: 8px; } + #animationList::-webkit-scrollbar-track { background: transparent; } + #animationList::-webkit-scrollbar-thumb { + min-height: 28px; border: 2px solid transparent; border-radius: 999px; + background: color-mix(in srgb, var(--text-dim) 58%, transparent); + background-clip: padding-box; + } + #animationList::-webkit-scrollbar-thumb:hover { + background: color-mix(in srgb, var(--text-dim) 82%, transparent); + background-clip: padding-box; + } + #animationList::-webkit-scrollbar-corner { background: transparent; } + .animation-item { + display: block; width: 100%; min-width: 116px; max-width: 248px; + padding: 6px 8px; margin: 0 0 2px; + border: 0; border-radius: 4px; background: transparent; + color: var(--text); text-align: left; overflow: hidden; + text-overflow: ellipsis; cursor: pointer; + } + .animation-item:hover { background: var(--panel-2); } + .animation-item.active { + background: color-mix(in srgb, var(--accent) 20%, var(--panel)); + color: var(--accent); box-shadow: inset 3px 0 0 var(--accent); + } + #viewport { position: relative; width: 100%; min-width: 0; flex: 1; overflow: hidden; } canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; } #status { - position: absolute; bottom: 10px; left: 12px; color: var(--text-dim); + position: absolute; bottom: calc(22px + var(--timeline-height)); left: 12px; color: var(--text-dim); font-size: 12px; pointer-events: none; text-shadow: 0 1px 2px rgba(0,0,0,.3); } #timelineBar { + position: absolute; left: 12px; right: 12px; bottom: 12px; z-index: 10; display: flex; align-items: center; gap: 10px; - padding: 6px 12px; background: var(--panel); - border-top: 1px solid var(--border); + padding: 6px 12px; + background: color-mix(in srgb, var(--panel) 92%, transparent); + border: 1px solid var(--border); border-radius: 8px; + box-shadow: 0 -4px 18px rgba(0, 0, 0, .22); + backdrop-filter: blur(8px); } #timelineLabels { display: flex; flex-direction: column; justify-content: space-between; @@ -124,9 +181,6 @@
- @@ -148,13 +202,19 @@
- -
-
-