antv x6 沿边图标循环动画实现

实现效果,如下图,边上存在两个图标,要求图标延边一直循环动画
在这里插入图片描述
实现方法:
1.注册一个自定义边,边上定义两个图标,并设置其初始位置
在这里插入图片描述
2.使用transition给边设置动画,利用complte方法实现循环动画
在这里插入图片描述

补充代码:
1.注册边的代码

Graph.registerEdge(
  'arrow-edge',
  {
    markup: [
      {
        tagName: 'path',
        selector: 'wrap',
        attrs: {
          fill: 'none',
          cursor: 'pointer'
          // stroke: '#ff0000'
        }
      },
      {
        tagName: 'path',
        selector: 'line',
        attrs: {
          fill: 'none',
          'pointer-events': 'none'
        }
      },
      {
        tagName: 'path',
        groupSelector: 'arrow',
        selector: 'arrow1'
      },
      {
        tagName: 'path',
        groupSelector: 'arrow',
        selector: 'arrow2'
      }
    ],
    attrs: {
      wrap: {
        connection: true,
        strokeWidth: 10,
        strokeLinejoin: 'round',
        style: {
          animation: 'animation-line 30s infinite linear'
        }
      },
      line: {
        connection: true,
        stroke: '#7eb2fb',
        strokeLinejoin: 'round',
        targetMarker: 'classic',
        style: {
          // animation: 'animation-line 30s infinite linear'
        }
      },
      arrow: {
        // d: 'M 0 2 2 1 4 0 2 -1 0 -2 -14 -1 -14 0 -14 1 z',
        d: 'M 0 2 2 1 Q4 0 2 -1 0 -2 -24 -1 -24 0 -24 1 z',
        fill: {
          type: 'linearGradient',
          stops: [
            { offset: '0%', color: 'rgba(73, 131, 236,0.7)' },
            { offset: '30%', color: '#4983EC' },
            { offset: '100%', color: '#FFFFFF' }
          ]
        },
        filter: {
          name: 'dropShadow',
          args: {
            color: '#4983EC',
            dx: 0,
            dy: 0,
            blur: 2,
            opacity: 0.7
          }
        },
        stroke: 'none',
        pointerEvents: 'none',
        style: {
          boxShadow: '0px 0px 10px 1px #002DFF'
        }
      },
      arrow1: {
        // atConnectionRatio: 将边中的指定元素移动到指定比例 [0, 1] 位置处,并自动旋转元素,
        // 使其方向与所在位置边的斜率保持一致。
        atConnectionRatio: 0.25
      },
      arrow2: {
        atConnectionRatio: 0.5
      }

    },

    // connector: { name: 'smooth' },
    zIndex: 0

  },
  true
)

2.动画代码:

import { Timing } from '@antv/x6'
render(){
	const { edges: newEdges, nodes } = newData//接口获取或者自定义的拓扑数据
	//根据连接节点的位置获取边的入口port和出口port ,根据自己的需要调整或者选择删除
	newEdges.map(it => {
	  let sourceNode, targetNode
	  for (let i = 0; i < nodes.length; i++) {
	    if (nodes[i].id === it.source.cell) {
	      sourceNode = nodes[i]
	    }
	    if (nodes[i].id === it.target.cell) {
	      targetNode = nodes[i]
	    }
	    if (sourceNode && targetNode) {
	      break
	    }
	  }
	  const sourceX = sourceNode.x
	  const sourceY = sourceNode.y
	  const targetX = targetNode.x
	  const targetY = targetNode.y
	  const sourceUsedPort = sourceNode.data?.usedPort || []
	  const targetUsedPort = targetNode.data?.usedPort || []
	  //getEdgePort2代码在下面
	  const { sourcePort, targetPort } = this.SystemFlow.getEdgePort2({ sourceX, sourceY, targetX, targetY, sourceUsedPort, targetUsedPort })
	  sourceUsedPort.push(sourcePort)
	  targetUsedPort.push(targetPort)
	  sourceNode.data.usedPort = sourceUsedPort
	  targetNode.data.usedPort = targetUsedPort
	  it.source.port = sourcePort
	  it.target.port = targetPort
	})
	graph.fromJSON(newData)
	const edges = graph.getEdges()
	//核心代码
	edges.forEach((edge) => {
	   const options = {
	     delay: 0,
	     duration: 8000,
	     timing: Timing.linear,
	     complete: (p) => {
	       const attr = p.path.includes('arrow1') ? 'arrow1' : 'arrow2'
	       edge.updateAttrs({
	         [attr]: { atConnectionRatio: 0 }
	       }, { silent: true })
	       edge.transition(p.path, 1, options)
	     }
	   }
	   edge.transition('attrs/arrow1/atConnectionRatio', 1, options)
	   // 两个图标第一次需要在不同时间结束后面才能以相同速度动画。
	   edge.transition('attrs/arrow2/atConnectionRatio', 1, { ...options, duration: 6000 })
	 })
}

