Vue3 解决大屏自适应(缩放)解决方案

智能缩放容器组件,支持自适应缩放、键盘快捷键控制滚轮缩放等功能。

✨ 特性

  • 🎯 自适应缩放:根据窗口高度自动计算最佳缩放比例
  • ⌨️ 键盘控制:按住 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>

⚠️ 注意事项

  1. 设计稿尺寸 :组件默认按 1920x1080 设计,可根据需要修改 state.widthstate.height
  2. CSS 单位 :建议内容使用 px 单位,避免使用 vw/vh 等相对单位
  3. 事件冲突:如果页面有其他滚轮事件,可能需要调整事件处理逻辑
  4. 性能考虑:大量 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>
相关推荐
再学一点就睡3 小时前
手写 Promise 静态方法:从原理到实现
前端·javascript·面试
再学一点就睡4 小时前
前端必会:Promise 全解析,从原理到实战
前端·javascript·面试
前端工作日常4 小时前
我理解的eslint配置
前端·eslint
前端工作日常5 小时前
项目价值判断的核心标准
前端·程序员
90后的晨仔5 小时前
理解 Vue 的列表渲染:从传统 DOM 到响应式世界的演进
前端·vue.js
OEC小胖胖6 小时前
性能优化(一):时间分片(Time Slicing):让你的应用在高负载下“永不卡顿”的秘密
前端·javascript·性能优化·web
烛阴6 小时前
ABS - Rhomb
前端·webgl
植物系青年6 小时前
10+核心功能点!低代码平台实现不完全指南 🧭(下)
前端·低代码
植物系青年6 小时前
10+核心功能点!低代码平台实现不完全指南 🧭(上)
前端·低代码
桑晒.6 小时前
CSRF漏洞原理及利用
前端·web安全·网络安全·csrf