You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue Transition Group实现SVG树形节点个性化过渡动画的问题

Vue SVG树形节点个性化进入/移动动画实现方案

Vue完全可以实现你要的个性化节点动画需求,不用退回到D3的手动DOM操作。核心思路是利用TransitionGroup的JS钩子,结合节点数据中的父节点位置,给每个进入的节点设置专属的起始过渡参数,具体实现步骤如下:


1. 修正TransitionGroup的key值

你当前用index作为v-for的key是错误的——节点展开/折叠时,列表的index会重新排序,导致Vue无法正确跟踪节点实例,进而无法触发正确的进入/离开动画。必须改用节点的唯一标识(比如节点数据中的id字段)作为key:

<template>
  <svg class="TreeLayout fit" name="slide-left-centered">
    <TransitionGroup
      name="tree-node"
      tag="g"
      id="tree-canvas"
      @before-enter="handleBeforeEnter"
      @enter="handleEnter"
      @before-leave="handleBeforeLeave"
    >
      <testNode 
        v-for="item in h.descendants()" 
        :key="item.id" <!-- 替换为节点唯一ID -->
        :x="item.x"
        :y="item.y"
        :width="rectW"
        :height="rectH"
        :node-data="item"
        @some-event="handleClick(item)"
      />
    </TransitionGroup>
  </svg>
</template>

2. 用TransitionGroup钩子实现个性化动画

TransitionGroup提供的JS钩子可以精准控制每个节点的动画过程,你可以在钩子中获取节点的父位置,设置起始状态,再过渡到目标位置:

<script setup>
import { ref } from 'vue'

const rectW = ref(100)
const rectH = ref(40)

// 进入前:设置节点初始位置为父节点位置
const handleBeforeEnter = (el, { props }) => {
  const parent = props.nodeData.parent
  if (parent) {
    // SVG的transform用translate语法,初始位置绑定父节点坐标
    el.style.transform = `translate(${parent.x}px, ${parent.y}px)`
    el.style.opacity = '0'
  } else {
    // 根节点直接显示
    el.style.opacity = '1'
  }
}

// 进入时:过渡到自身目标位置
const handleEnter = (el, done) => {
  const { x, y } = el.dataset
  // 用requestAnimationFrame确保DOM已更新
  requestAnimationFrame(() => {
    el.style.transition = 'transform 0.3s ease, opacity 0.3s ease'
    el.style.transform = `translate(${x}px, ${y}px)`
    el.style.opacity = '1'
  })
  // 动画结束后通知Vue完成过渡
  el.addEventListener('transitionend', done, { once: true })
}

// 离开时:过渡回父节点位置后移除
const handleBeforeLeave = (el, { props }) => {
  const parent = props.nodeData.parent
  if (parent) {
    el.style.transition = 'transform 0.3s ease, opacity 0.3s ease'
    el.style.transform = `translate(${parent.x}px, ${parent.y}px)`
    el.style.opacity = '0'
  }
}

const handleClick = (item) => {
  // 这里处理节点展开/折叠逻辑,更新tree数据触发响应式更新
}
</script>

3. 子组件传递位置数据

在testNode子组件中,把目标位置绑定到DOM元素的data-*属性,方便钩子中获取:

<template>
  <g 
    :data-x="x" 
    :data-y="y"
    class="tree-node"
  >
    <rect 
      :width="width" 
      :height="height"
      :rx="4"
      fill="#fff"
      stroke="#333"
    />
    <text :x="width/2" :y="height/2" text-anchor="middle" dominant-baseline="middle">
      {{ nodeData.name }}
    </text>
  </g>
</template>

<script setup>
defineProps({
  x: Number,
  y: Number,
  width: Number,
  height: Number,
  nodeData: Object
})
</script>

补充说明

  • 你之前用refs操作没生效,是因为refs在节点刚进入时可能还未完成挂载,而TransitionGroup的钩子是在Vue的过渡生命周期中触发的,能确保DOM元素已存在。
  • 节点移动动画:如果父节点展开导致其他节点移位,可以给testNode添加watch监听x/y的变化,触发过渡动画,或者结合TransitionGroup的@move钩子处理。
  • 完全不需要回到D3的手动DOM操作,Vue的响应式+TransitionGroup钩子可以完美实现个性化的节点动画逻辑。

内容的提问来源于stack exchange,提问作者Maranderine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 02:02:26