feat: add draggable sidebar resizer (#100)
* feat: add draggable sidebar resizer - Add resizer handle between map and event panel - Sidebar width adjustable from 300px to 50% screen width - Default width remains 400px - Auto-adjust on window resize * fix: preserve map zoom when resizing sidebar Only update viewport screen size without re-fitting the map * fix: use window size for canvas to prevent map scaling on resize - Canvas size now based on window instead of container - Sidebar resize only clips the visible area, map stays same size - Remove CSS stretch on canvas element * docs: update sidebar-resizer spec with canvas fix
This commit is contained in:
@@ -27,6 +27,35 @@ const uiStore = useUiStore()
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const showSplash = ref(true)
|
||||
|
||||
// Sidebar resizer 状态。
|
||||
const sidebarWidth = ref(400)
|
||||
const isResizing = ref(false)
|
||||
const MIN_SIDEBAR_WIDTH = 300
|
||||
|
||||
function getMaxSidebarWidth() {
|
||||
return Math.floor(window.innerWidth * 0.5)
|
||||
}
|
||||
|
||||
function onResizerMouseDown(e: MouseEvent) {
|
||||
e.preventDefault()
|
||||
isResizing.value = true
|
||||
document.addEventListener('mousemove', onResizerMouseMove)
|
||||
document.addEventListener('mouseup', onResizerMouseUp)
|
||||
}
|
||||
|
||||
function onResizerMouseMove(e: MouseEvent) {
|
||||
if (!isResizing.value) return
|
||||
const newWidth = window.innerWidth - e.clientX
|
||||
const maxWidth = getMaxSidebarWidth()
|
||||
sidebarWidth.value = Math.max(MIN_SIDEBAR_WIDTH, Math.min(newWidth, maxWidth))
|
||||
}
|
||||
|
||||
function onResizerMouseUp() {
|
||||
isResizing.value = false
|
||||
document.removeEventListener('mousemove', onResizerMouseMove)
|
||||
document.removeEventListener('mouseup', onResizerMouseUp)
|
||||
}
|
||||
const openedFromSplash = ref(false)
|
||||
|
||||
// 1. 游戏初始化逻辑
|
||||
@@ -146,14 +175,26 @@ async function handleReturnToMain() {
|
||||
}
|
||||
}
|
||||
|
||||
// 窗口 resize 时,确保 sidebar 宽度不超过最大值。
|
||||
function onWindowResize() {
|
||||
const maxWidth = getMaxSidebarWidth()
|
||||
if (sidebarWidth.value > maxWidth) {
|
||||
sidebarWidth.value = maxWidth
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', onKeydown)
|
||||
window.addEventListener('resize', onWindowResize)
|
||||
// Ensure backend language setting matches frontend preference
|
||||
settingStore.syncBackend()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', onKeydown)
|
||||
window.removeEventListener('resize', onWindowResize)
|
||||
document.removeEventListener('mousemove', onResizerMouseMove)
|
||||
document.removeEventListener('mouseup', onResizerMouseUp)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -210,7 +251,12 @@ onUnmounted(() => {
|
||||
/>
|
||||
<InfoPanelContainer />
|
||||
</div>
|
||||
<aside class="sidebar">
|
||||
<div
|
||||
class="sidebar-resizer"
|
||||
:class="{ 'is-resizing': isResizing }"
|
||||
@mousedown="onResizerMouseDown"
|
||||
></div>
|
||||
<aside class="sidebar" :style="{ width: sidebarWidth + 'px' }">
|
||||
<EventPanel />
|
||||
</aside>
|
||||
</div>
|
||||
@@ -305,12 +351,25 @@ onUnmounted(() => {
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.sidebar-resizer {
|
||||
width: 4px;
|
||||
background: transparent;
|
||||
cursor: col-resize;
|
||||
transition: background 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-resizer:hover,
|
||||
.sidebar-resizer.is-resizing {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 400px;
|
||||
background: #181818;
|
||||
border-left: 1px solid #333;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 20;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Application } from 'vue3-pixi'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useElementSize, useWindowSize } from '@vueuse/core'
|
||||
import Viewport from './Viewport.vue'
|
||||
import MapLayer from './MapLayer.vue'
|
||||
import EntityLayer from './EntityLayer.vue'
|
||||
@@ -9,7 +9,11 @@ import CloudLayer from './CloudLayer.vue'
|
||||
import { useTextures } from './composables/useTextures'
|
||||
|
||||
const container = ref<HTMLElement>()
|
||||
const { width, height } = useElementSize(container)
|
||||
const { width: containerWidth, height: containerHeight } = useElementSize(container)
|
||||
// 使用窗口大小作为 canvas 的固定尺寸,这样拖动 sidebar 时地图不会被缩放。
|
||||
const { width: windowWidth, height: windowHeight } = useWindowSize()
|
||||
const width = computed(() => windowWidth.value)
|
||||
const height = computed(() => windowHeight.value)
|
||||
const { loadBaseTextures, isLoaded } = useTextures()
|
||||
|
||||
const mapSize = ref({ width: 2000, height: 2000 })
|
||||
@@ -88,8 +92,6 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.game-canvas-container :deep(canvas) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -70,9 +70,9 @@ function fitMap() {
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => [props.screenWidth, props.screenHeight], ([w, h]) => {
|
||||
watch(() => [props.screenWidth, props.screenHeight], () => {
|
||||
if (viewport) {
|
||||
// 窗口尺寸变化时,直接重新适配地图,确保自动 Zoom 和居中
|
||||
// 窗口尺寸变化时,重新适配地图。
|
||||
fitMap()
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user