PostsMapsLinks
技术博客 / Coder

🔗 @lionad/leader-line:在网页元素之间绘制引导线

一个基于 Vite 8 + TypeScript 7 的 SVG 引导线库,提供现代化构建产物、完整类型定义与读写分离的渲染调度。

在需要表达元素之间关系的界面里,引导线(Leader Line)是一种便宜又直观的可视化手段。流程图、表单联动、节点编辑器、看板依赖……只要两个 DOM 元素需要被“连起来”,就会用到它。

最近把 anseki/leader-line fork 成了一个现代维护版 @lionad/leader-line,顺手补上了类型、ESM/CJS/IIFE 三产物,以及一个可以批量合并重排的渲染调度器。这篇文章简单介绍它解决了什么问题、怎么用、以及在 Vue / Nuxt 中的集成方式。

基础用法

安装:

pnpm add @lionad/leader-line

ESM 或 CommonJS 都能直接导入,也支持 <script> 标签直载:

import LeaderLine from '@lionad/leader-line'

const line = new LeaderLine(
  document.getElementById('start'),
  document.getElementById('end'),
  { color: 'coral', endPlug: 'arrow2' }
)

API 与上游基本兼容,所以旧项目迁移时通常只改包名即可。

Vue / Nuxt 集成

命令式 API 直接塞进 Vue 的响应式系统会比较啰嗦,所以另外提供了两个平行子包:

  • @lionad/leader-line-vue:composable + 组件
  • @lionad/leader-line-nuxt:Nuxt 4 模块(包含 vue 包)

单线响应式

<script setup>
import { ref, useTemplateRef } from 'vue'
import { useLeaderLine } from '@lionad/leader-line-vue'

const a = useTemplateRef('a')
const b = useTemplateRef('b')
const color = ref('coral')

const { line, position, show, hide, remove } = useLeaderLine(a, b, {
  color,
  endPlug: 'arrow2'
})
</script>

colorref 时,修改 color.value 会自动调用 setOptions,不会重建整条线。组件卸载时也会自动 remove,不用手动清理。

批量连线

import { useLeaderLines } from '@lionad/leader-line-vue'

const edges = ref([
  { key: 'a→b', start: elA, end: elB, color: 'coral', data: { fieldId: 'x' } }
])

const { lines, getLine, findWhere, removeWhere } = useLeaderLines(edges, {
  preventSame: true
})

edgespush / splice / 同 key 属性修改 / 重排都会按最小代价更新,避免频繁重建 SVG。

Nuxt 零配置模块

export default defineNuxtConfig({
  modules: ['@lionad/leader-line-nuxt'],
  leaderLine: {
    defaults: { color: '#4a9eff', size: 2 }
  }
})

之后所有 composable 和 <LeaderLine> 组件都免 import 直接使用,且 SSR 安全(client-only)。

渲染调度

上游的 position() 是同步的。如果一帧内更新 N 条线,每条线都先读布局再写 DOM,会触发 N 次强制同步 reflow。在拖拽或窗口 resize 这种高频场景里,代价很明显。

这个版本把一帧内的 position() 更新合并为“全部读 → 全部写”,reflow 次数降到至多 1 次。用法有两种:

// 方式 A:逐调用调度(拖拽等高频场景推荐)
lines.forEach(line => line.requestPosition())

// 方式 B:全局开关,position() 自动走调度
LeaderLine.deferPositionUpdate = true
lines.forEach(line => line.position())

position() 默认保持同步语义以兼容旧代码,LeaderLine.positionByWindowResize 内部已经走调度。

相比上游的新增与修复

功能

  • ESM 默认导出
  • options.svgContainer:指定 SVG 挂载容器,默认 document.body
  • 完整 TypeScript 类型定义
  • 三产物:dist/leader-line.mjsdist/leader-line.cjsdist/leader-line.min.js

修复

  • aplStats.position_plugOverheadSE is not defined
  • @micro-zoe/micro-app 中的 getFrame error

迁移

从旧 leader-line 包迁移:

- "leader-line": "^1.1.0"
+ "@lionad/leader-line": "^1.0.0"
  • 不再支持 IE11 与旧 Edge
  • 产物路径变为 dist/ 下三格式
  • Bower 已移除
  • API 完全兼容,requestPosition() / deferPositionUpdate 为纯新增

结语

@lionad/leader-line 没有重新发明引导线,只是把上游库搬到了一个更现代的维护轨道上:类型完整、构建产物齐全、渲染有调度、Vue / Nuxt 有封装。如果你的项目里正好需要画几根线,可以试试看。

仓库地址:Lionad-Morotar/leader-line

Copyright © 2024 Lionad - CC-BY-NC-CD-4.0