Vue 3 缩放盒子组件 ScaleBox:实现内容动态缩放与坐标拾取偏移矫正

一、组件概述 ScaleBox 是一个基于 Vue 3 和 TypeScript 开发的组件,其主要功能是根据浏览器窗口大小动态调整内部内容的缩放比例,在大屏适配需求上很有用。此外,该组件还提供了矫正功能,可解决部分第三方 canvas 地图在进行坐标拾取时因 transform: scale 导致的位置偏移问题。

二、组件用法

1. 安装依赖 此组件依赖 lodash 库,使用前需确保已安装。可通过以下命令进行安装:

bash 复制代码
    npm install lodash 

2. 引入组件 在 Vue 项目中,将 index.vue 文件中的 ScaleBox 组件引入到需要使用的地方,示例如下:

xml 复制代码
<template> 
  <div> 
    <ScaleBox :width="1920" :height="1080"> 
      <!-- 这里放置需要缩放的内容 --> 
      <p>这是需要缩放的内容</p> 
    </ScaleBox> 
  </div> 
</template> 
<script setup lang="ts"> 
import ScaleBox from './index.vue'; 
</script>

3. 组件属性

属性名 类型 默认值 描述
width NumberString 1920 组件的初始宽度
height NumberString 1080 组件的初始高度
duration Number 300 窗口大小变化时,缩放动画的持续时间(单位:毫秒)
corrections StringString[] undefined 待矫正的元素的 idid 数组,用于解决部分第三方 canvas 地图中拾取坐标时的位置偏移问题

三、代码详细解析

1. 导入与组件选项定义

typescript 复制代码
import { onMounted, ref } from "vue";
import lodash, { isArray } from "lodash";

defineOptions({
  name: "ScaleBox"
});
  • 导入 vue 中的 onMountedref 函数,onMounted 用于在组件挂载后执行代码,ref 用于创建响应式引用。
  • 导入 lodash 库及其 isArray 方法,isArray 用于判断一个值是否为数组。
  • 使用 defineOptions 定义组件名称为 ScaleBox

2. 定义响应式引用和组件属性

typescript 复制代码
const ScaleBox = ref();

const props = defineProps({
  width: {
    type: [Number, String],
    default: 1920
  },
  height: {
    type: [Number, String],
    default: 1080
  },
  duration: {
    type: Number,
    default: 300
  },
  // 部分第三方canvas地图中进行拾取坐标时会受到 transform: scale 影响导致拾取位置偏移
  // 传入待矫正的元素 id 或 id 数组进行矫正
  corrections: {
    type: [String, Array] as PropType<string | undefined | string[]>
  }
});
  • ScaleBox 是一个响应式引用,用于引用模板中的 div 元素。
  • props 定义了组件的属性,包括 widthheightdurationcorrections,并指定了属性的类型和默认值。

3. 定义变量和事件

typescript 复制代码
let scale = 0; 
const emit = defineEmits(["change"]); 
  • scale 用于存储计算得到的缩放比例。
  • emit 用于触发自定义事件,这里定义了一个 change 事件,当缩放比例改变时会触发该事件。

4. 计算缩放比例

typescript 复制代码
function getScale() {
  // 计算缩放比
  const { width, height } = props;
  const wh = window.innerHeight / Number(height);
  const ww = window.innerWidth / Number(width);
  return ww < wh ? ww : wh;
}
  • getScale 函数用于计算缩放比例,它通过比较窗口宽度与组件宽度的比例 ww 和窗口高度与组件高度的比例 wh,取较小值作为最终的缩放比例。

5. 设置缩放比例

typescript 复制代码
function setScale() {
  // 获取到缩放比例,设置它
  scale = getScale();
  emit("change", scale);
  const elements = getElements();
  elements.forEach(el => {
    handleCorrection(el, scale);
  });
  if (ScaleBox.value) {
    ScaleBox.value.style.setProperty("--scale", scale);
  }
}
  • setScale 函数用于设置缩放比例,它首先调用 getScale 函数获取缩放比例,然后触发 change 事件。
  • 调用 getElements 函数获取需要矫正的元素数组,并对每个元素调用 handleCorrection 函数进行矫正。
  • 最后,将计算得到的缩放比例设置为 ScaleBox 元素的自定义 CSS 变量 --scale

