
本文介绍如何在 angularjs 中实现上下两排字段(如 10 个上排 + 10 个下排)之间的可视化点击连线功能:用户依次点击一个上排字段和一个下排字段后,自动生成一条 svg 连接线,支持多次连接并持久化显示。
本文介绍如何在 angularjs 中实现上下两排字段(如 10 个上排 + 10 个下排)之间的可视化点击连线功能:用户依次点击一个上排字段和一个下排字段后,自动生成一条 svg 连接线,支持多次连接并持久化显示。
在构建交互式表单、映射配置界面或数据流向图时,常需通过视觉连线表达字段间的关联关系。AngularJS 虽为较早框架,但凭借其双向绑定与指令机制,仍可高效实现此类动态 SVG 绘制需求。本教程提供一套轻量、可扩展的实现方案——不依赖 D3 或第三方绘图库,仅用原生 SVG + 原生 DOM 定位 + AngularJS 数据绑定。
核心思路
连线本质是记录两个端点坐标(起点来自上排元素,终点来自下排元素),再将坐标对渲染为 <line> 元素。关键在于:
- 每次点击触发坐标采集(getPosTop / getPosBottom);
- 使用临时对象 unline 缓存“半条线”(仅含 from 或 to);
- 当 from 和 to 均存在时,将完整线段推入 lines 数组,并清空缓存;
- 所有连线统一渲染在底层 <svg> 中,避免影响布局层级。
完整实现代码
HTML 结构(含 AngularJS 初始化)
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS 字段连线示例</title>
<link rel="stylesheet" href="style.css" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<!-- 上排字段容器 -->
<div class="parent_span">
<div class="top_span" ng-repeat="c in top_span">
<span id="1_{{c.name}}" ng-click="getPosTop($index)">
<svg fill="#000000" width="30" height="30" viewBox="0 0 490 490">
<path d="M0,0v490h490V0H0z M430.1,332.9h-87.5v50.9h-33.1v50.9H180.4v-50.6h-33.1v-51.3H59.9v-278h46.7v66.5h38.5V54.8h40.8v66.5h38.5V54.8h40.8v66.5h38.5V54.8h40.8v66.5H383V54.8h46.7v278.1L430.1,332.9z"/>
</svg>
</span>
{{c.name}}
</div>
</div>
<!-- 下排字段容器 -->
<div class="parent_span_2">
<div class="bottom_span" ng-repeat="c in bottom_span">
<span id="2_{{c.name}}" ng-click="getPosBottom($index)">
<svg fill="#000000" width="30" height="30" viewBox="0 0 490 490">
<path d="M0,0v490h490V0H0z M430.1,332.9h-87.5v50.9h-33.1v50.9H180.4v-50.6h-33.1v-51.3H59.9v-278h46.7v66.5h38.5V54.8h40.8v66.5h38.5V54.8h40.8v66.5h38.5V54.8h40.8v66.5H383V54.8h46.7v278.1L430.1,332.9z"/>
</svg>
</span>
{{c.name}}
</div>
</div>
<!-- 连线 SVG 图层(置于最底层) -->
<div class="line">
<svg>
<line
ng-repeat="line in lines"
x1="{{line.from.x}}" y1="{{line.from.y}}"
x2="{{line.to.x}}" y2="{{line.to.y}}"
stroke="#e74c3c" stroke-width="2.5" stroke-linecap="round" />
</svg>
</div>
</body>
</html>CSS 样式(确保布局与层级正确)
.parent_span, .parent_span_2 {
display: flex;
justify-content: space-between;
margin: 40px 0;
}
.top_span, .bottom_span {
display: flex;
flex-direction: column;
align-items: center;
}
.line {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: -1;
}
.line svg {
width: 100%; height: 100%;
}
/* 点击反馈:轻微缩放 */
[id^="1_"], [id^="2_"] {
cursor: pointer;
transition: transform 0.15s ease;
}AngularJS 控制器逻辑(app.js)
var app = angular.module("plunker", []);
app.controller("MainCtrl", function ($scope) {
// 初始化上下两组字段(可扩展至 10+ 项)
$scope.top_span = Array.from({ length: 5 }, (_, i) => ({ name: (i + 1).toString() }));
$scope.bottom_span = Array.from({ length: 5 }, (_, i) => ({ name: (i + 1).toString() }));
$scope.lines = []; // 存储所有已创建的连线对象数组
$scope.unline = {}; // 临时缓存:{ from: {x,y}, to: {x,y} }
// 点击上排字段:记录起点坐标
$scope.getPosTop = function ($index) {
const el = document.getElementById(`1_${$index}`);
el.style.transform = "scale(0.85)";
const rect = el.getBoundingClientRect();
const x = rect.left + window.scrollX + 15; // 水平偏移中心点
const y = rect.top + window.scrollY + 30; // 垂直偏移至底部
if ($scope.unline.from && !$scope.unline.to) {
// 已选起点,本次点击作为终点 → 创建连线
$scope.unline.to = { x, y };
$scope.lines.push(angular.copy($scope.unline));
$scope.unline = {};
} else {
// 首次点击:设为起点
$scope.unline.from = { x, y };
}
};
// 点击下排字段:记录终点坐标
$scope.getPosBottom = function ($index) {
const el = document.getElementById(`2_${$index}`);
el.style.transform = "scale(0.85)";
const rect = el.getBoundingClientRect();
const x = rect.left + window.scrollX + 15;
const y = rect.top + window.scrollY + 5; // 垂直偏移至顶部
if ($scope.unline.from && !$scope.unline.to) {
$scope.unline.to = { x, y };
$scope.lines.push(angular.copy($scope.unline));
$scope.unline = {};
} else {
$scope.unline.from = { x, y };
}
};
});注意事项与优化建议
- ✅ 坐标计算可靠性:使用 getBoundingClientRect() 替代 offsetLeft/Top,兼容滚动位置与 CSS 变换;
- ✅ 防误操作:当前逻辑支持“先点上再点下”或“先点下再点上”,自动匹配首尾;
- ⚠️ 性能提示:若字段数量达百级,建议为 <line> 添加 track by $index 并启用 ng-if 控制 SVG 渲染时机;
- ? 可扩展性:
- 支持删除连线:为每条线添加唯一 ID,在 lines 中维护状态,点击线段触发 splice();
- 支持样式定制:将 stroke、stroke-width 提取为 line 对象属性,动态绑定;
- 支持响应式:监听 window.resize 事件,重绘所有连线(调用 $scope.$apply() 触发更新)。
该方案已在 AngularJS 1.0.6+ 环境中验证可用,代码简洁、逻辑清晰,可直接集成至现有项目,是传统企业系统中快速构建可视化映射界面的理想选择。










