- 前端
- UI组件
【免费下载链接】tether
A positioning engine to make overlays, tooltips and dropdowns better
Tether 官方文档的 Examples 章节以“示例目录”的形式,给出了从入门到进阶共 9 个可直接运行的定位场景(examples/目录)。本文逐个拆解这些示例的完整配置——包括attachment、targetAttachment、constraints、targetModifier等参数的真实取值——并对照 src/js/tether.js 与 src/js/constraint.js 的源码,解释每种定位策略(越界隐藏、贴边钉住、可见区对齐、滚动条跟随、链式元素)是如何实现的。读完后你可以按图索骥地复现每个 Demo,并理解其背后的调用链。
示例总览:目录结构与运行前提
官方文档 docs/2-Examples/1-list_of_examples.md 将示例分为 Beginner 与 Advanced 两级,页面版导航 examples/index.html 与其内容一一对应:
| 级别 | 示例 | 解决的问题 |
|---|---|---|
| Beginner | examples/simple | 最基础的双元素定位入门 |
| Beginner | examples/out-of-bounds | 元素将要超出屏幕时将其隐藏 |
| Beginner | examples/pin | 将元素钉在可视区内,永不越出 |
| Beginner | examples/enable-disable | 用 JavaScript 动态启用/禁用 Tether |
| Advanced | examples/content-visible | 用visible修饰器对齐到另一元素的可见部分 |
| Advanced | examples/dolls | 数十个元素链式 tether 的性能演示 |
| Advanced | examples/element-scroll | 用scroll-handle修饰器跟随元素滚动条 |
| Advanced | examples/scroll | 用scroll-handle修饰器跟随页面滚动条 |
| Advanced | examples/viewport | 将元素对齐到视口中央 |
所有示例共享两个前提:
- 需要先构建产物:每个示例的
index.html都引用../../dist/js/tether.js,该文件由 rollup.config.js 打包生成。根据 package.json 的 scripts,pnpm build(内部为pnpm clean && rollup -c)会产出dist/,pnpm start则以 watch 模式持续构建; - 需要静态服务器:示例页面是纯静态 HTML。仓库的 Cypress 流程(
test:cy:ci)会用http-server -p 9002启动start-test-server,本地运行示例时同样需要任意静态服务器(示例本身不依赖构建工具运行)。
Beginner 示例
simple:最小可用的双元素定位
simple/index.html 是官方推荐的起点,页面包含两个div(.element与.target)和一段 10 行左右的脚本:
<div class="element"></div> <div class="target"></div> <script src="../../dist/js/tether.js"></script> <script> new Tether({ element: '.element', target: '.target', attachment: 'top left', targetAttachment: 'top right', constraints: [{ to: 'window', attachment: 'together' }] }); </script>配置要点:
element/target接受 CSS 选择符字符串。在 src/js/tether.js 的setOptions中,字符串会被document.querySelector解析为真实元素,jQuery 对象则取[0]解包;attachment: 'top left'表示锚定元素自身的左上角,targetAttachment: 'top right'表示锚到目标的右上角,即气泡贴在目标右侧;constraints: [{ to: 'window', attachment: 'together' }]约束两个元素必须保持在窗口内;页面提示“Resize the page to see the Tether flip”——缩小窗口时 Tether 会自动翻转(flip)锚点方向,这正是targetAttachment默认值auto auto参与的结果(默认值见 src/js/tether.js 中的defaults:offset: '0 0'、targetOffset: '0 0'、targetAttachment: 'auto auto'、classPrefix: 'tether')。
该示例由 test/cypress/integration/simple.cy.js 的端到端测试持续验证。
out-of-bounds:越界即隐藏
out-of-bounds/index.html 与 simple 几乎相同,多了两段关键代码:
.tether-element.tether-out-of-bounds { display: none; }tether.on('update', function(event) { console.log(event); });原理在 src/js/constraint.js 中:约束模块计算元素是否越界(_calculateOOBAndPinnedLeft/_calculateOOBAndPinnedTop,见 constraint.js),越界时给元素添加tether-out-of-bounds类(由classPrefix(默认tether)与out-of-bounds组合,见 constraint.js);示例中的 CSS 利用这个类把元素直接display: none。配合tether.on('update', ...)事件回调(Tether 每次重定位都会触发),你可以在应用层做更精细的处理,例如淡出或重新锚定。
pin:把元素钉在可视区内
pin/index.html 只改了约束的一项配置:
constraints: [{ to: 'window', pin: true }]pin的取值在 constraint.js 中解析:传入字符串(如'left')会按逗号拆分为数组;传入true则等价于['top', 'left', 'right', 'bottom'],即四个方向全部钉住。被钉住的每条边会额外生成pinned-top/pinned-left等类名(constraint.js),供 CSS 做进一步样式区分;同时元素位置被钳制在约束边界内,表现为“缩放窗口时元素贴着屏幕边缘移动而永不消失”。
enable-disable:运行时开关
enable-disable/index.html 演示了 Tether 实例的两个生命周期方法:
var tether = new Tether({ element: '.element', target: '.target', attachment: 'top left', targetAttachment: 'top right' }); document.querySelector('.target').addEventListener('click', function(){ if (tether.enabled) tether.disable(); else tether.enable(); });点击绿色 target 即在启用/禁用之间切换。从源码看(src/js/tether.js),enable()会给元素与目标添加tether-enabled类(同样由classPrefix组合而来)并调用position()执行定位;disable()则移除该类,实例的enabled标志随之翻转,供业务代码做状态判断。这个“CSS 类 + 实例标志”的双重状态设计,使 enable/disable 既能驱动样式又能驱动逻辑。
Advanced 示例
content-visible:对齐到另一元素的“可见部分”
content-visible/index.html 构造了一个 600vh 高的蓝色长条(.content-box),其中有一个 200px 高的黄色.element,核心配置是:
new Tether({ element: '.element', target: '.content-box', attachment: 'middle center', targetAttachment: 'middle center', targetModifier: 'visible' });targetModifier: 'visible'是整个示例的关键。在 src/js/tether.js 的getTargetBounds中,当targetModifier === 'visible'时,目标边界不再取元素的完整 bounds,而是取getVisibleBounds(bodyElement, target)——即目标元素与视口重叠的“可见部分”的边界。于是.element会始终对齐到.content-box当前露在屏幕内那一段的中央,滚动页面时对话框跟着“可见中段”移动。实现细节(可见区裁剪算法)位于 src/js/utils/bounds.js。
viewport:把元素固定在视口中央
viewport/index.html 是visible修饰器的第二种用法——目标直接取document.body:
new Tether({ element: '.element', target: document.body, attachment: 'middle center', targetAttachment: 'middle center', targetModifier: 'visible' });body的可见部分恰好就是当前视口,因此.element永远停留在视口正中,页面滚动(示例动态生成了 300 个彩色块制造可滚动内容)也不影响它。页面提示可检查元素最终计算样式,Tether 在此场景下会决定使用position: fixed来维持位置。源码中还有一个等价捷径:src/js/tether.js 里,直接把target写成字符串'viewport'时,会自动改写为document.body+targetModifier: 'visible',与示例的显式写法完全等效。
scroll:跟随页面滚动条
scroll/index.html 在一整页约瑟夫·康拉德的小说正文上,把一个提示条 tether 到页面滚动进度:
new Tether({ element: '.pointer', attachment: 'middle right', targetAttachment: 'middle left', targetModifier: 'scroll-handle', target: document.body });当targetModifier === 'scroll-handle'时,getTargetBounds返回getScrollHandleBounds(src/js/tether.js),即目标滚动条“拇指”(thumb)的几何位置。于是.pointer在垂直方向上始终与页面滚动拇指同步,随滚动上下移动;示例再叠加一段自定义脚本,把当前章节标题与滚动百分比(pageYOffset / (scrollHeight - innerHeight))写进提示条。注意 src/js/tether.js 中的配套逻辑:scroll-handle修饰器会把scrollParents直接锁定为[this.target],而普通场景则用getScrollParents(target)收集滚动父级,两者决定了滚动监听的注册对象。
element-scroll:跟随“元素内部”的滚动条
element-scroll/index.html 把同样的思路搬到容器级滚动上:页面中有一个position: fixed、overflow-y: scroll的 80vh 阅读框.scroll(并刻意用::-webkit-scrollbar { display: none }隐藏原生滚动条),配置为:
new Tether({ element: pointer, // .pointer target: scroll, // .scroll attachment: 'middle right', targetAttachment: 'middle left', targetModifier: 'scroll-handle' });与 scroll 示例的差异在于target是任意元素而非 body,Tether 会为它计算一个替代滚动条.pointer(3.6em 高、与容器等宽),垂直位置严格映射该容器scrollTop / (scrollHeight - clientHeight)的进度。示例后续代码(高亮当前句)只是演示性效果,与 Tether 无关。这展示了scroll-handle修饰器可以驱动“自定义滚动条、阅读进度条、章节导航”一类组件。
dolls:60 个元素的链式性能演示
dolls/index.html 配合 examples/dolls/dolls.js 是一个压力/性能演示:脚本创建 60 个div追加进.scroll容器,每个元素 tether 到前一个元素,形成一条链:
var count = 60; var parent = null; var dir = 'left'; while (count--){ var el = document.createElement('div'); el.setAttribute('data-index', count); document.querySelector('.scroll').appendChild(el); if (count % 10 === 0) dir = dir == 'right' ? 'left' : 'right'; if (parent){ tethers.push(new Tether({ element: el, target: parent, attachment: 'middle ' + dir, targetOffset: (dir == 'left' ? '10px 10px' : '10px -10px') })); } parent = el; }可以看到链的走向每 10 个元素左右折返一次(dir在 left/right 间切换),targetOffset给出 10px 的间隙。演示的交互是“拖动左上角第一个元素”:dolls.js中监听mousedown/mousemove更新被拖元素的top/left,随后调用静态方法Tether.position()一次性重算全部实例——这是链式布局的关键,否则只有第一个 Tether 更新而其余 59 个滞后。这个示例直观验证了 Tether 批量实例下的重定位行为。
示例的自动化验证
文档目录中未列出、但对“示例可长期运行”至关重要的一点:这些示例有专门的端到端测试。test/cypress/integration/ 下存在simple.cy.js、enable-disable.cy.js、pin.cy.js、scroll.cy.js、content-visible.cy.js、testbed.cy.js等用例,配合package.json中的test:cy:ci(先pnpm build,再起http-server -p 9002,最后cypress run)形成闭环——也就是说每个 Demo 的行为变更都会被回归测试捕获。本地想打开示例时,流程为:pnpm build生成dist/,再在仓库根目录起任意静态服务器访问examples/<示例名>/index.html。
小结:从示例到配置的选择路径
综合这 9 个 Demo,可以把 Tether 的定位能力归纳为一张“场景 → 参数”速查表:
| 场景 | 关键参数 |
|---|---|
| 基础气泡/工具提示 | attachment+targetAttachment+constraints: [{ to: 'window', attachment: 'together' }] |
| 放不下就隐藏 | 同上约束,靠默认类tether-out-of-bounds控制显示 |
| 永不越出窗口 | 约束中加pin: true(等价四边钉住) |
| 动态开关 | 实例方法tether.enable()/tether.disable()+tether.enabled标志 |
| 对齐可见区 | targetModifier: 'visible'(或target: 'viewport'简写) |
| 跟随滚动条/进度 | targetModifier: 'scroll-handle',target 可为 body 或任意可滚动元素 |
文档结尾也明确欢迎社区贡献新示例(“please send a PR with any examples you might create”)。结合上表与 examples/ 下每个目录“一个 index.html 自包含”的组织方式,扩展一个新 Demo 的成本很低:新建目录、写一个引用../../dist/js/tether.js的页面,需要时再补一个 Cypress 用例即可。
- 前端
- UI组件
【免费下载链接】tether
A positioning engine to make overlays, tooltips and dropdowns better
相关推荐
Zustand 官方 Starter 示例解读:从零搭建计数 Demo 并剖析 create 的底层实现
Zustand 官方 Starter 示例解读:从零搭建计数 Demo 并剖析 create 的底层实现 Zustand(🐻)是一个轻量级的 React 状态
前端CPython `email` 包实战:官方 7 个示例逐行精讲(读写、发送与解析邮件)
CPython email 包实战:官方 7 个示例逐行精讲(读写、发送与解析邮件) 本文以 CPython 官方文档 Doc/library/email.ex
编程语言语言运行时解释器标准库KubeSphere 配置解码底层库解析:mapstructure v1.5.0 变更日志与源码实现对照
KubeSphere 配置解码底层库解析:mapstructure v1.5.0 变更日志与源码实现对照 本文以 KubeSphere 仓库 vendor 目录
前端UI组件设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考