6. 获取需要矫正的元素

typescript 复制代码
interface ScaleElement extends HTMLElement {
  oldValue?: number;
}

// 获取元素
function getElements() {
  if (!props.corrections) return [];
  if (isArray(props.corrections)) {
    return props.corrections
      .map(id => document.getElementById(id))
      .filter(Boolean) as ScaleElement[];
  } else {
    const el = document.getElementById(props.corrections);
    return el ? [el] : [];
  }
}
  • 定义 ScaleElement 接口,它继承自 HTMLElement 并添加了一个可选的 oldValue 属性,用于存储元素的旧缩放比例。
  • getElements 函数根据 props.corrections 的值获取需要矫正的元素数组。如果 props.correctionsundefined,则返回空数组;如果是数组,则通过 map 方法获取每个 id 对应的元素,并过滤掉 null 值;如果是字符串,则直接获取该 id 对应的元素。

7. 矫正元素

typescript 复制代码
const handleCorrection = (el: ScaleElement, scale: number) => {
  const oldScale = el.oldValue || 1;
  el.oldValue = scale;
  if (!scale || scale === oldScale) return;
  const { offsetWidth, offsetHeight } = el;
  const newWidth = (offsetWidth / oldScale) * scale;
  const newHeight = (offsetHeight / oldScale) * scale;
  el.style.transform = `scale(${1 / scale})`;
  el.style.transformOrigin = "0 0";
  el.style.width = newWidth + "px";
  el.style.height = newHeight + "px";
};
  • handleCorrection 函数用于矫正元素的缩放。它首先获取元素的旧缩放比例 oldScale,并将新的缩放比例存储在 el.oldValue 中。
  • 如果缩放比例为 0 或与旧缩放比例相同,则不进行矫正。
  • 计算元素的新宽度和新高度,并设置元素的 transformtransformOriginwidthheight 样式。

8. 组件挂载后执行的操作

typescript 复制代码
onMounted(() => { 
  setScale(); 
  window.addEventListener("resize", lodash.debounce(setScale, props.duration)); 
}); 
  • 在组件挂载后,调用 setScale 函数设置初始缩放比例。
  • 监听窗口的 resize 事件,使用 lodashdebounce 函数对 setScale 函数进行防抖处理,确保在窗口大小变化时不会频繁触发缩放操作,防抖时间为 props.duration

9. 模板部分

vue 复制代码
<template> 
  <div ref="ScaleBox" class="ScaleBox" :style="{ width: width + 'px', height: height + 'px' }" > 
    <slot /> 
  </div> 
</template> 
  • 模板中定义了一个 div 元素,使用 ref 绑定到 ScaleBox,并设置其 widthheight 样式。
  • <slot /> 用于插入组件的子内容。

10. 样式部分

scss 复制代码
<style lang="scss" scoped>
#ScaleBox {
  //默认缩放比
  --scale: 1;
}
.ScaleBox {
  position: absolute;
  transform: scale(var(--scale)) translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  transform-origin: 0 0;
  left: 50%;
  top: 50%;
  transition: 0.3s;
}
</style>
  • 定义一个自定义 CSS 变量 --scale,默认值为 1
  • .ScaleBox 类设置了元素的定位、缩放、布局和过渡效果。

四、总结 ScaleBox 组件通过动态计算缩放比例和矫正元素的方式,实现了根据窗口大小自动缩放内容的功能,并解决了部分第三方 canvas 地图中坐标拾取的位置偏移问题。通过合理使用组件属性和理解代码逻辑,我们可以灵活应用该组件到各种前端项目中。

五、完整代码

