面板布局更新
This commit is contained in:
1 parent
a417c9119d
commit
7069b168de
18 files changed
+220
-145
No files matched your search
+4
-2
@@ -18,6 +18,7 @@
|
||||
<nav
|
||||
v-if="store.settings.floatingMenuEnabled"
|
||||
class="floating-section-nav"
|
||||
:class="{ 'nav-left': !store.settings.parameterPanelOnRight }"
|
||||
role="tablist"
|
||||
:aria-label="$t('settings.floatingMenu')"
|
||||
>
|
||||
@@ -107,13 +108,14 @@ function onTimelineDrag(e: PointerEvent) {
|
||||
.panel-right .parameter-panel { order: 3; }
|
||||
.stage-area { position: relative; flex: 1; min-height: 0; overflow: clip; }
|
||||
.floating-section-nav {
|
||||
position: absolute; right: 12px; top: 72px; z-index: 6; display: flex;
|
||||
width: clamp(184px, 15vw, 236px); max-height: calc(100% - 92px);
|
||||
position: absolute; right: 12px; top: 12px; z-index: 6; display: flex;
|
||||
width: clamp(184px, 15vw, 236px); max-height: calc(100% - 124px);
|
||||
flex-direction: column; gap: 5px; overflow-y: auto; box-sizing: border-box; padding: 8px;
|
||||
border: 1px solid rgba(79, 91, 121, .72); border-radius: 10px;
|
||||
background: rgba(15, 18, 29, .9); box-shadow: 0 12px 30px rgba(0, 0, 0, .36);
|
||||
scrollbar-width: thin; scrollbar-color: #3a455e transparent;
|
||||
}
|
||||
.floating-section-nav.nav-left { left: 12px; right: auto; }
|
||||
.floating-section-tab {
|
||||
display: flex; width: 100%; min-height: 40px; flex: 0 0 auto; align-items: center; gap: 9px;
|
||||
padding: 0 10px; border: 1px solid #2d3548; border-radius: 8px;
|
||||
|
||||
+16
-12
@@ -1,10 +1,18 @@
|
||||
<template>
|
||||
<div class="stage-wrap">
|
||||
<div ref="holder" class="stage-holder" :class="{ grid: store.settings.showGrid }" tabindex="0" @wheel.prevent="onWheelEvent" @pointerdown="onPanDown" @dblclick.prevent="onStageDoubleClick"></div>
|
||||
<img
|
||||
class="stage-watermark"
|
||||
:class="{ 'watermark-left-panel': !store.settings.parameterPanelOnRight }"
|
||||
src="/images/yeomai-watermark.png"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<StageToolbar
|
||||
:ctx="stageContext"
|
||||
:active-tool="activeTool"
|
||||
:hud="info"
|
||||
:show-settings="showSettings"
|
||||
:show-help="showHelp"
|
||||
@update:active-tool="activeTool = $event"
|
||||
@@ -18,10 +26,6 @@
|
||||
|
||||
<HelpDialog :show="showHelp" @close="showHelp = false" />
|
||||
|
||||
<div class="hud" v-if="info">
|
||||
{{ $t('app.systems') }}: <b class="hud-number hud-system-count">{{ info.systems }}</b> · {{ $t('app.particlesBones') }}: <b class="hud-number hud-particle-count">{{ info.particles }}</b> · {{ $t('app.rootBone') }}: <b>root</b>
|
||||
<span class="hud-tip">{{ $t('app.hudTip') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -584,10 +588,10 @@ watch(() => [store.activeObjectType, store.activeObjectId], () => pathEditing.re
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stage-wrap { position: relative; width: 100%; height: 100%; overflow: hidden; }
|
||||
.stage-wrap { position: relative; display: flex; width: 100%; height: 100%; min-height: 0; flex-direction: column; overflow: hidden; }
|
||||
/* 画布:默认纯色;开启网格时显示深色棋盘格 */
|
||||
.stage-holder {
|
||||
width: 100%; height: 100%; cursor: grab; touch-action: none;
|
||||
width: 100%; min-height: 0; flex: 1; cursor: grab; touch-action: none;
|
||||
background-color: #0e1219;
|
||||
outline: none;
|
||||
}
|
||||
@@ -596,10 +600,10 @@ watch(() => [store.activeObjectType, store.activeObjectId], () => pathEditing.re
|
||||
background-size: 28px 28px;
|
||||
}
|
||||
.stage-holder:active { cursor: grabbing; }
|
||||
.hud { position: absolute; bottom: 12px; left: 12px; padding: 8px 12px; background: rgba(20,20,32,0.75); border-radius: 8px; font-size: 12px; color: #a0a8cc; }
|
||||
.hud-number { display: inline-block; box-sizing: border-box; text-align: right; font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1; }
|
||||
.hud-system-count { width: 3ch; }
|
||||
.hud-particle-count { width: 7ch; text-align: left; }
|
||||
.hud-tip { margin-left: 12px; color: #5f6a8a; font-size: 11px; }
|
||||
b { color: #ffcc33; }
|
||||
.stage-watermark {
|
||||
position: absolute; top: 14px; left: 14px; z-index: 1;
|
||||
width: clamp(105px, 13.5vw, 150px); height: auto;
|
||||
opacity: .8; pointer-events: none; user-select: none;
|
||||
}
|
||||
.stage-watermark.watermark-left-panel { left: auto; right: 14px; }
|
||||
</style>
|
||||
@@ -1,13 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import { useParticleStore } from '../store/particleStore'
|
||||
import { setUndoLimit } from '../editor/editorHistory'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
defineProps<{ show: boolean }>()
|
||||
const props = defineProps<{ show: boolean }>()
|
||||
const emit = defineEmits<{ (e: 'close'): void }>()
|
||||
|
||||
const store = useParticleStore()
|
||||
const { t } = useI18n()
|
||||
const panelRef = ref<HTMLElement | null>(null)
|
||||
|
||||
watch(
|
||||
() => props.show,
|
||||
async (show) => {
|
||||
if (!show) return
|
||||
await nextTick()
|
||||
panelRef.value?.focus()
|
||||
},
|
||||
)
|
||||
|
||||
function onFocusOut(event: FocusEvent) {
|
||||
const next = event.relatedTarget as Node | null
|
||||
if (next && panelRef.value?.contains(next)) return
|
||||
requestAnimationFrame(() => {
|
||||
if (props.show && !panelRef.value?.contains(document.activeElement)) emit('close')
|
||||
})
|
||||
}
|
||||
|
||||
function onUndoLimitChange(event: Event) {
|
||||
setUndoLimit(store, Number((event.target as HTMLInputElement).value))
|
||||
@@ -20,7 +39,7 @@ function normalizeBoneDebugSizes() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="show" class="settings-panel">
|
||||
<div v-if="show" ref="panelRef" class="settings-panel" tabindex="-1" @focusout="onFocusOut">
|
||||
<div class="sp-head">{{ t('settings.title') }} <button class="sp-close" @click="emit('close')">✕</button></div>
|
||||
<div class="sp-section-title">{{ t('settings.layout') }}</div>
|
||||
<label class="sp-switch-row">
|
||||
@@ -105,10 +124,11 @@ function normalizeBoneDebugSizes() {
|
||||
|
||||
<style scoped>
|
||||
.settings-panel {
|
||||
position: absolute; top: 52px; left: 10px; width: 240px; z-index: 10;
|
||||
position: absolute; bottom: 92px; left: 10px; width: 240px; z-index: 30;
|
||||
background: rgba(20,20,32,0.97); border: 1px solid #2a2a3c; border-radius: 10px; padding: 10px 12px;
|
||||
font-size: 12px; color: #aab; box-shadow: 0 6px 20px rgba(0,0,0,0.4);
|
||||
}
|
||||
.settings-panel:focus { outline: none; }
|
||||
.sp-head { display: flex; justify-content: space-between; align-items: center; font-weight: 700; color: #cdf; margin-bottom: 10px; }
|
||||
.sp-section-title { margin: 2px 0 8px; color: #8a93bb; font-size: 11px; }
|
||||
.sp-switch-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; color: #aab; }
|
||||
|
||||
+89
-59
@@ -9,6 +9,7 @@ import { setAppLocale, SUPPORTED_LOCALES, type AppLocale } from '../i18n'
|
||||
const props = defineProps<{
|
||||
ctx: StageContext
|
||||
activeTool: '' | 'translate' | 'rotate' | 'scale'
|
||||
hud: { systems: number; particles: number } | null
|
||||
showSettings: boolean
|
||||
showHelp: boolean
|
||||
}>()
|
||||
@@ -67,76 +68,105 @@ function changeLanguage(event: Event) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="topbar">
|
||||
<button class="settings-btn" @click="toggleSettings" :title="t('toolbar.settings')">{{ t('toolbar.settings') }}</button>
|
||||
<button class="undo-btn" :disabled="editorHistoryState.undoCount === 0" @click="undo" :title="t('toolbar.undo')">↶</button>
|
||||
<div class="top-operation-stack">
|
||||
<div class="topbar">
|
||||
<button class="settings-btn" @click="toggleSettings" :title="t('toolbar.settings')">{{ t('toolbar.settings') }}</button>
|
||||
<button class="undo-btn" :disabled="editorHistoryState.undoCount === 0" @click="undo" :title="t('toolbar.undo')">↶</button>
|
||||
|
||||
<!-- 根变换工具:拖动(默认) / 位移 / 旋转 / 缩放 -->
|
||||
<div class="tool-group">
|
||||
<button class="tool-btn" :class="{ on: activeTool === '' }" @click="toggleTool('')" :title="t('toolbar.pan')">✋</button>
|
||||
<button class="tool-btn" :class="{ on: activeTool === 'translate' }" @click="toggleTool('translate')" :title="t('toolbar.translate')">✥</button>
|
||||
<button class="tool-btn" :class="{ on: activeTool === 'rotate' }" @click="toggleTool('rotate')" :title="t('toolbar.rotate')">⟳</button>
|
||||
<button class="tool-btn" :class="{ on: activeTool === 'scale' }" @click="toggleTool('scale')" :title="t('toolbar.scale')">⤢</button>
|
||||
</div>
|
||||
<!-- 根变换工具:拖动(默认) / 位移 / 旋转 / 缩放 -->
|
||||
<div class="tool-group">
|
||||
<button class="tool-btn" :class="{ on: activeTool === '' }" @click="toggleTool('')" :title="t('toolbar.pan')">✋</button>
|
||||
<button class="tool-btn" :class="{ on: activeTool === 'translate' }" @click="toggleTool('translate')" :title="t('toolbar.translate')">✥</button>
|
||||
<button class="tool-btn" :class="{ on: activeTool === 'rotate' }" @click="toggleTool('rotate')" :title="t('toolbar.rotate')">⟳</button>
|
||||
<button class="tool-btn" :class="{ on: activeTool === 'scale' }" @click="toggleTool('scale')" :title="t('toolbar.scale')">⤢</button>
|
||||
</div>
|
||||
|
||||
<!-- 工具按钮组(参考画布工具栏) -->
|
||||
<div class="tool-group">
|
||||
<button class="tool-btn" @click="zoomIn(ctx)" :title="t('toolbar.zoomIn')">+</button>
|
||||
<button class="tool-btn" @click="zoomOut(ctx)" :title="t('toolbar.zoomOut')">-</button>
|
||||
<button class="tool-btn" @click="fitView(ctx)" :title="t('toolbar.fitView')">⤢</button>
|
||||
<button class="tool-btn" :class="{ on: store.settings.showGrid }" @click="toggleGrid(ctx)" :title="t('toolbar.toggleGrid')">▦</button>
|
||||
<button class="tool-btn" @click="resetView(ctx)" :title="t('toolbar.resetView')">⟳</button>
|
||||
</div>
|
||||
<!-- 工具按钮组(参考画布工具栏) -->
|
||||
<div class="tool-group">
|
||||
<button class="tool-btn" @click="zoomIn(ctx)" :title="t('toolbar.zoomIn')">+</button>
|
||||
<button class="tool-btn" @click="zoomOut(ctx)" :title="t('toolbar.zoomOut')">-</button>
|
||||
<button class="tool-btn" @click="fitView(ctx)" :title="t('toolbar.fitView')">⤢</button>
|
||||
<button class="tool-btn" :class="{ on: store.settings.showGrid }" @click="toggleGrid(ctx)" :title="t('toolbar.toggleGrid')">▦</button>
|
||||
<button class="tool-btn" @click="resetView(ctx)" :title="t('toolbar.resetView')">⟳</button>
|
||||
</div>
|
||||
|
||||
<div class="background-menu" @click.stop>
|
||||
<button
|
||||
class="background-btn"
|
||||
:class="{ on: showBackgroundMenu }"
|
||||
:title="t('toolbar.backgroundSettings')"
|
||||
@click="toggleBackgroundMenu"
|
||||
>{{ t('toolbar.background') }}</button>
|
||||
<div v-if="showBackgroundMenu" class="background-panel">
|
||||
<button class="background-upload" @click="backgroundFileInput?.click()">
|
||||
{{ backgroundFileName ? t('toolbar.changeBackground') : t('toolbar.uploadBackground') }}
|
||||
</button>
|
||||
<button class="background-clear" :disabled="!backgroundFileName" @click="clearBackground()">{{ t('toolbar.clearBackground') }}</button>
|
||||
<input
|
||||
ref="backgroundFileInput"
|
||||
class="background-file-input"
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
@change="onBackgroundFileChange"
|
||||
/>
|
||||
<div class="background-file-name" :title="backgroundFileName">{{ backgroundFileName || t('toolbar.noBackground') }}</div>
|
||||
<div class="background-position-row">
|
||||
<label><span>X</span><input v-model.number="store.settings.backgroundX" type="number" step="1" :aria-label="t('toolbar.backgroundX')" /></label>
|
||||
<label><span>Y</span><input v-model.number="store.settings.backgroundY" type="number" step="1" :aria-label="t('toolbar.backgroundY')" /></label>
|
||||
<div class="background-menu" @click.stop>
|
||||
<button
|
||||
class="background-btn"
|
||||
:class="{ on: showBackgroundMenu }"
|
||||
:title="t('toolbar.backgroundSettings')"
|
||||
@click="toggleBackgroundMenu"
|
||||
>{{ t('toolbar.background') }}</button>
|
||||
<div v-if="showBackgroundMenu" class="background-panel">
|
||||
<button class="background-upload" @click="backgroundFileInput?.click()">
|
||||
{{ backgroundFileName ? t('toolbar.changeBackground') : t('toolbar.uploadBackground') }}
|
||||
</button>
|
||||
<button class="background-clear" :disabled="!backgroundFileName" @click="clearBackground()">{{ t('toolbar.clearBackground') }}</button>
|
||||
<input
|
||||
ref="backgroundFileInput"
|
||||
class="background-file-input"
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
@change="onBackgroundFileChange"
|
||||
/>
|
||||
<div class="background-file-name" :title="backgroundFileName">{{ backgroundFileName || t('toolbar.noBackground') }}</div>
|
||||
<div class="background-position-row">
|
||||
<label><span>X</span><input v-model.number="store.settings.backgroundX" type="number" step="1" :aria-label="t('toolbar.backgroundX')" /></label>
|
||||
<label><span>Y</span><input v-model.number="store.settings.backgroundY" type="number" step="1" :aria-label="t('toolbar.backgroundY')" /></label>
|
||||
</div>
|
||||
<label class="background-opacity-row">
|
||||
<span>{{ t('toolbar.opacity') }}</span>
|
||||
<input v-model.number="store.settings.backgroundOpacity" type="range" min="0" max="1" step="0.01" :aria-label="t('toolbar.backgroundOpacity')" />
|
||||
<b>{{ Math.round(store.settings.backgroundOpacity * 100) }}%</b>
|
||||
</label>
|
||||
</div>
|
||||
<label class="background-opacity-row">
|
||||
<span>{{ t('toolbar.opacity') }}</span>
|
||||
<input v-model.number="store.settings.backgroundOpacity" type="range" min="0" max="1" step="0.01" :aria-label="t('toolbar.backgroundOpacity')" />
|
||||
<b>{{ Math.round(store.settings.backgroundOpacity * 100) }}%</b>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="language-picker">
|
||||
<span>{{ t('language.label') }}</span>
|
||||
<select :value="locale" :aria-label="t('language.label')" @change="changeLanguage">
|
||||
<option v-for="entry in SUPPORTED_LOCALES" :key="entry.code" :value="entry.code">{{ entry.label }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<button class="help-btn" :class="{ on: showHelp }" :title="t('toolbar.openHelp')" @click="openHelp">{{ t('toolbar.help') }}</button>
|
||||
<span class="zoom-pct">{{ Math.round(store.editor.viewScale * 100) }}%</span>
|
||||
</div>
|
||||
<div class="timeline-controls-row">
|
||||
<div id="timeline-controls-host" class="timeline-controls-host"></div>
|
||||
<div v-if="hud" class="stage-hud">
|
||||
{{ t('app.systems') }}: <b class="hud-number hud-system-count">{{ hud.systems }}</b> · {{ t('app.particlesBones') }}: <b class="hud-number hud-particle-count">{{ hud.particles }}</b> · {{ t('app.rootBone') }}: <b>root</b>
|
||||
<span class="hud-tip">{{ t('app.hudTip') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="language-picker">
|
||||
<span>{{ t('language.label') }}</span>
|
||||
<select :value="locale" :aria-label="t('language.label')" @change="changeLanguage">
|
||||
<option v-for="entry in SUPPORTED_LOCALES" :key="entry.code" :value="entry.code">{{ entry.label }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<button class="help-btn" :class="{ on: showHelp }" :title="t('toolbar.openHelp')" @click="openHelp">{{ t('toolbar.help') }}</button>
|
||||
<span class="zoom-pct">{{ Math.round(store.editor.viewScale * 100) }}%</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.topbar {
|
||||
position: absolute; top: 10px; left: 10px; right: 10px; display: flex; align-items: center; gap: 10px;
|
||||
padding: 8px 12px; background: rgba(20,20,32,0.85); border-radius: 10px; border: 1px solid rgba(255,255,255,0.08);
|
||||
font-size: 12px; color: #aab; z-index: 5; flex-wrap: wrap;
|
||||
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
||||
font-size: 12px; color: #aab;
|
||||
}
|
||||
.top-operation-stack {
|
||||
position: relative; z-index: 20; flex: 0 0 auto;
|
||||
display: flex; flex-direction: column; gap: 6px; padding: 8px 12px;
|
||||
border-top: 1px solid #2a2a3c; background: #12121c;
|
||||
}
|
||||
.timeline-controls-host {
|
||||
min-width: 0;
|
||||
}
|
||||
.timeline-controls-row {
|
||||
position: relative; min-width: 0; padding-top: 6px; border-top: 1px solid #2a2a3c;
|
||||
}
|
||||
.stage-hud {
|
||||
position: absolute; right: 42px; top: 6px; bottom: 0;
|
||||
display: flex; align-items: center; color: #8390ab; font-size: 10px; line-height: 1;
|
||||
white-space: nowrap; pointer-events: none;
|
||||
}
|
||||
.hud-number { display: inline-block; box-sizing: border-box; text-align: right; font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1; }
|
||||
.hud-system-count { width: 3ch; }
|
||||
.hud-particle-count { width: 7ch; text-align: left; }
|
||||
.hud-tip { margin-left: 10px; color: #59657e; font-size: 10px; }
|
||||
b { color: #ffcc33; }
|
||||
.topbar input[type=range] { background: #1a1a28; border: 1px solid #2e2e44; color: #dde; border-radius: 5px; }
|
||||
/* 工具按钮组 */
|
||||
.tool-group { display: flex; gap: 6px; margin-left: 6px; padding-left: 12px; border-left: 1px solid #2a2a3c; }
|
||||
@@ -153,7 +183,7 @@ function changeLanguage(event: Event) {
|
||||
}
|
||||
.background-btn:hover, .background-btn.on { border-color: #7774ff; background: #343b63; color: #fff; }
|
||||
.background-panel {
|
||||
position: absolute; top: calc(100% + 9px); right: 0; width: 248px; box-sizing: border-box; padding: 10px;
|
||||
position: absolute; right: 0; bottom: calc(100% + 9px); width: 248px; box-sizing: border-box; padding: 10px;
|
||||
border: 1px solid #343b50; border-radius: 9px; background: rgba(20,20,32,.98); color: #aeb8cc;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,.48); z-index: 12;
|
||||
}
|
||||
|
||||
+21
-4
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="timeline">
|
||||
<!-- 控制按钮 -->
|
||||
<Teleport defer to="#timeline-controls-host">
|
||||
<div class="tl-controls">
|
||||
<button class="tl-btn" :class="{ on: store.timeline.playing }" @click="store.togglePlay()" :title="store.timeline.playing ? $t('timeline.pause') : $t('timeline.play')">
|
||||
{{ store.timeline.playing ? '❚❚' : '▶' }}
|
||||
@@ -65,6 +66,7 @@
|
||||
</label>
|
||||
<span class="tl-zoom">{{ zoomLabel }}</span>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<!-- 时间轴视口 -->
|
||||
<div
|
||||
@@ -145,6 +147,8 @@ type SysLike = { id: number; config: any }
|
||||
const store = useParticleStore()
|
||||
const { t } = useI18n()
|
||||
const vpRef = ref<HTMLElement | null>(null)
|
||||
const viewportWidth = ref(0)
|
||||
let viewportResizeObserver: ResizeObserver | null = null
|
||||
const animationMenuRef = ref<HTMLElement | null>(null)
|
||||
const animationMenuOpen = ref(false)
|
||||
const animationNameDraft = ref('animation')
|
||||
@@ -203,8 +207,21 @@ function onDocumentPointerDown(event: PointerEvent) {
|
||||
if (!animationMenuRef.value?.contains(event.target as Node)) animationMenuOpen.value = false
|
||||
}
|
||||
|
||||
onMounted(() => document.addEventListener('pointerdown', onDocumentPointerDown))
|
||||
onBeforeUnmount(() => document.removeEventListener('pointerdown', onDocumentPointerDown))
|
||||
onMounted(() => {
|
||||
document.addEventListener('pointerdown', onDocumentPointerDown)
|
||||
const viewport = vpRef.value
|
||||
if (!viewport) return
|
||||
viewportWidth.value = viewport.clientWidth
|
||||
viewportResizeObserver = new ResizeObserver(([entry]) => {
|
||||
viewportWidth.value = Math.max(1, entry.contentRect.width)
|
||||
})
|
||||
viewportResizeObserver.observe(viewport)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('pointerdown', onDocumentPointerDown)
|
||||
viewportResizeObserver?.disconnect()
|
||||
viewportResizeObserver = null
|
||||
})
|
||||
|
||||
// 时间轴与固定步长模拟统一读取 Store;默认 1 秒 = 30 帧,与 Spine 对齐。
|
||||
const fps = computed(() => Math.max(1, store.timeline.fps || 30))
|
||||
@@ -258,7 +275,7 @@ function commitPlaybackEnd() {
|
||||
|
||||
// 每像素代表的帧数(视口宽度内放下 viewFrames 帧)
|
||||
function pxPerF() {
|
||||
const vw = vpRef.value?.clientWidth || 600
|
||||
const vw = viewportWidth.value || vpRef.value?.clientWidth || 600
|
||||
return viewFrames.value / vw
|
||||
}
|
||||
|
||||
@@ -494,7 +511,7 @@ function onSpineBarDown(e: PointerEvent, spine: SpineSceneObject) {
|
||||
|
||||
<style scoped>
|
||||
.timeline { height: 100%; background: #12121c; border-top: 1px solid #2a2a3c; display: flex; flex-direction: column; padding: 6px 10px; gap: 4px; }
|
||||
.tl-controls { display: flex; align-items: center; gap: 8px; }
|
||||
.tl-controls { display: flex; width: 100%; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||
.tl-btn { width: 30px; height: 26px; background: #1e1e2e; border: 1px solid #2e2e44; border-radius: 6px; color: #aab; font-size: 12px; cursor: pointer; }
|
||||
.tl-btn.on { background: #3a5a8c; color: #fff; border-color: #5a7ab8; }
|
||||
.tl-ab-loop { position: relative; }
|
||||
|
||||
Reference in new issue
Block a user