智能缩放容器组件,支持自适应缩放、键盘快捷键控制滚轮缩放等功能。
✨ 特性
- 🎯 自适应缩放:根据窗口高度自动计算最佳缩放比例
- ⌨️ 键盘控制:按住 Z 键 + 滚轮进行精确缩放
- 📱 响应式设计:窗口大小变化时自动重新计算缩放
- 🎨 CSS 变量支持:通过 CSS 变量控制缩放效果
- 🧹 内存安全:组件卸载时自动清理事件监听器
- ⚡ 性能优化:防抖处理窗口 resize 事件
基础用法
vue
<template>
<AppContainer>
<div class="your-content">
<!-- 你的页面内容 -->
<h1>这里是需要缩放的内容</h1>
<p>内容会根据窗口大小自动缩放</p>
</div>
</AppContainer>
</template>
<script setup>
import AppContainer from '@/components/Layout/AppContainer.vue'
</script>
在路由页面中使用
vue
<template>
<AppContainer>
<div class="dashboard">
<header class="header">顶部导航</header>
<main class="main-content">
<div class="sidebar">侧边栏</div>
<div class="content">主要内容区域</div>
</main>
</div>
</AppContainer>
</template>
<script setup>
import AppContainer from '@/components/Layout/AppContainer.vue'
</script>
<style scoped>
.dashboard {
width: 1920px; /* 设计稿宽度 */
height: 1080px; /* 设计稿高度 */
background: #f5f5f5;
}
</style>
🎮 交互方式
键盘快捷键
- 按住 Z 键 + 滚轮 :进入缩放模式
- 向上滚动:放大
- 向下滚动:缩小
- 松开 Z 键:退出缩放模式
自动缩放
- 页面加载时自动计算最佳缩放比例
- 窗口大小改变时自动重新缩放(防抖处理)
🔧 核心代码解析
组件结构
vue
<template>
<div class="ScaleBox" ref="scaleBoxRef" :style="{
'--scale': state.scale,
}">
<slot></slot>
</div>
</template>
- 使用
slot
插槽接收子内容 - 通过 CSS 变量
--scale
控制缩放比例 ref
用于 DOM 操作
响应式状态管理
javascript
const state = reactive({
scale: 0, // 当前缩放比例
width: 1920, // 设计稿宽度
height: 1080, // 设计稿高度
initialScale: 0, // 初始缩放比例
isKeyPressed: false, // Z键是否被按下
});
核心方法详解
1. 缩放计算 getScale()
javascript
const getScale = () => {
return window.innerHeight / state.height;
};
根据窗口高度与设计稿高度的比例计算最佳缩放值。
2. 设置缩放 setScale()
javascript
const setScale = () => {
state.initialScale = state.scale = getScale();
if (scaleBoxRef.value) {
const { innerWidth } = window;
state.width = innerWidth / state.scale;
document.body.style.width = `${state.width}px`;
document.body.style.height = `${state.height}px`;
}
document.documentElement.style.setProperty("--scale", state.scale.toString());
};
- 计算并设置初始缩放比例
- 动态调整 body 尺寸
- 更新 CSS 变量
3. 滚轮缩放 zoom()
javascript
const zoom = (event) => {
event.preventDefault();
const delta = Math.sign(event.deltaY);
const newScale = state.scale + -delta * 0.04;
if (newScale < state.initialScale) {
state.scale = state.initialScale;
} else {
state.scale = newScale;
}
// 计算垂直居中位置
const windowHeight = window.innerHeight;
const newHeight = state.height * state.scale;
let top = (windowHeight - newHeight) / 2;
if (top < 0) top = 0;
if (scaleBoxRef.value) {
scaleBoxRef.value.style.setProperty("top", `${top}px`);
}
};
- 阻止默认滚轮行为
- 限制最小缩放比例
- 动态调整垂直位置保持居中
4. 防抖处理 debounce()
javascript
const debounce = (fn, delay = 500) => {
let timer = null;
return (...args) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
};
防止窗口 resize 事件频繁触发,提升性能。
事件管理
组件挂载时
javascript
onMounted(() => {
setScale();
// 窗口大小变化监听(防抖)
resizeHandler = debounce(setScale);
window.addEventListener("resize", resizeHandler);
// 键盘事件监听
keydownHandler = (event) => {
if (!state.isKeyPressed && event.keyCode === 90) {
window.addEventListener("wheel", zoom, { passive: false });
}
state.isKeyPressed = true;
};
keyupHandler = () => {
state.isKeyPressed = false;
window.removeEventListener("wheel", zoom);
};
document.addEventListener("keydown", keydownHandler);
document.addEventListener("keyup", keyupHandler);
});
组件卸载时
javascript
onBeforeUnmount(() => {
// 清理所有事件监听器,防止内存泄漏
if (resizeHandler) {
window.removeEventListener("resize", resizeHandler);
}
if (keydownHandler) {
document.removeEventListener("keydown", keydownHandler);
}
if (keyupHandler) {
document.removeEventListener("keyup", keyupHandler);
}
window.removeEventListener("wheel", zoom);
});
🎨 样式说明
组件样式
scss
.ScaleBox {
width: 100%;
height: 100%;
transition: 0.3s; // 缩放过渡动画
overflow: hidden; // 隐藏溢出内容
}
全局样式
scss
body {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: scale(var(--scale)); // 使用CSS变量控制缩放
transform-origin: 0 0; // 缩放原点为左上角
}
📋 使用场景
1. 大屏数据展示
vue
<template>
<AppContainer>
<div class="data-screen">
<!-- 1920x1080 设计的大屏内容 -->
<div class="charts-container">
<div class="chart-item">图表1</div>
<div class="chart-item">图表2</div>
</div>
</div>
</AppContainer>
</template>
2. 固定尺寸的管理后台
vue
<template>
<AppContainer>
<div class="admin-layout">
<aside class="sidebar">侧边栏</aside>
<main class="main">
<header class="header">顶部栏</header>
<section class="content">内容区</section>
</main>
</div>
</AppContainer>
</template>
3. 游戏或互动应用界面
vue
<template>
<AppContainer>
<div class="game-ui">
<div class="game-board">游戏画布</div>
<div class="control-panel">控制面板</div>
</div>
</AppContainer>
</template>
⚠️ 注意事项
- 设计稿尺寸 :组件默认按 1920x1080 设计,可根据需要修改
state.width
和state.height
- CSS 单位 :建议内容使用
px
单位,避免使用vw/vh
等相对单位 - 事件冲突:如果页面有其他滚轮事件,可能需要调整事件处理逻辑
- 性能考虑:大量 DOM 元素时,频繁缩放可能影响性能
📄 缩放整体代码
vue
<template>
<div class="ScaleBox" ref="scaleBoxRef" :style="{
'--scale': state.scale,
}">
<slot></slot>
</div>
</template>
<script setup>
import { reactive, ref, onMounted, onBeforeUnmount } from 'vue';
const state = reactive({
scale: 0,
width: 1920,
height: 1080,
initialScale: 0,
isKeyPressed: false,
});
const scaleBoxRef = ref(null);
// 存储事件监听器引用,用于清理
let resizeHandler = null;
let keydownHandler = null;
let keyupHandler = null;
const zoom = (event) => {
event.preventDefault();
const delta = Math.sign(event.deltaY);
const newScale = state.scale + -delta * 0.04;
if (newScale < state.initialScale) {
state.scale = state.initialScale;
} else {
state.scale = newScale;
}
const windowHeight = window.innerHeight;
const newHeight = state.height * state.scale;
let top = (windowHeight - newHeight) / 2;
if (top < 0) {
top = 0;
}
if (scaleBoxRef.value) {
scaleBoxRef.value.style.setProperty("top", `${top}px`);
}
};
const getScale = () => {
return window.innerHeight / state.height;
};
const setScale = () => {
state.initialScale = state.scale = getScale();
if (scaleBoxRef.value) {
const { innerWidth } = window;
state.width = innerWidth / state.scale;
document.body.style.width = `${state.width}px`;
document.body.style.height = `${state.height}px`;
}
document.documentElement.style.setProperty("--scale", state.scale.toString());
};
const debounce = (fn, delay = 500) => {
let timer = null;
return (...args) => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn(...args);
}, delay);
};
};
onMounted(() => {
setScale();
// 创建防抖的resize处理器
resizeHandler = debounce(setScale);
window.addEventListener("resize", resizeHandler);
// 键盘事件处理
keydownHandler = (event) => {
if (!state.isKeyPressed && event.keyCode === 90) {
window.addEventListener("wheel", zoom, { passive: false });
}
state.isKeyPressed = true;
};
keyupHandler = () => {
state.isKeyPressed = false;
window.removeEventListener("wheel", zoom);
};
document.addEventListener("keydown", keydownHandler);
document.addEventListener("keyup", keyupHandler);
});
onBeforeUnmount(() => {
// 清理所有事件监听器
if (resizeHandler) {
window.removeEventListener("resize", resizeHandler);
}
if (keydownHandler) {
document.removeEventListener("keydown", keydownHandler);
}
if (keyupHandler) {
document.removeEventListener("keyup", keyupHandler);
}
window.removeEventListener("wheel", zoom);
});
</script>
<style scoped lang="scss">
.ScaleBox {
width: 100%;
height: 100%;
transition: 0.3s;
overflow: hidden;
}
</style>
<style lang="scss">
#ScaleBox {
--scale: 2;
}
body {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: scale(var(--scale));
transform-origin: 0 0;
}
</style>