feat(metronome): walker 区改用 MP4 视频,playbackRate 跟 BPM 同步
将之前的 PNG runner + CSS 律动方案,升级为真正的卡通 walk cycle 视频: - 删除 runner.png(静态图无法表现关节运动) - 新增 runner.mp4(LottieFiles 卡通跑步动画,150x150, 60fps, 27KB) - walker 区从 image + CSS 抖动 改为 video + playback-rate 调速 - 视频原速基准 124 BPM(0.97s 一个 walk cycle = 2 步) - playbackRate = clamp(bpm/124, 0.5, 2.0),贴合微信小程序限制 - 视频与节拍器同步策略: · watch isPlaying → 自动 play/pause · watch playbackRate → 调用 videoCtx.playbackRate API 兜底 · onMounted 默认暂停,等用户点开始才播 - 视频白底跟暗色卡片冲突,改用"嵌入式高亮卡片"设计: · runner-card 白底 + 32rpx 圆角 + 阴影,像专业健康 APP · video object-fit contain 保证不被裁切 - 移除冗余的 .ground 地面线样式 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -28,15 +28,24 @@
|
||||
</view>
|
||||
|
||||
<view class="walker" :class="{ playing: isPlaying }">
|
||||
<view class="runner-wrap">
|
||||
<image
|
||||
class="runner"
|
||||
src="/static/training/runner.png"
|
||||
mode="aspectFit"
|
||||
<view class="runner-card">
|
||||
<video
|
||||
id="runner-video"
|
||||
class="runner-video"
|
||||
src="/static/training/runner.mp4"
|
||||
:muted="true"
|
||||
:loop="true"
|
||||
:autoplay="true"
|
||||
:playback-rate="playbackRate"
|
||||
:show-controls="false"
|
||||
:show-fullscreen-btn="false"
|
||||
:show-play-btn="false"
|
||||
:show-center-play-btn="false"
|
||||
:enable-progress-gesture="false"
|
||||
:show-mute-btn="false"
|
||||
object-fit="contain"
|
||||
/>
|
||||
<view class="runner-shadow" />
|
||||
</view>
|
||||
<view class="ground" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -78,10 +87,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onUnmounted } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { onShow, onHide } from '@dcloudio/uni-app'
|
||||
import { useMetronome } from '@/hooks/useMetronome'
|
||||
|
||||
/**
|
||||
* 视频原速对应的 BPM 基准
|
||||
* runner.mp4 时长 0.97s, 一个 walk cycle (左右脚各一次) ≈ 124 BPM 步频
|
||||
* 用户调速时 playbackRate = bpm / VIDEO_BASE_BPM
|
||||
* 微信小程序限制 playback-rate 范围 [0.5, 2.0]
|
||||
*/
|
||||
const VIDEO_BASE_BPM = 124
|
||||
|
||||
const METER_OPTIONS = [
|
||||
{ label: '2', value: 2 },
|
||||
{ label: '3', value: 3 },
|
||||
@@ -126,6 +143,36 @@ const togglePlay = () => {
|
||||
}
|
||||
}
|
||||
|
||||
/* 视频实例与 BPM 同步 */
|
||||
let videoCtx: UniApp.VideoContext | null = null
|
||||
|
||||
const playbackRate = computed(() => {
|
||||
const rate = bpm.value / VIDEO_BASE_BPM
|
||||
return Math.max(0.5, Math.min(2.0, +rate.toFixed(2)))
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
videoCtx = uni.createVideoContext('runner-video')
|
||||
/* 进页面默认让视频保持暂停,跟节拍器 isPlaying 状态同步 */
|
||||
setTimeout(() => videoCtx?.pause(), 50)
|
||||
})
|
||||
|
||||
/* 节拍器状态变化 → 同步视频播放/暂停 */
|
||||
watch(isPlaying, (playing) => {
|
||||
if (playing) {
|
||||
videoCtx?.play()
|
||||
} else {
|
||||
videoCtx?.pause()
|
||||
}
|
||||
})
|
||||
|
||||
/* BPM 变化 → 调整视频播放速度(部分基础库需要手动 API 调用) */
|
||||
watch(playbackRate, (rate) => {
|
||||
try {
|
||||
videoCtx?.playbackRate?.(rate)
|
||||
} catch (_) {}
|
||||
})
|
||||
|
||||
const rootStyle = computed(() => ({
|
||||
'--beat-duration': `${intervalMs.value}ms`,
|
||||
'--step-duration': `${intervalMs.value}ms`,
|
||||
@@ -412,103 +459,28 @@ $radius-btn: 20rpx;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.walker .ground {
|
||||
position: absolute;
|
||||
bottom: 22rpx;
|
||||
left: 10%;
|
||||
right: 10%;
|
||||
height: 1rpx;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.18) 30%,
|
||||
rgba(255, 255, 255, 0.18) 70%,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 跑步小人(静态 PNG + CSS 律动)
|
||||
* 跑步小人视频(白卡嵌入)
|
||||
* ============================================================ */
|
||||
.runner-wrap {
|
||||
.runner-card {
|
||||
position: absolute;
|
||||
bottom: 18rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
width: 220rpx;
|
||||
height: 220rpx;
|
||||
background: #fff;
|
||||
border-radius: 32rpx;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 12rpx 32rpx rgba(0, 0, 0, 0.25),
|
||||
inset 0 0 0 1rpx rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.runner {
|
||||
.runner-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform-origin: bottom center;
|
||||
/* 默认状态保持微妙的呼吸感,不至于完全静止 */
|
||||
transition: filter 0.3s;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 跟节拍同步: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(2rpx) rotate(-1.5deg) scale(1, 0.99);
|
||||
}
|
||||
50% {
|
||||
/* 离地最高点,身体抬起 */
|
||||
transform: translateY(-14rpx) rotate(0deg) scale(1, 1.01);
|
||||
}
|
||||
75% {
|
||||
/* 另一脚着地半程,反向倾斜 */
|
||||
transform: translateY(2rpx) rotate(1.5deg) scale(1, 0.99);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0) rotate(0deg) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes runner-shadow-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
transform: scaleX(1);
|
||||
}
|
||||
50% {
|
||||
/* 跳起时阴影变小变淡 */
|
||||
opacity: 0.2;
|
||||
transform: scaleX(0.7);
|
||||
}
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
Reference in New Issue
Block a user