<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<!-- 引入 vue 入口文件 -->
<script type="module" src="./main.js"></script>
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="app"></div>
</body>
</html>
index.html
main.js
App.vue
ResizableBoxContainer.vue
global.css
package.json
md
README.md
现在支持上传本地图片了!
index.html
main.js
import { createApp } from 'vue'
import './global.css'
import App from './App.vue'
createApp(App).mount('#app')
console.log(["Hello 笔.COOL 控制台"])
App.vue
<template>
<div style="width: 100vw; height: 100vh; border: 1px solid #f00; overflow: hidden">
<ResizableBoxContainer :list="boxList" />
</div>
</template>
<script setup lang="ts">
import ResizableBoxContainer from './ResizableBoxContainer.vue';
const boxList = [
{ x: 10, y: 10, width: 100, height: 50 },
{ x: 150, y: 80, width: 200, height: 120 },
{ x: 400, y: 200, width: 300, height: 150 }
];
</script>
ResizableBoxContainer.vue
<template>
<div
ref="containerRef"
class="box-container"
@resize="handleResize"
>
<!-- 绘制列表中的每一个矩形 -->
<div
v-for="(item, index) in list"
:key="index"
class="box-item"
:style="{
left: `${item.x * scale}px`,
top: `${item.y * scale}px`,
width: `${item.width * scale}px`,
height: `${item.height * scale}px`
}"
>
{{ Math.round(item.width) }} x {{ Math.round(item.height) }}
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
// 定义列表项的类型
interface BoxItem {
x: number;
y: number;
width: number;
height: number;
}
// 接收 props
const props = defineProps<{
list: BoxItem[];
}>();
// 容器引用
const containerRef = ref<HTMLElement | null>(null);
// 缩放比例
const scale = ref(1);
// 计算缩放比例的函数
const calculateScale = () => {
if (!containerRef.value || !props.list.length) return;
// 获取容器实际宽高
const containerWidth = containerRef.value.clientWidth;
const containerHeight = containerRef.value.clientHeight;
if (containerWidth === 0 || containerHeight === 0) return;
// 计算所有 item 的最大边界 (x + width, y + height)
let maxX = 0;
let maxY = 0;
props.list.forEach(item => {
const rightEdge = item.x + item.width;
const bottomEdge = item.y + item.height;
maxX = Math.max(maxX, rightEdge);
maxY = Math.max(maxY, bottomEdge);
});
// 如果没有有效数据,直接返回
if (maxX === 0 && maxY === 0) {
scale.value = 1;
return;
}
// 计算缩放比例:取宽度和高度缩放比例中的较小值,确保内容完全适应容器
// 但根据你的需求“保证最大位置正好绘制在容器边界处”,我们取最小比例以确保不溢出
const scaleX = containerWidth / maxX;
const scaleY = containerHeight / maxY;
// 使用较小的比例以确保所有内容都能显示在容器内
scale.value = Math.min(scaleX, scaleY); // 最大为1,不放大
};
// 处理窗口大小变化
const handleResize = () => {
nextTick(() => {
calculateScale();
});
};
// 监听 list 变化和容器挂载
watch(
() => props.list,
() => {
nextTick(() => {
calculateScale();
});
},
{ deep: true }
);
onMounted(() => {
// 确保 DOM 渲染完成后再计算尺寸
nextTick(() => {
calculateScale();
});
// 监听窗口大小变化
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
</script>
<style scoped>
.box-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #f9f9f9; /* 可选:背景色 */
}
.box-item {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
background-color: #3498db;
color: white;
font-size: 12px;
border: 1px solid #2980b9;
box-sizing: border-box;
/* 添加一些样式让矩形更美观 */
border-radius: 2px;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
</style>
global.css
body {
background-color: #fefefe;
}
package.json
注意:新添加的依赖包首次加载可能会报错,稍后再次刷新即可
{
"dependencies": {
"vue": "3.5.15"
}
}
README.md
### Vue 组件 (ResizableBoxContainer.vue)
这是一个使用 Vue 3 Composition API 和 TypeScript 实现的组件。它接收坐标列表,计算缩放比例以适应容器,并正确绘制矩形。
### 主要特性说明
1. **TypeScript 类型安全**:定义了 `BoxItem` 接口确保数据结构正确
2. **自动缩放**:根据容器大小和内容最大边界自动计算缩放比例
3. **响应式**:监听窗口大小变化和列表数据变化
4. **精确计算**:不是对整个容器进行 CSS 缩放,而是计算每个元素的位置和大小
5. **性能优化**:使用 `nextTick` 确保 DOM 更新后再计算尺寸
### 注意事项
- 组件假设父容器有明确的宽高(100% 依赖父容器)
- 使用 `Math.min(scaleX, scaleY)` 确保内容在两个维度上都能适应容器
- 添加了 `overflow: hidden` 防止内容溢出容器
- 使用 `box-sizing: border-box` 确保边框不影响尺寸计算
预览页面