用 <svg> 画带箭头的课程依赖线最可控,因其能精确控制起点、终点、箭头朝向和层级;需将箭头定义于 <defs> 内并用 marker-end 引用,连线用 <line> 而非 <path>,坐标须经 getScreenCTM().inverse() 转换,且需在 DOMContentLoaded 和 resize 时动态重算。

用 <svg> 画带箭头的课程依赖线最可控
纯 CSS 实现节点连线在复杂路径(比如交叉、弯曲、动态增删)下容易错位或失真,<svg> 是唯一能精确控制起点、终点、箭头朝向和层级关系的方式。浏览器原生支持,无需额外库,兼容性从 IE9+ 开始就稳定。
关键点:箭头定义必须放在 <defs> 里,再通过 marker-end 引用;否则 Chrome/Firefox 渲染不一致。
- 每个课程节点用
<g class="course-node">包裹,设data-x/data-y属性存坐标,方便 JS 计算连线 - 依赖线用
<line>,不要用<path>——后者对简单直线是过度设计,且marker-end在<line>上行为更可预测 - 箭头尺寸建议设为
refX="10" refY="3" markerWidth="10" markerHeight="6",太小在高 DPI 屏上模糊,太大压过节点文字
getBoundingClientRect() 动态取节点位置比硬编码坐标靠谱
写死 x/y 值看似快,但一旦页面缩放、字体加载延迟、响应式重排,连线立刻偏移。真实项目中,所有依赖线都该在 DOMContentLoaded 后 + resize 事件里重新计算。
注意:不能直接对 <div> 节点调 getBoundingClientRect() 然后塞进 <line> 的 x1/y1 —— SVG 坐标系和 HTML 文档流坐标系不同,需转换。
立即学习“前端免费学习笔记(深入)”;
- 用
svg.getScreenCTM().inverse()把 DOM 坐标转为 SVG 用户坐标(示例中常被跳过,导致连线漂移) - 如果课程节点有
transform: translate()或 margin/padding,getBoundingClientRect()返回的是布局后位置,可直接用,但要确保父容器没overflow: hidden截断边界 - 批量更新时,先
line.setAttribute()批量设属性,最后再svg.appendChild(),避免反复重排
IE11 下 marker-end 不生效?补 stroke-linecap="round"
IE11 对 SVG marker 支持有缺陷:当线段极短(markerUnits="strokeWidth" 的实现偏差。
绕过方式不是降级成三角形 <path>,而是加一个视觉补偿:
- 给
<line>加stroke-linecap="round",让末端变圆点,至少保留“有连接”的语义 - 同时把
stroke-width设为1.5(别用整数),IE11 对非整数描边更敏感,能触发 marker 渲染 - 若必须显示箭头,改用
markerUnits="userSpaceOnUse"并手动算refX/refY,但代价是失去缩放自适应
课程节点拖拽时连线卡顿?用 requestAnimationFrame 节流重绘
监听 mousemove 直接更新所有依赖线的 x2/y2,滚动或快速拖拽时帧率会掉到 10fps 以下。问题不在计算,而在频繁触发 SVG 布局重排。
真正有效的节流不是用 setTimeout 延迟,而是把更新逻辑塞进浏览器下一帧:
- 在
dragstart时记下被拖节点 ID,在mousemove中只存新坐标到变量,不操作 DOM - 用
requestAnimationFrame(updateLines)触发一次批量更新,updateLines函数里统一读坐标、批量设setAttribute - 拖拽结束(
dragend)后清空缓存,避免残留状态影响下次计算
最麻烦的其实是节点重叠检测——两个课程靠太近时,箭头可能画到文字上。这没法靠 SVG 自动解决,得在计算连线前加个最小间距判断,而且这个阈值还得随缩放比例动态调。没人提,但上线第一天就会被用户截图发群里问。



















