From a49d0483a390731d11911122d577c00d017b0b44 Mon Sep 17 00:00:00 2001 From: sanbuphy Date: Sun, 29 Mar 2026 13:32:26 +0800 Subject: [PATCH] fix(vibe-stories): finalize laptop viewport sizing --- .../theme/components/VibeStories.vue | 89 ++++++++++++++----- 1 file changed, 67 insertions(+), 22 deletions(-) diff --git a/docs/.vitepress/theme/components/VibeStories.vue b/docs/.vitepress/theme/components/VibeStories.vue index c61a2b4..6ff5f7c 100644 --- a/docs/.vitepress/theme/components/VibeStories.vue +++ b/docs/.vitepress/theme/components/VibeStories.vue @@ -72,39 +72,50 @@ const tStories = computed(() => [ } ]) +const defaultScreenViewport = Object.freeze({ + left: 19, + top: 1.75, + width: 62.75, + height: 71.5, + radius: 12 +}) + const currentIndex = ref(0) let autoplayTimer = null const isPaginating = ref(false) const containerRef = ref(null) +const laptopRef = ref(null) let wheelHandler = null +let resizeObserver = null -const screenRegion = ref({ - centerX: 50, - centerY: 45.75, - width: 80, - height: 90, - radius: 4 -}) +const LAPTOP_ASPECT_RATIO = 2675 / 4608 +const laptopHeightPx = ref(null) + +// Visible image container geometry relative to `.laptop-container`. +// Adjust these five values directly to control the screen viewport. +const screenViewport = ref({ ...defaultScreenViewport }) const formatPercent = (value) => `${value}%` const formatPixels = (value) => `${value}px` -const screenContentStyle = computed(() => ({ - top: formatPercent(screenRegion.value.centerY - screenRegion.value.height / 2), - left: formatPercent(screenRegion.value.centerX - screenRegion.value.width / 2), - width: formatPercent(screenRegion.value.width), - height: formatPercent(screenRegion.value.height), - borderRadius: formatPixels(screenRegion.value.radius) +// The percentages below are always resolved against `.laptop-container`. +const screenViewportStyle = computed(() => ({ + '--screen-left': formatPercent(screenViewport.value.left), + '--screen-top': formatPercent(screenViewport.value.top), + '--screen-width': formatPercent(screenViewport.value.width), + '--screen-height': formatPercent(screenViewport.value.height), + '--screen-radius': formatPixels(screenViewport.value.radius) })) const currentStory = computed(() => tStories.value[currentIndex.value] ?? tStories.value[0]) -// Keep crop settings explicit in source so they survive reload/HMR. -const currentImageStyle = computed(() => ({ - objectFit: 'cover', - objectPosition: 'center center', - ...(currentStory.value?.imageStyle || {}) -})) +const currentImageStyle = computed(() => currentStory.value?.imageStyle || {}) + +const laptopContainerStyle = computed(() => ( + laptopHeightPx.value + ? { height: `${laptopHeightPx.value}px` } + : {} +)) const transitionName = ref('slide-left') @@ -149,9 +160,18 @@ const stopAutoplay = () => { } } +const updateLaptopHeight = () => { + const laptop = laptopRef.value + if (!laptop) return + + const nextHeight = laptop.clientWidth * LAPTOP_ASPECT_RATIO + laptopHeightPx.value = nextHeight > 0 ? nextHeight : null +} + onMounted(() => { startAutoplay() const container = containerRef.value + const laptop = laptopRef.value if (!container) return wheelHandler = (e) => { @@ -166,6 +186,17 @@ onMounted(() => { } container.addEventListener('wheel', wheelHandler, { passive: false }) + + updateLaptopHeight() + + if (typeof ResizeObserver !== 'undefined' && laptop) { + resizeObserver = new ResizeObserver(() => { + updateLaptopHeight() + }) + resizeObserver.observe(laptop) + } else if (typeof window !== 'undefined') { + window.addEventListener('resize', updateLaptopHeight) + } }) onUnmounted(() => { @@ -174,6 +205,13 @@ onUnmounted(() => { if (container && wheelHandler) { container.removeEventListener('wheel', wheelHandler) } + + if (resizeObserver) { + resizeObserver.disconnect() + resizeObserver = null + } else if (typeof window !== 'undefined') { + window.removeEventListener('resize', updateLaptopHeight) + } }) @@ -185,7 +223,7 @@ onUnmounted(() => {
-