diff --git a/pages/home/home.js b/pages/home/home.js index 08f040a..e188712 100644 --- a/pages/home/home.js +++ b/pages/home/home.js @@ -66,8 +66,8 @@ Page({ a: '在 Node 里用 @napi-rs/canvas 跨 item 文件夹读取资源渲染首帧,尺寸 400×400,右下角为 Spine 官方 badge,左下角是完整版本号。移除这个依赖只影响缩略图,画面预览不受影响。', }, { - q: '试用版和插件是什么关系?', - a: '试用版只支持 Spine 4.2 导出的文件,用来快速看一眼效果;3.4–4.3 全版本识别、皮肤切换、时间轴、缩略图这些完整能力都在桌面插件里。', + q: '小程序版和插件是什么关系?', + a: '小程序版支持 Spine 4.2 导出的文件,用来在手机上快速看一眼效果;3.4–4.3 全版本识别、皮肤切换、时间轴、缩略图这些完整能力都在桌面插件里。', }, ], }, diff --git a/pages/home/home.wxml b/pages/home/home.wxml index 395677a..5e68e55 100644 --- a/pages/home/home.wxml +++ b/pages/home/home.wxml @@ -11,7 +11,7 @@ 查看套餐 - 试用版体验 + 小程序版体验 @@ -29,9 +29,9 @@ - 这里是试用版 - 试用版只支持 Spine 4.2 导出的文件;桌面插件的完整能力包括 3.4–4.3 全版本识别、皮肤切换、时间轴、缩略图生成。 - 去试用版看看 → + 小程序版 + 小程序版支持 Spine 4.2 导出的文件,可以先在手机上看看效果;桌面插件的完整能力包括 3.4–4.3 全版本识别、皮肤切换、时间轴、缩略图生成。 + 去小程序版看看 → diff --git a/pages/viewer/viewer.js b/pages/viewer/viewer.js index 2d7555f..d9ae22c 100644 --- a/pages/viewer/viewer.js +++ b/pages/viewer/viewer.js @@ -43,6 +43,7 @@ Page({ _ty: 0, _fitOffset: null, _fitSize: null, + _lastSizeCheck: 0, _lastTime: 0, _raf: null, _ready: false, @@ -80,10 +81,15 @@ Page({ wx.createSelectorQuery() .in(this) .select('#spineCanvas') - .fields({ node: true, size: true }) + .fields({ node: true }) + // 画布节点返回的 size 是缓冲区尺寸(不是当前布局尺寸), + // 所以量外面这层普通 view 来定缓冲区大小,否则布局一变画面就会被拉伸 + .select('#stageBox') + .fields({ size: true }) .exec((res) => { - const hit = res && res[0] - if (!hit || !hit.node) { + const canvasHit = res && res[0] + const stageHit = res && res[1] + if (!canvasHit || !canvasHit.node) { this.setData({ status: 'error', errorMsg: '取不到 canvas 节点,请确认基础库支持 ', @@ -91,8 +97,8 @@ Page({ return } - const canvas = hit.node - const size = hit.size || { width: 300, height: 300 } + const canvas = canvasHit.node + const size = (stageHit && stageHit.size) || { width: 300, height: 300 } let dpr = 1 try { @@ -158,11 +164,11 @@ Page({ if (!this._canvas || !this._gl) return wx.createSelectorQuery() .in(this) - .select('#spineCanvas') - .fields({ node: true, size: true }) + .select('#stageBox') + .fields({ size: true }) .exec((res) => { const hit = res && res[0] - if (!hit || !hit.node || !hit.size) return + if (!hit || !hit.size) return const size = hit.size if (!size.width || !size.height) return @@ -470,6 +476,15 @@ Page({ if (!canvas || !gl || !this._skeleton) return const now = Date.now() + + // 面板内容变化(比如加载后多出播放/参数区)会改变画布显示尺寸。 + // 只在加载后校对一次是不够的:那次可能跑在布局生效之前,之后就再也不会纠正, + // 缓冲区与显示区比例不一致就会让画面被拉伸。这里定期校对,尺寸没变时直接返回,开销很小。 + if (now - this._lastSizeCheck > 400) { + this._lastSizeCheck = now + this.syncCanvasSize() + } + let dt = (now - this._lastTime) / 1000 this._lastTime = now if (!(dt > 0)) dt = 0 @@ -487,8 +502,10 @@ Page({ const mvp = env.buildMVP(scale, this._tx, this._ty, W, H) gl.viewport(0, 0, W, H) - // 深色画布背景(与 UI 的深色预览区保持一致) - gl.clearColor(0.12, 0.13, 0.16, 1) + // 透明清屏:canvas 是原生组件、层级在 WebView 之上, + // 用不透明颜色清屏会盖住压在它上面的浮层(缩放按钮、手势提示、信息徽标)。 + // 背景交给舞台的 CSS 渐变,观感一致,但浮层能正常显示。 + gl.clearColor(0, 0, 0, 0) gl.clear(gl.COLOR_BUFFER_BIT) // 预乘开关:同时影响渲染器的混色方式与 GL 混合函数 const pma = this.data.premultiplied diff --git a/pages/viewer/viewer.wxml b/pages/viewer/viewer.wxml index 4a2a885..c31071d 100644 --- a/pages/viewer/viewer.wxml +++ b/pages/viewer/viewer.wxml @@ -1,7 +1,7 @@ - + {{info}} + - - - - 可一次选中多个文件 · .skel / .atlas / .png + + + 单指拖动平移 · 双指捏合缩放 + + - + {{scalePercent}}% + + + 重置 + + + + @@ -55,19 +62,19 @@ {{missingText}} - 清空已选 - 播放 - - - - {{animations[animIndex]}} - ▾ - - + + 播放 + + + {{animations[animIndex]}} + ▾ + + + {{playing ? '⏸ 暂停' : '▶ 播放'}} @@ -90,18 +97,17 @@ /> {{speed}}x + - - 缩放 - - - - {{scalePercent}}% - + - - 重置 + + + + 清空已选 + - - 单指拖动平移 · 双指捏合缩放 + 可一次选中多个文件 · .skel / .atlas / .png diff --git a/pages/viewer/viewer.wxss b/pages/viewer/viewer.wxss index 704ccd4..d1a57fa 100644 --- a/pages/viewer/viewer.wxss +++ b/pages/viewer/viewer.wxss @@ -9,9 +9,12 @@ /* ===== 预览区(深色)===== */ .stage { position: relative; - flex: 1; - min-height: 0; - background: linear-gradient(180deg, var(--panel) 0%, #14161b 100%); + /* 固定高度:控制面板出现/消失都不再改变画布的显示区域, + 否则缓冲区比例会和显示区对不上,画面会被拉伸 */ + flex: none; + height: 50vh; + /* 和整页统一底色,不要再上下两种颜色 */ + background: var(--bg); } .stage-canvas { @@ -71,17 +74,75 @@ top: 22rpx; padding: 8rpx 18rpx; border-radius: var(--r-pill); - background: rgba(0, 0, 0, 0.34); - color: rgba(255, 255, 255, 0.72); + background: #24242c; + color: rgba(255, 255, 255, 0.82); + font-size: 22rpx; +} + +/* ===== 画布下方:手势提示 + 缩放控制 ===== */ +.tool-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 24rpx; +} + +.tool-hint { + flex: 1; + margin-right: 16rpx; + font-size: 21rpx; + color: rgba(255, 255, 255, 0.42); +} + +.zoom-group { + flex: none; + display: flex; + align-items: center; + padding: 4rpx; + border-radius: var(--r-pill); + background: var(--panel-2); +} + +.zoom-btn { + width: 56rpx; + height: 56rpx; + line-height: 56rpx; + text-align: center; + font-size: 30rpx; + color: rgba(255, 255, 255, 0.85); +} + +.zoom-val { + min-width: 92rpx; + text-align: center; + font-size: 24rpx; + font-weight: 600; + color: #ffffff; +} + +.zoom-reset { + height: 56rpx; + line-height: 56rpx; + padding: 0 20rpx; + margin-left: 6rpx; + border-radius: var(--r-pill); + background: #34343e; + color: rgba(255, 255, 255, 0.85); font-size: 22rpx; } /* ===== 控制面板(深色,和预览区统一)===== */ .sheet { - background: var(--bg-2); - border-radius: var(--r-lg) var(--r-lg) 0 0; + /* 同上:整页一种底色,去掉面板的圆角与投影,避免出现分界线 */ + background: var(--bg); + /* 面板自己滚动,这样它的内容变化不会挤动上面的画布 */ + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + overflow-y: auto; + -webkit-overflow-scrolling: touch; padding: 30rpx 28rpx 28rpx; - box-shadow: 0 -10rpx 36rpx rgba(0, 0, 0, 0.45); } .section { @@ -131,6 +192,36 @@ color: rgba(255, 255, 255, 0.38); } +/* 选择文件放在面板最下面 */ +.pick-section { + /* auto 让它在内容不足时贴到面板底部,内容多时自然跟在最后 */ + margin-top: auto; + padding-top: 26rpx; + margin-bottom: 0; +} + +/* 清空已选 + 重新选择文件:两个等宽按钮并排 */ +.pick-row { + display: flex; + align-items: center; +} + +.pick-row .pick-btn { + flex: 1; +} + +.clear-btn { + flex: 1; + height: 100rpx; + line-height: 100rpx; + margin-right: 20rpx; + text-align: center; + border-radius: var(--r-lg); + background: var(--panel-2); + color: rgba(255, 255, 255, 0.72); + font-size: 30rpx; +} + /* ===== 文件类型标签 ===== */ .chips { display: flex; @@ -186,18 +277,6 @@ color: #f0b45c; } -.link { - margin-top: 14rpx; - font-size: 24rpx; - color: rgba(255, 255, 255, 0.45); -} - -.link.inline { - margin-top: 0; - margin-left: 18rpx; - color: var(--cyan); -} - /* ===== 播放选择 ===== */ .select { display: flex; @@ -221,6 +300,35 @@ color: rgba(255, 255, 255, 0.4); } +/* ===== 播放(标题和动画列表同一行,开关在下面一行)===== */ +.section-head { + display: flex; + align-items: center; + margin-bottom: 14rpx; +} + +.section-head .section-title { + flex: none; + margin-right: 20rpx; + margin-bottom: 0; +} + +.anim-picker { + flex: 1; + min-width: 0; +} + +.section-head .select { + margin-bottom: 0; +} + +/* 动画名太长时省略,不要挤掉右边的按钮 */ +.select-label { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + /* ===== 分段控件 ===== */ .segment { display: flex; @@ -241,7 +349,7 @@ .seg.on { background: #3d262e; - color: var(--cyan); + color: var(--accent-2); font-weight: 600; box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.25); } @@ -273,41 +381,3 @@ font-weight: 600; color: var(--cyan); } - -/* ===== 缩放步进器 ===== */ -.stepper { - display: flex; - align-items: center; - margin-left: 16rpx; - background: var(--panel-2); - border-radius: var(--r-lg); - overflow: hidden; -} - -.step-btn { - width: 70rpx; - height: 64rpx; - line-height: 64rpx; - text-align: center; - font-size: 32rpx; - color: rgba(255, 255, 255, 0.75); -} - -.step-btn:active { - background: var(--panel-2); -} - -.step-val { - min-width: 104rpx; - text-align: center; - font-size: 25rpx; - font-weight: 600; - color: #ffffff; -} - -.gesture-hint { - margin-top: 4rpx; - text-align: center; - font-size: 22rpx; - color: rgba(255, 255, 255, 0.3); -}