适用场景:VueFlow 图表需要在容器宽高动态变化时(如面板折叠、窗口 resize、布局变化)自动缩放适配。
核心思路
用 ResizeObserver 监听容器 div 的尺寸变化,每次变化时调用 VueFlow 的 fitView() 重新适配视口。
scss
容器尺寸变化 → ResizeObserver 触发 → nextTick → fitView() → [可选] panBy() 偏移补偿
完整实现
第一步:补全 imports
javascript
import { ref, onMounted, markRaw, nextTick, onBeforeUnmount } from "vue";
import { VueFlow, useVueFlow } from "@vue-flow/core";
// ↑ 必须引入 useVueFlow
第二步:声明变量
csharp
// 容器 ref
const containerRef = ref<HTMLElement | null>(null);
// ResizeObserver 实例(保存引用,方便销毁时清理)
let resizeObserver: ResizeObserver | null = null;
// 从 useVueFlow 解构需要的 API
const { fitView, panBy } = useVueFlow();
// 如果不需要偏移补偿,只解构 fitView 即可:
// const { fitView } = useVueFlow();
第三步:fitView 适配函数
基础版(无偏移补偿):
scss
function fitViewWithOffset() {
nextTick(() => {
fitView();
});
}
带偏移补偿版(适用于有视觉偏移需求的场景,如台体底部对齐):
scss
function fitViewWithOffset() {
nextTick(() => {
fitView();
nextTick(() => {
panBy({ x: 0, y: 150 }); // 数值按实际视觉效果调整
});
});
}
为什么需要双层 nextTick?
- 第一层:等容器 DOM 更新完毕,VueFlow 感知到新尺寸
- 第二层:等
fitView()的视口计算完成,再执行panBy()
第四步:onMounted 注册监听
scss
onMounted(() => {
if (containerRef.value) {
resizeObserver = new ResizeObserver(() => {
fitViewWithOffset();
});
resizeObserver.observe(containerRef.value); // 监听容器 div
}
});
第五步:onBeforeUnmount 清理
ini
onBeforeUnmount(() => {
resizeObserver?.disconnect(); // 断开监听
resizeObserver = null; // 释放引用,防止内存泄漏
});
第六步:初始加载适配(onPaneReady)
scss
// 首次加载用 @pane-ready 事件,逻辑与 ResizeObserver 回调保持一致
function onPaneReady(instance: VueFlowStore) {
instance.fitView();
nextTick(() => {
instance.panBy({ x: 0, y: 150 }); // 如有偏移需求则加,否则省略
});
}
第七步:模板绑定 ref
xml
<!-- 容器 div 必须绑定 ref,ResizeObserver 才能监听到它 -->
<div ref="containerRef" class="your-flow-container">
<VueFlow
v-model:nodes="nodes"
v-model:edges="edges"
:node-types="nodeTypes"
:edge-types="edgeTypes"
@pane-ready="onPaneReady"
/>
</div>
完整模板(可直接复制)
xml
<script setup lang="ts">
import { ref, onMounted, markRaw, nextTick, onBeforeUnmount } from "vue";
import { VueFlow, useVueFlow } from "@vue-flow/core";
import type { Edge, Node, VueFlowStore } from "@vue-flow/core";
import "@vue-flow/core/dist/style.css";
import "@vue-flow/core/dist/theme-default.css";
// --- 自适应相关 ---
const containerRef = ref<HTMLElement | null>(null);
let resizeObserver: ResizeObserver | null = null;
const { fitView } = useVueFlow(); // 有偏移需求时加 panBy
function fitViewWithOffset() {
nextTick(() => {
fitView();
// 如需偏移补偿,再嵌套一层 nextTick:
// nextTick(() => { panBy({ x: 0, y: 150 }); });
});
}
function onPaneReady(instance: VueFlowStore) {
instance.fitView();
// 如需偏移补偿:
// nextTick(() => { instance.panBy({ x: 0, y: 150 }); });
}
onMounted(() => {
if (containerRef.value) {
resizeObserver = new ResizeObserver(() => {
fitViewWithOffset();
});
resizeObserver.observe(containerRef.value);
}
});
onBeforeUnmount(() => {
resizeObserver?.disconnect();
resizeObserver = null;
});
// --- 节点 / 边 ---
const nodes = ref<Node[]>([/* ... */]);
const edges = ref<Edge[]>([/* ... */]);
const nodeTypes = { /* ... */ };
const edgeTypes = { /* ... */ };
</script>
<template>
<div ref="containerRef" class="flow-container">
<VueFlow
v-model:nodes="nodes"
v-model:edges="edges"
:node-types="nodeTypes"
:edge-types="edgeTypes"
@pane-ready="onPaneReady"
/>
</div>
</template>
<style scoped>
.flow-container {
position: relative;
width: 100%;
height: 100%;
}
</style>
知识点速查
| 知识点 | 说明 |
|---|---|
ResizeObserver |
浏览器原生 API,监听 DOM 元素的尺寸变化 |
fitView() |
VueFlow API,将所有节点缩放平移至充满视口 |
panBy({ x, y }) |
VueFlow API,在当前视口基础上做相对偏移 |
useVueFlow() |
VueFlow composable,在 <script setup> 中获取 fitView / panBy 等方法 |
onPaneReady |
VueFlow 事件,首次渲染完成时触发,用于初始适配 |
双层 nextTick |
第一层等 DOM 更新,第二层等 fitView 计算完成 |
resizeObserver?.disconnect() |
组件销毁时必须调用,防止内存泄漏 |
常见问题
Q: fitView 后图位置不对? 在 fitView() 之后嵌套一层 nextTick 再调用 panBy() 做补偿。
Q: 容器初始化时图没有适配? 确保绑定了 @pane-ready="onPaneReady",VueFlow 内部初始化完成后才能调用 fitView。
Q: ResizeObserver 触发太频繁性能差? 可以对 fitViewWithOffset 加防抖(debounce),延迟 100~200ms 执行,减少高频调用。
scss
import { debounce } from 'lodash-es';
const debouncedFitView = debounce(() => {
nextTick(() => { fitView(); });
}, 150);
// 替换 ResizeObserver 回调中的调用:
resizeObserver = new ResizeObserver(debouncedFitView);