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

如何在Flutter中设计对阵匹配(brackets/match ups)及获胜者连线组件

晋级连线组件实现方案

整体布局逻辑

  • 采用双列+绝对定位SVG容器的结构:父容器设置position: relative,左右两列分别放置First List(所有参赛选手)和Second List(晋级选手)的卡片,中间层放SVG容器用于绘制蓝色连线。
  • 所有选手卡片绑定唯一的选手ID作为匹配标识。

核心实现代码

HTML结构

<div class="bracket-wrapper">
  <!-- 左侧参赛选手列表 -->
  <div class="player-column left">
    <div class="player-card" data-player-id="p1">选手1</div>
    <div class="player-card" data-player-id="p2">选手2</div>
    <div class="player-card" data-player-id="p3">选手3</div>
    <div class="player-card" data-player-id="p4">选手4</div>
  </div>
  <!-- SVG连线层 -->
  <svg class="connect-line-container"></svg>
  <!-- 右侧晋级选手列表 -->
  <div class="player-column right">
    <div class="player-card" data-player-id="p2">选手2</div>
    <div class="player-card" data-player-id="p4">选手4</div>
  </div>
</div>

CSS样式

.bracket-wrapper {
  position: relative;
  display: flex;
  gap: 120px; /* 控制两列间距,即连线默认长度 */
  padding: 20px;
  width: fit-content;
}
.player-column {
  display: flex;
  flex-direction: column;
  gap: 30px; /* 同列卡片上下间距 */
}
.player-card {
  width: 200px;
  padding: 12px 16px;
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  background: #fff;
}
.connect-line-container {
  position: absolute;
  inset: 0;
  pointer-events: none; /* 不遮挡下层卡片的点击等交互 */
}
.blue-connect-line {
  stroke: #2563eb;
  stroke-width: 2;
  fill: none;
}

JS连线逻辑

const wrapper = document.querySelector('.bracket-wrapper')
const leftCards = document.querySelectorAll('.left .player-card')
const rightCards = document.querySelectorAll('.right .player-card')
const lineSvg = document.querySelector('.connect-line-container')
const wrapperRect = wrapper.getBoundingClientRect()

// 存储左侧选手ID和对应连线起点坐标
const leftPointMap = new Map()
leftCards.forEach(card => {
  const pid = card.dataset.playerId
  const cardRect = card.getBoundingClientRect()
  // 计算左侧卡片右边缘中点的相对坐标作为连线起点
  const startX = cardRect.right - wrapperRect.left
  const startY = cardRect.top - wrapperRect.top + cardRect.height / 2
  leftPointMap.set(pid, {startX, startY})
})

// 遍历右侧晋级选手绘制连线
rightCards.forEach(card => {
  const pid = card.dataset.playerId
  if (!leftPointMap.has(pid)) return
  const {startX, startY} = leftPointMap.get(pid)
  const cardRect = card.getBoundingClientRect()
  // 计算右侧卡片左边缘中点的相对坐标作为连线终点
  const endX = cardRect.left - wrapperRect.left
  const endY = cardRect.top - wrapperRect.top + cardRect.height / 2
  // 生成直线路径,需要拐角的话可以调整d属性的路径规则
  const path = document.createElementNS("http://www.w3.org/2000/svg", "path")
  path.setAttribute('d', `M ${startX} ${startY} L ${endX} ${endY}`)
  path.classList.add('blue-connect-line')
  lineSvg.appendChild(path)
})

扩展优化

  • 要实现带拐角的连线,只需修改路径规则,例如:M ${startX} ${startY} L ${startX + 40} ${startY} L ${endX - 40} ${endY} L ${endX} ${endY}即可生成带两个直角拐点的连线。
  • 监听窗口resize事件,清空现有连线后重新计算坐标绘制,实现响应式适配。
  • 框架场景下(React/Vue等),把绘制逻辑放到组件挂载、列表数据更新的生命周期钩子中执行即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 00:54:04