如何在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
相关产品推荐
相关产品推荐

