Skip to content

Commit 3c2f29c

Browse files
committed
修复website悬浮球不显示的问题
1 parent ee0a380 commit 3c2f29c

File tree

2 files changed

+180
-26
lines changed

2 files changed

+180
-26
lines changed

website/src/App.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ const translations: Record<SiteLang, TranslationEntry> = {
155155
themeToggle: '切换主题',
156156
pinOn: '已置顶',
157157
pinOff: '置顶',
158-
floatingOn: '悬浮中',
158+
floatingOn: '双击返回',
159159
floatingOff: '悬浮球'
160160
},
161161
seo: {
@@ -210,7 +210,7 @@ const translations: Record<SiteLang, TranslationEntry> = {
210210
themeToggle: 'Toggle theme',
211211
pinOn: 'Pinned',
212212
pinOff: 'Pin window',
213-
floatingOn: 'Floating',
213+
floatingOn: 'Double-tap to return',
214214
floatingOff: 'Floating widget'
215215
},
216216
seo: {

website/src/components/BpmDemo.vue

Lines changed: 178 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const props = withDefaults(
3232
themeToggle: '切换主题',
3333
pinOn: '已置顶',
3434
pinOff: '置顶',
35-
floatingOn: '悬浮中',
35+
floatingOn: '双击返回',
3636
floatingOff: '悬浮球'
3737
})
3838
}
@@ -80,6 +80,18 @@ const lightTheme = {
8080
}
8181
8282
const theme = computed(() => (themeName.value === 'dark' ? darkTheme : lightTheme))
83+
const floatingWindowStyle = computed(() => {
84+
if (!floating.value) return {}
85+
const height = floatingCanvasEdge + 48
86+
return {
87+
background: 'transparent',
88+
border: 'none',
89+
boxShadow: 'none',
90+
width: `${floatingCanvasEdge + 8}px`,
91+
height: `${height}px`,
92+
padding: '24px 0 10px'
93+
}
94+
})
8395
8496
const displayBpm = computed(() => '124')
8597
const metaText = computed(() => i18n.value.metaText)
@@ -99,6 +111,19 @@ function toggleFloating() {
99111
floating.value = !floating.value
100112
}
101113
114+
function exitFloatingMode() {
115+
floating.value = false
116+
}
117+
118+
function handleFloatingDblClick() {
119+
exitFloatingMode()
120+
}
121+
122+
function handleFloatingKeydown(event: KeyboardEvent) {
123+
event.preventDefault()
124+
exitFloatingMode()
125+
}
126+
102127
function handleRefresh() {
103128
if (refreshSpin.value) return
104129
refreshSpin.value = true
@@ -156,8 +181,6 @@ const pulseEnvelope = (phase: number, attack: number, decay: number, curve = 1.5
156181
return Math.exp(-fall * (1 + curve * 0.55))
157182
}
158183
159-
const floatingCanvasRef = ref<HTMLCanvasElement | null>(null)
160-
161184
const floatingBallSize = 58
162185
const floatingBaseStroke = 1.68
163186
const floatingWidthGain = 2.24
@@ -169,6 +192,9 @@ const floatingGap = 0.12
169192
const floatingMargin = Math.ceil(floatingShadowBlur + (floatingBaseStroke + floatingWidthGain) / 2 + floatingRadiusGain + 2)
170193
const FLOATING_CANVAS_EDGE = floatingBallSize + floatingMargin * 2
171194
195+
const floatingCanvasRef = ref<HTMLCanvasElement | null>(null)
196+
const floatingCanvasEdge = FLOATING_CANVAS_EDGE
197+
172198
function hexToRgba(hex: string, alpha: number) {
173199
const h = hex.replace('#', '')
174200
if (h.length !== 3 && h.length !== 6) return `rgba(235,26,80,${alpha})`
@@ -180,6 +206,10 @@ function hexToRgba(hex: string, alpha: number) {
180206
return `rgba(${r},${g},${b},${alpha})`
181207
}
182208
209+
function colorWithAlpha(color: string, alpha: number) {
210+
return color.startsWith('#') ? hexToRgba(color, alpha) : color
211+
}
212+
183213
let floatingRafId: number | null = null
184214
185215
function stopFloatingLoop() {
@@ -214,6 +244,16 @@ function renderFloatingFrame() {
214244
const energyBoost = Math.min(2.2, 0.85 + rms * 2.4)
215245
const rBase = Math.max(8, floatingBallSize / 2 - floatingBaseStroke / 2 + floatingRadiusGain)
216246
247+
const ballBg = theme.value.panel ?? theme.value.background ?? '#14060a'
248+
ctx.save()
249+
ctx.shadowColor = hexToRgba(accent, 0.35)
250+
ctx.shadowBlur = floatingShadowBlur * 0.8
251+
ctx.fillStyle = colorWithAlpha(ballBg, 0.92)
252+
ctx.beginPath()
253+
ctx.arc(cx, cy, floatingBallSize / 2, 0, Math.PI * 2, false)
254+
ctx.fill()
255+
ctx.restore()
256+
217257
ctx.save()
218258
ctx.shadowBlur = 0
219259
ctx.lineWidth = Math.max(1.1, floatingBaseStroke * (silent ? 0.85 : 0.7))
@@ -255,6 +295,19 @@ function renderFloatingFrame() {
255295
ctx.arc(cx, cy, innerR, a0, a1, false)
256296
ctx.stroke()
257297
}
298+
299+
const textColor = theme.value.textPrimary ?? '#ffffff'
300+
const bpmFontSize = floatingBallSize * 0.38
301+
const labelFontSize = floatingBallSize * 0.14
302+
ctx.textAlign = 'center'
303+
ctx.textBaseline = 'middle'
304+
ctx.fillStyle = textColor
305+
ctx.font = `700 ${bpmFontSize}px 'Space Grotesk', 'Inter', 'Segoe UI', sans-serif`
306+
ctx.fillText(displayBpm.value, cx, cy - floatingBallSize * 0.04)
307+
308+
ctx.font = `600 ${labelFontSize}px 'Space Grotesk', 'Inter', 'Segoe UI', sans-serif`
309+
ctx.fillStyle = hexToRgba(textColor, 0.75)
310+
ctx.fillText('BPM', cx, cy + floatingBallSize * 0.3)
258311
}
259312
260313
async function startFloatingLoop() {
@@ -398,27 +451,51 @@ onUnmounted(() => {
398451
</script>
399452

400453
<template>
401-
<div class="demo-window" :style="{ background: theme.background, color: theme.textPrimary }">
402-
<div class="title">BPM</div>
403-
<div class="bpm-display" :style="{ color: bpmColor }">
404-
{{ displayBpm }}
405-
</div>
406-
<div class="meta-line">
407-
{{ metaText }}
408-
<span class="conf" :style="{ color: confColor }">{{ confText }}</span>
409-
</div>
410-
<VizPanel
411-
class="viz-panel-shell"
412-
:theme="theme"
413-
:hide-rms="false"
414-
:viz="viz"
415-
:mode="vizMode"
416-
:theme-name="themeName"
417-
:width="350"
418-
:base-height="150"
419-
@toggle="cycleVizMode"
420-
/>
421-
<div class="actions">
454+
<div
455+
class="demo-window"
456+
:class="{ 'floating-mode': floating }"
457+
:style="[{ background: theme.background, color: theme.textPrimary }, floatingWindowStyle]"
458+
>
459+
<transition name="demo-fade">
460+
<div v-if="!floating" class="demo-body">
461+
<div class="title">BPM</div>
462+
<div class="bpm-display" :style="{ color: bpmColor }">
463+
{{ displayBpm }}
464+
</div>
465+
<div class="meta-line">
466+
{{ metaText }}
467+
<span class="conf" :style="{ color: confColor }">{{ confText }}</span>
468+
</div>
469+
<VizPanel
470+
class="viz-panel-shell"
471+
:theme="theme"
472+
:hide-rms="false"
473+
:viz="viz"
474+
:mode="vizMode"
475+
:theme-name="themeName"
476+
:width="350"
477+
:base-height="150"
478+
@toggle="cycleVizMode"
479+
/>
480+
</div>
481+
</transition>
482+
<transition name="floating-fade">
483+
<div
484+
v-if="floating"
485+
class="floating-preview"
486+
role="button"
487+
tabindex="0"
488+
:title="i18n.floatingOn"
489+
:aria-label="i18n.floatingOn"
490+
@dblclick="handleFloatingDblClick"
491+
@keydown.enter.prevent="handleFloatingKeydown"
492+
@keydown.space.prevent="handleFloatingKeydown"
493+
>
494+
<canvas ref="floatingCanvasRef" class="floating-canvas" aria-hidden="true"></canvas>
495+
<span class="floating-label">{{ i18n.floatingOn }}</span>
496+
</div>
497+
</transition>
498+
<div class="actions" v-if="!floating">
422499
<button class="icon-btn" :title="i18n.refresh" @click="handleRefresh">
423500
<img
424501
:src="refreshIcon"
@@ -452,6 +529,7 @@ onUnmounted(() => {
452529
border-radius: 5px;
453530
padding: 50px 10px 24px;
454531
position: relative;
532+
overflow: hidden;
455533
box-shadow:
456534
0 25px 60px rgba(0, 0, 0, 0.55),
457535
inset 0 1px 0 rgba(255, 255, 255, 0.06);
@@ -462,6 +540,26 @@ onUnmounted(() => {
462540
gap: 0;
463541
}
464542
543+
.demo-window.floating-mode {
544+
width: auto;
545+
height: auto;
546+
min-height: 0;
547+
padding: 24px 0 14px;
548+
border: none;
549+
box-shadow: none;
550+
background: transparent;
551+
overflow: visible;
552+
gap: 12px;
553+
}
554+
555+
.demo-body {
556+
width: 100%;
557+
height: 100%;
558+
display: flex;
559+
flex-direction: column;
560+
align-items: center;
561+
}
562+
465563
.actions {
466564
position: absolute;
467565
top: 12px;
@@ -559,4 +657,60 @@ onUnmounted(() => {
559657
display: flex;
560658
justify-content: center;
561659
}
660+
661+
.floating-preview {
662+
width: 100%;
663+
min-width: 110px;
664+
min-height: 120px;
665+
display: flex;
666+
flex-direction: column;
667+
align-items: center;
668+
justify-content: center;
669+
gap: 18px;
670+
cursor: pointer;
671+
user-select: none;
672+
text-align: center;
673+
}
674+
675+
.floating-canvas {
676+
display: block;
677+
pointer-events: none;
678+
filter: drop-shadow(0 10px 18px rgba(0, 0, 0, 0.4));
679+
border-radius: 999px;
680+
}
681+
682+
.floating-label {
683+
font-size: 13px;
684+
letter-spacing: 0.08em;
685+
text-transform: none;
686+
font-weight: 600;
687+
color: v-bind('theme.subduedText');
688+
}
689+
690+
.floating-fade-enter-active,
691+
.floating-fade-leave-active {
692+
transition: opacity 160ms ease, transform 160ms ease;
693+
}
694+
695+
.floating-fade-enter-from,
696+
.floating-fade-leave-to {
697+
opacity: 0;
698+
transform: translateY(6px) scale(0.96);
699+
}
700+
701+
.demo-fade-enter-active,
702+
.demo-fade-leave-active {
703+
transition: opacity 160ms ease, transform 160ms ease;
704+
}
705+
706+
.demo-fade-enter-from,
707+
.demo-fade-leave-to {
708+
opacity: 0;
709+
transform: translateY(14px);
710+
}
711+
712+
.floating-preview:focus-visible {
713+
outline: 1px dashed rgba(235, 26, 80, 0.6);
714+
outline-offset: 6px;
715+
}
562716
</style>

0 commit comments

Comments
 (0)