From a3ee72b34d77b6256a10b98cd4348e1f6d8dcfeb Mon Sep 17 00:00:00 2001 From: long <452591453@qq.com> Date: Mon, 25 May 2026 15:45:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(metronome):=20walker=20=E5=8C=BA=E6=8E=A5?= =?UTF-8?q?=E5=85=A5=E5=8D=A1=E9=80=9A=E8=B7=91=E6=AD=A5=E5=B0=8F=E4=BA=BA?= =?UTF-8?q?,=E8=B7=9F=20BPM=20=E5=90=8C=E6=AD=A5=E5=BE=8B=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 替换原来两只脚印为一张跑步小人 PNG(static/training/runner.png) - 静态 PNG + CSS 律动: · runner-bob keyframes: 1 拍内做"下蹲→起跳→反向倾斜→落地" 四个阶段,模拟跑步身体起伏 · 上下抖动 ±14rpx, 左右轻摆 ±1.5°,scale Y 微妙压缩 · drop-shadow 让人物有立体感 - 阴影同步:runner-shadow-pulse 跟律动反向变化 (跳起时阴影变小变淡 0.2,落地时变大变浓 0.5) - 动画 duration 绑定 --step-duration = 60000/bpm · BPM 60 → 1000ms/次,慢悠悠 · BPM 120 → 500ms/次,正常跑 · BPM 180 → 333ms/次,紧张快跑 注意: 用户提供的文件实际是静态 PNG 不是 SVG 动图, 所以是"画面整体律动"而非真正的 walk cycle 关节运动。 Co-authored-by: Cursor --- TUICallKit-Vue3/pages/training/metronome.vue | 160 ++++++++----------- TUICallKit-Vue3/static/training/runner.png | Bin 0 -> 4343 bytes 2 files changed, 65 insertions(+), 95 deletions(-) create mode 100644 TUICallKit-Vue3/static/training/runner.png diff --git a/TUICallKit-Vue3/pages/training/metronome.vue b/TUICallKit-Vue3/pages/training/metronome.vue index c826a975..f2256a17 100644 --- a/TUICallKit-Vue3/pages/training/metronome.vue +++ b/TUICallKit-Vue3/pages/training/metronome.vue @@ -28,21 +28,13 @@ - + - - - - - + @@ -414,7 +406,7 @@ $radius-btn: 20rpx; .walker { position: relative; width: 100%; - height: 220rpx; + height: 240rpx; display: flex; justify-content: center; align-items: flex-end; @@ -436,108 +428,86 @@ $radius-btn: 20rpx; } /* ============================================================ - * 拟真脚印(SVG 矢量,单笔成型) + * 跑步小人(静态 PNG + CSS 律动) * ============================================================ */ -.foot { +.runner-wrap { position: absolute; - bottom: 22rpx; - width: 96rpx; - height: 144rpx; - - .footprint { - display: block; - width: 100%; - height: 100%; - transform-origin: bottom center; - } - - .shadow { - position: absolute; - bottom: -6rpx; - left: 50%; - transform: translateX(-50%); - width: 70rpx; - height: 8rpx; - background: radial-gradient( - ellipse at center, - rgba(0, 0, 0, 0.45), - transparent 70% - ); - border-radius: 50%; - } + bottom: 18rpx; + left: 50%; + transform: translateX(-50%); + width: 200rpx; + height: 200rpx; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; } -.foot-left { - left: calc(50% - 124rpx); -} -.foot-left .footprint { - transform: scaleX(-1); -} -.foot-right { - left: calc(50% + 24rpx); +.runner { + width: 100%; + height: 100%; + transform-origin: bottom center; + /* 默认状态保持微妙的呼吸感,不至于完全静止 */ + transition: filter 0.3s; } -/* 跟节拍同步:1 拍 1 步,左右各占半周期 */ -.walker.playing { - .foot-left .footprint { - animation: foot-step-left var(--step-duration, 750ms) ease-in-out infinite; - } - .foot-right .footprint { - animation: foot-step-right var(--step-duration, 750ms) ease-in-out infinite; - animation-delay: calc(var(--step-duration, 750ms) * -0.5); - } - .foot-left .shadow { - animation: shadow-pulse var(--step-duration, 750ms) ease-in-out infinite; - } - .foot-right .shadow { - animation: shadow-pulse var(--step-duration, 750ms) ease-in-out infinite; - animation-delay: calc(var(--step-duration, 750ms) * -0.5); - } +.runner-shadow { + position: absolute; + bottom: -8rpx; + width: 120rpx; + height: 12rpx; + background: radial-gradient( + ellipse at center, + rgba(0, 0, 0, 0.45), + transparent 70% + ); + border-radius: 50%; + transform-origin: center; } -@keyframes foot-step-right { - 0%, - 100% { - transform: translateY(0) rotate(0deg); +/* 跟节拍同步:1 拍 1 次律动 */ +.walker.playing .runner { + animation: runner-bob var(--step-duration, 750ms) ease-in-out infinite; + /* 高 BPM 时加速度模糊,让"跑得很快"有视觉表达 */ + filter: drop-shadow(0 4rpx 12rpx rgba(0, 0, 0, 0.25)); +} + +.walker.playing .runner-shadow { + animation: runner-shadow-pulse var(--step-duration, 750ms) ease-in-out infinite; +} + +/* 跑步律动:身体上下起伏 + 微妙左右摆动 */ +@keyframes runner-bob { + 0% { + transform: translateY(0) rotate(0deg) scale(1); } 25% { - transform: translateY(-40rpx) rotate(8deg); + /* 第一拍着地半程,身体最低 */ + transform: translateY(2rpx) rotate(-1.5deg) scale(1, 0.99); } 50% { - transform: translateY(-2rpx) rotate(0deg); + /* 离地最高点,身体抬起 */ + transform: translateY(-14rpx) rotate(0deg) scale(1, 1.01); } 75% { - transform: translateY(0) rotate(0deg); + /* 另一脚着地半程,反向倾斜 */ + transform: translateY(2rpx) rotate(1.5deg) scale(1, 0.99); + } + 100% { + transform: translateY(0) rotate(0deg) scale(1); } } -@keyframes foot-step-left { + +@keyframes runner-shadow-pulse { 0%, 100% { - transform: scaleX(-1) translateY(0) rotate(0deg); - } - 25% { - transform: scaleX(-1) translateY(-40rpx) rotate(8deg); + opacity: 0.5; + transform: scaleX(1); } 50% { - transform: scaleX(-1) translateY(-2rpx) rotate(0deg); - } - 75% { - transform: scaleX(-1) translateY(0) rotate(0deg); - } -} -@keyframes shadow-pulse { - 0%, - 100% { - opacity: 0.55; - transform: translateX(-50%) scaleX(1); - } - 25% { - opacity: 0.18; - transform: translateX(-50%) scaleX(0.5); - } - 50% { - opacity: 0.7; - transform: translateX(-50%) scaleX(1.08); + /* 跳起时阴影变小变淡 */ + opacity: 0.2; + transform: scaleX(0.7); } } diff --git a/TUICallKit-Vue3/static/training/runner.png b/TUICallKit-Vue3/static/training/runner.png new file mode 100644 index 0000000000000000000000000000000000000000..7a83be7fb40551d82aa269f613f79ccb3e2e926d GIT binary patch literal 4343 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 lnzB)O00006Nkl