二、组件用法
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 |
Number 或 String |
1920 |
组件的初始宽度 |
height |
Number 或 String |
1080 |
组件的初始高度 |
duration |
Number |
300 |
窗口大小变化时,缩放动画的持续时间(单位:毫秒) |
corrections |
String 或 String[] |
undefined |
待矫正的元素的 id 或 id 数组,用于解决部分第三方 canvas 地图中拾取坐标时的位置偏移问题 |
三、代码详细解析
1. 导入与组件选项定义
typescript
复制代码
import { onMounted, ref } from "vue";
import lodash, { isArray } from "lodash";
defineOptions({
name: "ScaleBox"
});
- 导入
vue
中的 onMounted
和 ref
函数,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
定义了组件的属性,包括 width
、height
、duration
和 corrections
,并指定了属性的类型和默认值。
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.corrections
为 undefined
,则返回空数组;如果是数组,则通过 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
或与旧缩放比例相同,则不进行矫正。
- 计算元素的新宽度和新高度,并设置元素的
transform
、transformOrigin
、width
和 height
样式。
8. 组件挂载后执行的操作
typescript
复制代码
onMounted(() => {
setScale();
window.addEventListener("resize", lodash.debounce(setScale, props.duration));
});
- 在组件挂载后,调用
setScale
函数设置初始缩放比例。
- 监听窗口的
resize
事件,使用 lodash
的 debounce
函数对 setScale
函数进行防抖处理,确保在窗口大小变化时不会频繁触发缩放操作,防抖时间为 props.duration
。
9. 模板部分
vue
复制代码
<template>
<div ref="ScaleBox" class="ScaleBox" :style="{ width: width + 'px', height: height + 'px' }" >
<slot />
</div>
</template>
- 模板中定义了一个
div
元素,使用 ref
绑定到 ScaleBox
,并设置其 width
和 height
样式。
<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>