3.根据节点的未知计算边的出口port和入口port(getEdgePort2)

 getEdgePort2 ({ sourceX, sourceY, targetX, targetY, sourceUsedPort = [], targetUsedPort = [] }) {
    let sourceLt = 'center'
    const sourceTb = 'output'
    let targetLt = 'center'
    let targetTb = 'input'
    const sourceLeftOffset = []
    const sourceRightOffset = []
    sourceUsedPort.forEach(it => {
      if (it.includes('output')) {
        const arr = it?.split('-') || []
        const offset = arr.length === 2 ? arr[1] : arr[2]
        if (it?.includes('left')) {
          sourceLeftOffset.push(offset)
        }
        if (it?.includes('right')) {
          sourceRightOffset.push(offset)
        }
      }
    })
    // 判断目标节点是否已经有父元素连接过(没有被连接过的优先使用其中间port)

    const hasTargetUseCenter = targetUsedPort.find(it => {
      if (it.includes('input')) {
        const arr = it?.split('-') || []
        const offset = arr.length === 2 ? arr[1] : arr[2]
        if (offset === 'center') {
          return true
        }
      }
    })
    if (sourceY >= targetY) {
      // sourceTb = 'input' 出必须在下面
      targetTb = 'output'
    }
    const allOffset = ['2', '1']
    let offset = allOffset[0]
    if (sourceX >= targetX) {
      if (sourceX <= targetX + 30) {
        sourceLt = 'center'
        targetLt = 'center'
      } else {
        // 如果改port已经使用过了,查看是否有未使用的port,如果有则在里面找一个右边对应已经使用过的来保持对称关系
        offset = this.getOffset(sourceLeftOffset, sourceRightOffset, allOffset, offset)
        sourceLt = `left-${offset}`
        targetLt = `right-${offset}`
      }
    } else {
      if (sourceX >= targetX - 30) {
        sourceLt = 'center'
        targetLt = 'center'
      } else {
        offset = this.getOffset(sourceRightOffset, sourceLeftOffset, allOffset, offset)
        sourceLt = `right-${offset}`
        targetLt = `left-${offset}`
      }
    }
    return {
      sourcePort: `${sourceTb}-${sourceLt}`,
      targetPort: !hasTargetUseCenter ? `${targetTb}-center` : `${targetTb}-${targetLt}`
    }
  }

4.节点port

export const rotatePorts = {
  groups: {
    input: {
      position: {
        name: 'line',
        args: {
          start: { x: 0, y: largeScreenNodeSize.height / 2 - 40 },
          end: { x: 80, y: largeScreenNodeSize.height / 2 - 40 },
          strict: true
        }
      },
      attrs: {
        circle: {
          r: 4,
          magnet: true,
          stroke: '#5F95FF',
          strokeWidth: 1,
          style: {
            visibility: 'hidden'
          }
        }
      }

    },
    output: {
      position: {
        name: 'line',
        args: {
          start: { x: 0, y: largeScreenNodeSize.height / 2 + 16 },
          end: { x: 80, y: largeScreenNodeSize.height / 2 + 16 },
          strict: true
        }
      },
      attrs: {
        circle: {
          r: 4,
          magnet: true,
          stroke: '#5F95FF',
          strokeWidth: 1,
          style: {
            visibility: 'hidden'
          }
        }
      }

    }

  },
  items: [
    {
      group: 'input',
      id: 'input-left-2',
      args: {

      }
    },
    {
      group: 'input',
      id: 'input-left-1',
      args: {

      }
    },
    {
      group: 'input',
      id: 'input-center',
      args: {

      }
    },
    {
      group: 'input',
      id: 'input-right-1',
      args: {

      }
    },
    {
      group: 'input',
      id: 'input-right-2',
      args: {

      }
    },
    {
      group: 'output',
      id: 'output-left-2',
      args: {

      }
    },
    {
      group: 'output',
      id: 'output-left-1',
      args: {
        dy: 4
      }
    },
    {
      group: 'output',
      id: 'output-center',
      args: {
        dy: 8
      }
    },
    {
      group: 'output',
      id: 'output-right-1',
      args: {
        dy: 4
      }
    },
    {
      group: 'output',
      id: 'output-right-2',
      args: {

      }
    }

  ]
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值