xml 复制代码
<script setup lang="ts">
import { onMounted, ref } from "vue";
import lodash, { isArray } from "lodash";

defineOptions({
  name: "ScaleBox"
});

const ScaleBox = ref();

const props = defineProps({
  width: {
    type: [Number, String],
    default: 1920
  },
  height: {
    type: [Number, String],
    default: 1080
  },
  duration: {
    type: Number,
    default: 300
  },
  // 部分第三方canvas地图中进行拾取坐标时会受到 transform: scale 影响导致拾取位置偏移
  // 传入待矫正的元素 id 或 id 数组进行矫正
  corrections: {
    type: [String, Array] as PropType<string | undefined | string[]>
  }
});

let scale = 0;
const emit = defineEmits(["change"]);

function getScale() {
  // 计算缩放比
  const { width, height } = props;
  const wh = window.innerHeight / Number(height);
  const ww = window.innerWidth / Number(width);
  return ww < wh ? ww : wh;
}
function setScale() {
  // 获取到缩放比例,设置它
  scale = getScale();
  emit("change", scale);
  const elements = getElements();
  elements.forEach(el => {
    handleCorrection(el, scale);
  });
  if (ScaleBox.value) {
    ScaleBox.value.style.setProperty("--scale", scale);
  }
}
interface ScaleElement extends HTMLElement {
  oldValue?: number;
}

// 获取元素
function getElements() {
  if (!props.corrections) return [];
  if (isArray(props.corrections)) {
    return props.corrections
      .map(id => document.getElementById(id))
      .filter(Boolean) as ScaleElement[];
  } else {
    const el = document.getElementById(props.corrections);
    return el ? [el] : [];
  }
}

const handleCorrection = (el: ScaleElement, scale: number) => {
  const oldScale = el.oldValue || 1;
  el.oldValue = scale;
  if (!scale || scale === oldScale) return;
  const { offsetWidth, offsetHeight } = el;
  const newWidth = (offsetWidth / oldScale) * scale;
  const newHeight = (offsetHeight / oldScale) * scale;
  el.style.transform = `scale(${1 / scale})`;
  el.style.transformOrigin = "0 0";
  el.style.width = newWidth + "px";
  el.style.height = newHeight + "px";
};

onMounted(() => {
  setScale();
  window.addEventListener("resize", lodash.debounce(setScale, props.duration));
});
</script>
<template>
  <div
    ref="ScaleBox"
    class="ScaleBox"
    :style="{
      width: width + 'px',
      height: height + 'px'
    }"
  >
    <slot />
  </div>
</template>

<style lang="scss" scoped>
#ScaleBox {
  //默认缩放比
  --scale: 1;
}
.ScaleBox {
  position: absolute;
  transform: scale(var(--scale)) translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  transform-origin: 0 0;
  left: 50%;
  top: 50%;
  transition: 0.3s;
}
</style>
相关推荐
Kusunoki_D12 分钟前
Python 实现 Web 静态服务器(HTTP 协议)
服务器·前端·python
爱学习的茄子22 分钟前
【前端实战】三分钟掌握原生JS电影搜索应用,从此告别框架依赖
前端·javascript·深度学习
林太白24 分钟前
Next.js超简洁完整篇
前端·后端·react.js
前端付豪24 分钟前
汇丰登录风控体系拆解:一次 FaceID 被模拟攻击的调查纪实
前端·后端·架构
天生我材必有用_吴用32 分钟前
Three.js开发必备:模型对象和材质详解
前端
万变不离其宗_832 分钟前
echarts使用笔记
前端·笔记·echarts
时光足迹35 分钟前
电子书阅读器之章节拆分
前端·javascript·react.js
无名之逆35 分钟前
大三自学笔记:探索Hyperlane框架的心路历程
java·开发语言·前端·spring boot·后端·rust·编程
郭顺发39 分钟前
个人网站大更新,还是有个总站比较好
前端
古夕41 分钟前
Webpack 之 打包后的 bundle 文件内容解析
前端·面试·webpack