Angular 13组织架构图:父节点与子节点曲线连接线实现问题
解决Angular 13组织架构图父子节点曲线连接线的对齐与自适应问题
核心问题分析
当前实现的SVG曲线未关联子节点的实际位置,固定路径无法匹配子卡片中心,且子节点展开后容器宽度变化时曲线无法自适应调整。以下是针对性解决方案:
步骤1:获取子节点位置与偏移量
在组件类中通过ViewChildren获取所有子节点实例,结合ElementRef获取DOM元素,计算每个子节点中心相对于父节点连接线容器(#nodeLinesContainer)中心的水平偏移。
import { Component, Input, ViewChild, ViewChildren, QueryList, ElementRef, AfterViewInit, OnDestroy } from '@angular/core'; @Component({ selector: 'stakeholders-map-node', templateUrl: './stakeholders-map-node.component.html', styleUrls: ['./stakeholders-map-node.component.scss'] }) export class StakeholdersMapNodeComponent implements AfterViewInit, OnDestroy { @Input() nodeId: any; @ViewChild('nodeLinesContainer') nodeLinesContainer!: ElementRef<HTMLDivElement>; @ViewChildren('childNode') childNodes!: QueryList<ElementRef>; private resizeObservers: ResizeObserver[] = []; pathDataList: string[] = []; get node() { // 保留原有节点数据获取逻辑 return /* 你的节点数据 */; } get centerIndex() { // 保留原有计算逻辑 if (!this.node) { return 0 } const length = this.node.childrenCount(); if (length % 2 === 0) { const lowerHalf = (length / 2) - 1; const upperHalf = lowerHalf + 1; return (lowerHalf + upperHalf) / 2; } return Math.floor(length / 2); } ngAfterViewInit(): void { this.updatePaths(); this.setupResizeListeners(); // 监听子节点列表变化(递归渲染时新增/移除节点) this.childNodes.changes.subscribe(() => { this.updatePaths(); this.setupResizeListeners(); }); } ngOnDestroy(): void { this.resizeObservers.forEach(observer => observer.disconnect()); } private setupResizeListeners(): void { // 销毁旧监听 this.resizeObservers.forEach(observer => observer.disconnect()); this.resizeObservers = []; // 监听连接线容器尺寸变化 const containerObserver = new ResizeObserver(() => this.updatePaths()); containerObserver.observe(this.nodeLinesContainer.nativeElement); this.resizeObservers.push(containerObserver); // 监听每个子节点尺寸变化 this.childNodes.forEach(child => { const observer = new ResizeObserver(() => this.updatePaths()); observer.observe(child.nativeElement); this.resizeObservers.push(observer); }); } private updatePaths(): void { if (!this.node || !this.nodeLinesContainer) return; const containerRect = this.nodeLinesContainer.nativeElement.getBoundingClientRect(); const containerCenterX = containerRect.width / 2; this.pathDataList = []; this.childNodes.forEach((child, index) => { const childRect = child.nativeElement.getBoundingClientRect(); const childCenterX = childRect.left - containerRect.left + childRect.width / 2; const offset = childCenterX - containerCenterX; if (index === this.centerIndex) { // 中心节点:垂直直线对齐子节点中心 this.pathDataList.push(`M ${childCenterX},0 L ${childCenterX},75`); } else if (index < this.centerIndex) { // 左侧节点:贝塞尔曲线,控制弯曲程度适配偏移量 const controlOffset = Math.abs(offset) * 1.5; this.pathDataList.push(`M ${containerCenterX},0 C ${containerCenterX + controlOffset},0 ${childCenterX - controlOffset},75 ${childCenterX},75`); } else { // 右侧节点:贝塞尔曲线,控制弯曲程度适配偏移量 const controlOffset = Math.abs(offset) * 1.5; this.pathDataList.push(`M ${containerCenterX},0 C ${containerCenterX - controlOffset},0 ${childCenterX + controlOffset},75 ${childCenterX},75`); } }); } }
步骤2:修改模板动态绑定路径
将原模板中固定的SVG path 替换为绑定组件中计算的pathDataList,移除固定viewBox让SVG自适应容器尺寸:
<!-- CHILDREN --> <div class="node-card">{{ node.name }}</div> <div class="tree-children" *ngIf="node.isExpanded"> <div class="tree-elements-group"> <div #nodeLinesContainer class="node-lines flex flex-row"> <div *ngFor="let child of node.children; let i = index" class="line"> <svg width="100%" height="75" preserveAspectRatio="none"> <path [d]="pathDataList[i]" stroke="#71CAAE" stroke-width="5" fill="none" vector-effect="non-scaling-stroke"></path> </svg> </div> </div> <ng-container *ngFor="let child of node.children; let i = index"> <stakeholders-map-node #childNode [nodeId]="child" class="tree-node tree-child"></stakeholders-map-node> </ng-container> </div> </div>
步骤3:调整CSS确保布局对齐
为.line和子节点添加样式,保证SVG容器与对应子节点宽度匹配:
.node-lines { position: relative; width: 100%; margin-bottom: 1rem; // 根据布局调整间距 } .line { flex: 1; position: relative; } .tree-child { flex: 1; // 确保子节点宽度均匀分配,与.line容器一一对应 }
关键说明
- 动态路径计算:通过
getBoundingClientRect()获取元素实际位置,生成适配子节点中心的贝塞尔曲线 - 自适应监听:使用
ResizeObserver实时监听容器和子节点尺寸变化,自动更新曲线路径 - 递归兼容:监听子节点列表的动态变化,确保递归渲染时曲线始终对齐正确
内容的提问来源于stack exchange,提问作者Sergio Mendez
相关产品推荐
相关产品推荐

