如何用HTML、CSS、JS及Angular实现径向汇聚图?
纯原生Angular径向汇聚图实现方案
以下方案无需第三方库,解决连线绘制、气泡径向分布及颜色联动问题:
核心思路
用SVG作为连线载体(原生支持箭头和颜色绑定),在父组件统一计算所有气泡的坐标,将位置、颜色数据同步传给气泡组件和SVG连线,实现完全联动。
1. 父组件(径向图容器)
模板代码
<div class="radial-container" #container> <!-- SVG连线层:置于最底层,不遮挡气泡交互 --> <svg [attr.width]="container.offsetWidth" [attr.height]="container.offsetHeight"> <!-- 定义箭头标记,可复用 --> <defs> <marker id="arrow-marker" markerWidth="12" markerHeight="8" refX="10" refY="4" orient="auto"> <polygon points="0 0, 12 4, 0 8" fill="currentColor" /> </marker> </defs> <!-- 循环生成连线,绑定气泡颜色 --> <line *ngFor="let item of radialItems" [attr.x1]="centerX" [attr.y1]="centerY" [attr.x2]="item.x" [attr.y2]="item.y" [attr.stroke]="item.color" stroke-width="2" marker-end="url(#arrow-marker)" stroke-opacity="0.8" /> </svg> <!-- 中心首页气泡 --> <app-bubble [top]="centerY - bubbleSize/2" [left]="centerX - bubbleSize/2" [text]="'首页'" [color]="'#2ecc71'" ></app-bubble> <!-- 外围气泡:按计算好的坐标渲染 --> <app-bubble *ngFor="let item of radialItems" [top]="item.y - bubbleSize/2" [left]="item.x - bubbleSize/2" [text]="item.text" [color]="item.color" ></app-bubble> </div>
TypeScript代码
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; // 定义气泡数据结构,包含文本、颜色、坐标 interface RadialBubble { text: string; color: string; x: number; y: number; } @Component({ selector: 'app-radial-graph', templateUrl: './radial-graph.component.html', styleUrls: ['./radial-graph.component.css'] }) export class RadialGraphComponent implements OnInit { @ViewChild('container') containerRef!: ElementRef; radialItems: RadialBubble[] = []; centerX = 0; centerY = 0; bubbleSize = 60; // 气泡固定宽高 circleRadius = 160; // 外围气泡所在虚拟圆周的半径 // 模拟业务数据:2-10个气泡均可适配 bubbleDataSource = [ { text: '用户管理', color: '#3498db' }, { text: '数据统计', color: '#e74c3c' }, { text: '系统设置', color: '#f39c12' }, { text: '日志查询', color: '#9b59b6' }, { text: '权限控制', color: '#1abc9c' } ]; ngOnInit(): void { this.calculateBubblePositions(); } // 计算所有外围气泡的坐标 calculateBubblePositions(): void { const container = this.containerRef.nativeElement; // 容器中心坐标 this.centerX = container.offsetWidth / 2; this.centerY = container.offsetHeight / 2; const totalBubbles = this.bubbleDataSource.length; this.radialItems = this.bubbleDataSource.map((item, index) => { // 计算角度:从正上方开始排列,转弧度 const angle = (index * 360 / totalBubbles) * Math.PI / 180 - Math.PI / 2; // 基于圆周半径计算气泡中心坐标 const x = this.centerX + this.circleRadius * Math.cos(angle); const y = this.centerY + this.circleRadius * Math.sin(angle); return { ...item, x, y }; }); } // 窗口 resize 时重新计算坐标,保证自适应 ngAfterViewInit(): void { window.addEventListener('resize', () => this.calculateBubblePositions()); } }
父组件样式
.radial-container { position: relative; width: 450px; height: 450px; margin: 2rem auto; } .radial-container svg { position: absolute; top: 0; left: 0; pointer-events: none; /* 禁止SVG拦截气泡的点击事件 */ }
2. 气泡子组件
模板代码
<div class="bubble" [style.backgroundColor]="color" [style.top.px]="top" [style.left.px]="left"> <span class="bubble-text">{{ text }}</span> </div>
TypeScript代码
import { Component, Input } from '@angular/core'; @Component({ selector: 'app-bubble', templateUrl: './bubble.component.html', styleUrls: ['./bubble.component.css'] }) export class BubbleComponent { @Input() top = 0; @Input() left = 0; @Input() text = ''; @Input() color = '#cccccc'; }
气泡组件样式
.bubble { position: absolute; width: 60px; height: 60px; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 3px 10px rgba(0,0,0,0.15); transition: transform 0.2s; } .bubble:hover { transform: scale(1.1); } .bubble-text { color: white; font-size: 13px; text-align: center; padding: 0 5px; }
关键细节说明
- 连线颜色联动:每个气泡的
color属性直接绑定到SVG连线的stroke属性,实现气泡与连线颜色完全一致 - 箭头实现:通过SVG的
<marker>定义可复用箭头,orient="auto"保证箭头始终指向气泡 - 坐标计算:所有位置逻辑集中在父组件,避免子组件分散计算导致的坐标偏差
- 自适应处理:监听窗口resize事件,重新计算所有坐标,保证在容器尺寸变化时布局正常
内容的提问来源于stack exchange,提问作者Zek Teadis
相关产品推荐
相关产品推荐

