@qingwu-ui/scroll-fab 0.9.0-beta.1 → 0.9.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -7,6 +7,46 @@ interface ScrollFabPosition {
7
7
  bottom?: number;
8
8
  left?: number;
9
9
  }
10
+ /** Lenis 实例的最小结构性类型(组件不硬依赖 lenis 包,按结构识别) */
11
+ interface LenisLike {
12
+ scrollTo: (target: number | string | HTMLElement, options?: Record<string, unknown>) => void;
13
+ raf?: (time: number) => void;
14
+ destroy?: () => void;
15
+ start?: () => void;
16
+ stop?: () => void;
17
+ on?: (event: string, callback: (...args: unknown[]) => void) => void;
18
+ off?: (event: string, callback: (...args: unknown[]) => void) => void;
19
+ isScrolling?: boolean;
20
+ }
21
+ /** 内部自建 Lenis 时的构造配置(lenis 官方选项子集,允许透传其他字段) */
22
+ interface ScrollFabLenisConfig {
23
+ wrapper?: HTMLElement | Window;
24
+ content?: HTMLElement;
25
+ duration?: number;
26
+ easing?: (t: number) => number;
27
+ /** 默认强制 false:自建实例不劫持用户滚轮,只驱动按钮触发的程序滚动;显式置 true 则接管全局 */
28
+ smoothWheel?: boolean;
29
+ smoothTouch?: boolean;
30
+ wheelMultiplier?: number;
31
+ touchMultiplier?: number;
32
+ [key: string]: unknown;
33
+ }
34
+ /**
35
+ * 异形按钮形状(如银杏叶)。
36
+ * 双 path 才能保证进度描边「完全包裹」:fill 是叶片实体,outline 是外扩一圈的闭合轮廓,
37
+ * SVG 描边居中于路径,单条 path 必有一半笔画压进形状内部。
38
+ * outline 的起点约定在 12 点钟方向(描边顺时针推进)。
39
+ */
40
+ interface ScrollFabShape {
41
+ /** SVG viewBox,默认 "0 0 48 48";自定义 path 建议按 48×48 绘制 */
42
+ viewBox?: string;
43
+ /** 形状实体 path d(被填充) */
44
+ fill: string;
45
+ /** 外扩进度轮廓 path d(track + 进度描边沿它走);可与 fill 同形再配 outlineTransform 外扩 */
46
+ outline: string;
47
+ /** 作用于 outline path 的 SVG transform,如 "translate(24 24) scale(1.1) translate(-24 -24)" */
48
+ outlineTransform?: string;
49
+ }
10
50
  interface ScrollFabOptions {
11
51
  /** 滚动容器,null(默认)为 window */
12
52
  target?: HTMLElement | null;
@@ -28,6 +68,18 @@ interface ScrollFabOptions {
28
68
  className?: string;
29
69
  /** 动画开关,关闭或 prefers-reduced-motion 时程序滚动瞬时到位,默认 true */
30
70
  animate?: boolean;
71
+ /**
72
+ * Lenis 平滑滚动(lenis 为 optional peerDependency,未安装静默回退内置 rAF):
73
+ * - 传 Lenis 实例:复用宿主实例,组件不负责销毁;
74
+ * - true:点击时动态 import lenis 并自建实例(仅驱动按钮程序滚动,不劫持滚轮);
75
+ * - 传配置对象:自建实例并透传配置(target 容器自动映射为 wrapper);
76
+ * - false / 缺省:内置 rAF 缓动。
77
+ */
78
+ lenis?: boolean | ScrollFabLenisConfig | LenisLike;
79
+ /** 异形形状(银杏叶等);传入后圆形外壳消失,进度沿外扩轮廓包裹 */
80
+ shape?: ScrollFabShape;
81
+ /** 自建 Lenis 实例就绪回调(外部实例注入时不触发),与 onEditorReady 模式对齐 */
82
+ onLenisReady?: (lenis: LenisLike) => void;
31
83
  /** 模式①(滚动到底部)无障碍标签 */
32
84
  ariaLabelToBottom?: string;
33
85
  /** 模式②(返回顶部)无障碍标签 */
@@ -39,11 +91,13 @@ interface ScrollFabOptions {
39
91
  //#region src/scroll-fab.d.ts
40
92
  declare class ScrollFab {
41
93
  readonly el: HTMLButtonElement;
42
- private readonly ring;
43
- private readonly track;
94
+ private ring;
95
+ private track;
96
+ private fillPath;
44
97
  private readonly icon;
45
- private readonly len;
98
+ private len;
46
99
  private readonly targetEl;
100
+ private readonly shape;
47
101
  private readonly ringMs;
48
102
  private readonly decayMs;
49
103
  private readonly cancelPx;
@@ -51,6 +105,13 @@ declare class ScrollFab {
51
105
  private readonly labelBottom;
52
106
  private readonly labelTop;
53
107
  private readonly onModeChange?;
108
+ private readonly onLenisReady?;
109
+ /** 外部注入:宿主实例,组件永不销毁 */
110
+ private externalLenis;
111
+ /** 自建配置:null = 不走 lenis;true 形态也归一成对象 */
112
+ private ownLenisConfig;
113
+ private ownLenis;
114
+ private ownLenisPromise;
54
115
  private mode;
55
116
  private progress;
56
117
  private hovering;
@@ -62,10 +123,15 @@ declare class ScrollFab {
62
123
  private ringRaf;
63
124
  private lastTs;
64
125
  private scrollRaf;
126
+ private lenisLoopRaf;
127
+ private lenisScrolling;
128
+ private lenisDest;
65
129
  private flashTimer;
66
130
  private tapResetTimer;
67
131
  private touchTap;
68
132
  private ro;
133
+ private actionSeq;
134
+ private destroyed;
69
135
  constructor(options?: ScrollFabOptions);
70
136
  /** 当前模式 */
71
137
  get currentMode(): ScrollFabMode;
@@ -75,6 +141,7 @@ declare class ScrollFab {
75
141
  scrollToTop(): void;
76
142
  destroy(): void;
77
143
  private makeCircle;
144
+ private makePath;
78
145
  private onEnter;
79
146
  private onLeave;
80
147
  private onDown;
@@ -92,20 +159,38 @@ declare class ScrollFab {
92
159
  private setMode;
93
160
  private doAction;
94
161
  private animateScroll;
162
+ /** 首次点击时动态 import lenis 并自建实例;未安装(或加载失败)→ null 永久回退 rAF */
163
+ private resolveOwnLenis;
164
+ /**
165
+ * 用 Lenis 执行程序滚动。
166
+ * 自建实例(own):组件自跑 raf loop(Lenis 需外部驱动 raf),保留 wheel/touch/keydown 打断与到底追击;
167
+ * 外部实例:宿主自带 raf loop 与手势处理,组件只发 scrollTo,不追击不打断。
168
+ */
169
+ private scrollWithLenis;
170
+ /** 自建实例的 raf 驱动 + 懒加载到底追击(对齐内置 rAF 语义) */
171
+ private startLenisLoop;
172
+ private stopLenisLoop;
173
+ /** 打断自建 Lenis 程序滚动:stop 冻结动画(下次发起前 start 复位) */
174
+ private onLenisInterrupt;
175
+ private endLenisScroll;
176
+ private animateRaf;
95
177
  private reducedMotion;
96
178
  private onInterrupt;
97
179
  private interruptSource;
180
+ /** 自建 Lenis 路线的打断监听 */
98
181
  private attachInterrupt;
99
182
  private detachInterrupt;
183
+ /** 内置 rAF 路线的打断监听 */
184
+ private attachRafInterrupt;
185
+ private detachRafInterrupt;
100
186
  private cancelScroll;
101
187
  private maxScroll;
102
188
  private targetPos;
103
189
  private scrollPos;
104
190
  private setScrollPos;
105
- private scrollTo;
106
191
  private onRecheck;
107
192
  private syncVisible;
108
193
  }
109
194
  //#endregion
110
- export { ScrollFab, type ScrollFabMode, type ScrollFabOptions, type ScrollFabPosition };
195
+ export { type LenisLike, ScrollFab, type ScrollFabLenisConfig, type ScrollFabMode, type ScrollFabOptions, type ScrollFabPosition, type ScrollFabShape };
111
196
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/scroll-fab.ts"],"mappings":";KAAY;;UAGK;EACf;EACA;EACA;EACA;;UAGe;;EAEf,SAAS;;EAET;;EAEA;;EAEA;;EAEA;;EAEA,mBAAmB;;EAEnB,WAAW;;EAEX;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,gBAAgB,MAAM;;;;cCvBX;WACF,IAAI;mBAEI;mBACA;mBACA;mBACA;mBAEA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;UAET;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;EAER,YAAY,UAAS;;MA4EjB,eAAe;;EAKnB;EAIA;EAIA;EAIA;UAoBQ;UAeA;UAMA;UAMA;UAeA;UAUA;UAeA;UAOA;UAUA;UAQA;UAIA;UAMA;;UAoBA;UAYA;UAOA;UAUA;UAIA;UAwCA;UAMA;UAIA;UAIA;UAMA;UAMA;UAUA;UAKA;UAIA;UAKA;UAKA;UAIA;UAIA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/scroll-fab.ts"],"mappings":";KAAY;;UAGK;EACf;EACA;EACA;EACA;;;UAIe;EACf,WACE,0BAA0B,aAC1B,UAAU;EAEZ,OAAO;EACP;EACA;EACA;EACA,MAAM,eAAe,cAAc;EACnC,OAAO,eAAe,cAAc;EACpC;;;UAIe;EACf,UAAU,cAAc;EACxB,UAAU;EACV;EACA,UAAU;;EAEV;EACA;EACA;EACA;GACC;;;;;;;;UASc;;EAEf;;EAEA;;EAEA;;EAEA;;UAGe;;EAEf,SAAS;;EAET;;EAEA;;EAEA;;EAEA;;EAEA,mBAAmB;;EAEnB,WAAW;;EAEX;;EAEA;;EAEA;;;;;;;;EAQA,kBAAkB,uBAAuB;;EAEzC,QAAQ;;EAER,gBAAgB,OAAO;;EAEvB;;EAEA;;EAEA,gBAAgB,MAAM;;;;cCjEX;WACF,IAAI;UAEL;UACA;UACA;mBACS;UACT;mBAES;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;;UAGT;;UAEA;UACA;UACA;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;EAER,YAAY,UAAS;;MA2GjB,eAAe;;EAKnB;EAIA;EAIA;EAIA;UAyBQ;UASA;UAaA;UAMA;UAMA;UAeA;UAUA;UAeA;UAOA;UAUA;UAQA;UAIA;UAMA;;UAoBA;UAYA;UAOA;UAUA;UAIA;;UAyBA;;;;;;UA6BA;;UA6BA;UAqBA;;UAQA;UAMA;UASA;UAmCA;UAMA;UAIA;;UAKA;UAMA;;UAOA;UAMA;UAMA;UAeA;UAKA;UAIA;UAKA;UAKA;UAIA"}
package/dist/index.mjs CHANGED
@@ -73,19 +73,26 @@ S16("<path d=\"m9 18 6-6-6-6\"/>");
73
73
  //#endregion
74
74
  //#region src/scroll-fab.ts
75
75
  /** 青梧UI ScrollFab 悬浮滚动栏:默认“滚动到底部”,桌面悬停/触屏按住绕满描边一圈翻转为“返回顶部”,点击(轻点)直接执行当前模式动作 */
76
+ const SVG_NS = "http://www.w3.org/2000/svg";
76
77
  const RADIUS = 21;
78
+ const FALLBACK_PATH_LEN = 100;
77
79
  const DEFAULT_LABEL_BOTTOM = "滚动到底部(悬停或按住绕圈可切换为返回顶部)";
78
80
  const DEFAULT_LABEL_TOP = "返回顶部(悬停或按住绕圈可切换为滚动到底部)";
79
81
  function easeInOutCubic(p) {
80
82
  return p < .5 ? 4 * p * p * p : 1 - (-2 * p + 2) ** 3 / 2;
81
83
  }
84
+ function isLenisInstance(v) {
85
+ return typeof v === "object" && v !== null && typeof v.scrollTo === "function";
86
+ }
82
87
  var ScrollFab = class {
83
88
  el;
84
89
  ring;
85
90
  track;
91
+ fillPath = null;
86
92
  icon;
87
93
  len;
88
94
  targetEl;
95
+ shape;
89
96
  ringMs;
90
97
  decayMs;
91
98
  cancelPx;
@@ -93,6 +100,13 @@ var ScrollFab = class {
93
100
  labelBottom;
94
101
  labelTop;
95
102
  onModeChange;
103
+ onLenisReady;
104
+ /** 外部注入:宿主实例,组件永不销毁 */
105
+ externalLenis = null;
106
+ /** 自建配置:null = 不走 lenis;true 形态也归一成对象 */
107
+ ownLenisConfig = null;
108
+ ownLenis = null;
109
+ ownLenisPromise = null;
96
110
  mode = "to-bottom";
97
111
  progress = 0;
98
112
  hovering = false;
@@ -104,12 +118,18 @@ var ScrollFab = class {
104
118
  ringRaf = null;
105
119
  lastTs = null;
106
120
  scrollRaf = null;
121
+ lenisLoopRaf = null;
122
+ lenisScrolling = false;
123
+ lenisDest = null;
107
124
  flashTimer = null;
108
125
  tapResetTimer = null;
109
126
  touchTap = false;
110
127
  ro = null;
128
+ actionSeq = 0;
129
+ destroyed = false;
111
130
  constructor(options = {}) {
112
131
  this.targetEl = options.target ?? null;
132
+ this.shape = options.shape ?? null;
113
133
  this.ringMs = Math.max(100, Math.min(5e3, options.ringDuration ?? 800));
114
134
  this.decayMs = Math.max(100, options.decayDuration ?? 300);
115
135
  this.cancelPx = Math.max(4, options.cancelThreshold ?? 10);
@@ -117,10 +137,20 @@ var ScrollFab = class {
117
137
  this.labelBottom = options.ariaLabelToBottom ?? DEFAULT_LABEL_BOTTOM;
118
138
  this.labelTop = options.ariaLabelToTop ?? DEFAULT_LABEL_TOP;
119
139
  this.onModeChange = options.onModeChange;
140
+ this.onLenisReady = options.onLenisReady;
141
+ if (isLenisInstance(options.lenis)) this.externalLenis = options.lenis;
142
+ else if (options.lenis != null && options.lenis !== false) {
143
+ const cfg = options.lenis === true ? {} : options.lenis;
144
+ this.ownLenisConfig = {
145
+ smoothWheel: false,
146
+ ...cfg
147
+ };
148
+ }
120
149
  this.el = document.createElement("button");
121
150
  this.el.type = "button";
122
151
  this.el.className = `qsf-root${options.className ? ` ${options.className}` : ""}`;
123
152
  this.el.dataset.mode = this.mode;
153
+ if (this.shape) this.el.dataset.shape = "on";
124
154
  this.el.setAttribute("aria-label", this.labelBottom);
125
155
  this.el.style.setProperty("--qsf-size", `${Math.max(44, options.size ?? 48)}px`);
126
156
  if (options.zIndex != null) this.el.style.setProperty("--qsf-z", String(options.zIndex));
@@ -128,17 +158,32 @@ var ScrollFab = class {
128
158
  if (options.position?.bottom != null) this.el.style.setProperty("--qsf-bottom", `${options.position.bottom}px`);
129
159
  if (options.position?.left != null) this.el.style.setProperty("--qsf-left", `${options.position.left}px`);
130
160
  if (options.position?.top != null) this.el.style.setProperty("--qsf-top", `${options.position.top}px`);
131
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
161
+ const svg = document.createElementNS(SVG_NS, "svg");
132
162
  svg.setAttribute("class", "qsf-svg");
133
- svg.setAttribute("viewBox", "0 0 48 48");
163
+ svg.setAttribute("viewBox", this.shape?.viewBox ?? "0 0 48 48");
134
164
  svg.setAttribute("aria-hidden", "true");
135
- this.track = this.makeCircle("qsf-track");
136
- this.ring = this.makeCircle("qsf-bar");
137
- svg.append(this.track, this.ring);
138
- let len = 2 * Math.PI * RADIUS;
165
+ if (this.shape) {
166
+ this.fillPath = document.createElementNS(SVG_NS, "path");
167
+ this.fillPath.setAttribute("class", "qsf-shape-fill");
168
+ this.fillPath.setAttribute("d", this.shape.fill);
169
+ this.track = this.makePath("qsf-track", this.shape.outline);
170
+ this.ring = this.makePath("qsf-bar", this.shape.outline);
171
+ if (this.shape.outlineTransform) {
172
+ this.track.setAttribute("transform", this.shape.outlineTransform);
173
+ this.ring.setAttribute("transform", this.shape.outlineTransform);
174
+ }
175
+ svg.append(this.fillPath, this.track, this.ring);
176
+ } else {
177
+ this.track = this.makeCircle("qsf-track");
178
+ this.ring = this.makeCircle("qsf-bar");
179
+ svg.append(this.track, this.ring);
180
+ }
181
+ let len;
182
+ if (this.shape) len = FALLBACK_PATH_LEN;
183
+ else len = 2 * Math.PI * RADIUS;
139
184
  try {
140
185
  const measured = this.ring.getTotalLength();
141
- if (Number.isFinite(measured)) len = measured;
186
+ if (Number.isFinite(measured) && measured > 0) len = measured;
142
187
  } catch {}
143
188
  this.len = len;
144
189
  this.ring.style.strokeDasharray = String(this.len);
@@ -179,9 +224,10 @@ var ScrollFab = class {
179
224
  this.animateScroll("bottom");
180
225
  }
181
226
  scrollToTop() {
182
- this.scrollTo(0);
227
+ this.animateScroll("top");
183
228
  }
184
229
  destroy() {
230
+ this.destroyed = true;
185
231
  this.el.removeEventListener("pointerenter", this.onEnter);
186
232
  this.el.removeEventListener("pointerleave", this.onLeave);
187
233
  this.el.removeEventListener("pointerdown", this.onDown);
@@ -194,6 +240,9 @@ var ScrollFab = class {
194
240
  (this.targetEl ?? window).removeEventListener("scroll", this.onRecheck);
195
241
  this.ro?.disconnect();
196
242
  this.cancelScroll();
243
+ this.stopLenisLoop();
244
+ this.ownLenis?.destroy?.();
245
+ this.ownLenis = null;
197
246
  if (this.ringRaf != null) cancelAnimationFrame(this.ringRaf);
198
247
  this.ringRaf = null;
199
248
  if (this.flashTimer != null) clearTimeout(this.flashTimer);
@@ -201,13 +250,19 @@ var ScrollFab = class {
201
250
  this.el.remove();
202
251
  }
203
252
  makeCircle(cls) {
204
- const c = document.createElementNS("http://www.w3.org/2000/svg", "circle");
253
+ const c = document.createElementNS(SVG_NS, "circle");
205
254
  c.setAttribute("class", cls);
206
255
  c.setAttribute("cx", "24");
207
256
  c.setAttribute("cy", "24");
208
257
  c.setAttribute("r", String(RADIUS));
209
258
  return c;
210
259
  }
260
+ makePath(cls, d) {
261
+ const p = document.createElementNS(SVG_NS, "path");
262
+ p.setAttribute("class", cls);
263
+ p.setAttribute("d", d);
264
+ return p;
265
+ }
211
266
  onEnter = (e) => {
212
267
  if (e.pointerType !== "mouse") return;
213
268
  this.hovering = true;
@@ -321,12 +376,110 @@ var ScrollFab = class {
321
376
  this.animateScroll(this.mode === "to-bottom" ? "bottom" : "top");
322
377
  }
323
378
  animateScroll(dest) {
379
+ ++this.actionSeq;
324
380
  this.cancelScroll();
325
381
  if (!this.animate || this.reducedMotion()) {
326
382
  this.setScrollPos(this.targetPos(dest));
327
383
  return;
328
384
  }
329
- this.attachInterrupt();
385
+ if (this.externalLenis) {
386
+ this.scrollWithLenis(this.externalLenis, dest, false);
387
+ return;
388
+ }
389
+ if (this.ownLenisConfig) {
390
+ const seq = ++this.actionSeq;
391
+ this.resolveOwnLenis().then((lenis) => {
392
+ if (this.destroyed || seq !== this.actionSeq) return;
393
+ if (lenis) this.scrollWithLenis(lenis, dest, true);
394
+ else this.animateRaf(dest);
395
+ });
396
+ return;
397
+ }
398
+ this.animateRaf(dest);
399
+ }
400
+ /** 首次点击时动态 import lenis 并自建实例;未安装(或加载失败)→ null 永久回退 rAF */
401
+ resolveOwnLenis() {
402
+ if (this.ownLenis) return Promise.resolve(this.ownLenis);
403
+ if (this.ownLenisPromise) return this.ownLenisPromise;
404
+ this.ownLenisPromise = import("lenis").then((mod) => {
405
+ const LenisCtor = mod.default ?? mod;
406
+ const cfg = { ...this.ownLenisConfig };
407
+ if (this.targetEl) cfg.wrapper = this.targetEl;
408
+ this.ownLenis = new LenisCtor(cfg);
409
+ this.onLenisReady?.(this.ownLenis);
410
+ return this.ownLenis;
411
+ }).catch(() => {
412
+ this.ownLenisConfig = null;
413
+ this.ownLenisPromise = null;
414
+ return null;
415
+ });
416
+ return this.ownLenisPromise;
417
+ }
418
+ /**
419
+ * 用 Lenis 执行程序滚动。
420
+ * 自建实例(own):组件自跑 raf loop(Lenis 需外部驱动 raf),保留 wheel/touch/keydown 打断与到底追击;
421
+ * 外部实例:宿主自带 raf loop 与手势处理,组件只发 scrollTo,不追击不打断。
422
+ */
423
+ scrollWithLenis(lenis, dest, own) {
424
+ const immediate = !this.animate || this.reducedMotion();
425
+ const target = this.targetPos(dest);
426
+ this.lenisDest = dest;
427
+ if (own) {
428
+ lenis.start?.();
429
+ lenis.scrollTo(target, {
430
+ immediate,
431
+ onComplete: () => this.endLenisScroll()
432
+ });
433
+ if (!immediate) {
434
+ this.lenisScrolling = true;
435
+ this.attachInterrupt();
436
+ this.startLenisLoop(lenis, dest);
437
+ } else this.endLenisScroll();
438
+ } else {
439
+ lenis.scrollTo(target, { immediate });
440
+ this.lenisDest = null;
441
+ }
442
+ }
443
+ /** 自建实例的 raf 驱动 + 懒加载到底追击(对齐内置 rAF 语义) */
444
+ startLenisLoop(lenis, dest) {
445
+ let lastEnd = this.targetPos(dest);
446
+ const loop = (time) => {
447
+ if (!this.lenisScrolling) return;
448
+ if (dest === "bottom") {
449
+ const live = this.targetPos("bottom");
450
+ if (Math.abs(live - lastEnd) > 2) {
451
+ lastEnd = live;
452
+ lenis.scrollTo(live, {
453
+ force: true,
454
+ onComplete: () => this.endLenisScroll()
455
+ });
456
+ }
457
+ }
458
+ lenis.raf?.(time);
459
+ this.lenisLoopRaf = requestAnimationFrame(loop);
460
+ };
461
+ this.lenisLoopRaf = requestAnimationFrame(loop);
462
+ }
463
+ stopLenisLoop() {
464
+ if (this.lenisLoopRaf != null) {
465
+ cancelAnimationFrame(this.lenisLoopRaf);
466
+ this.lenisLoopRaf = null;
467
+ }
468
+ }
469
+ /** 打断自建 Lenis 程序滚动:stop 冻结动画(下次发起前 start 复位) */
470
+ onLenisInterrupt = () => {
471
+ if (!this.lenisScrolling) return;
472
+ this.ownLenis?.stop?.();
473
+ this.endLenisScroll();
474
+ };
475
+ endLenisScroll() {
476
+ this.lenisScrolling = false;
477
+ this.lenisDest = null;
478
+ this.stopLenisLoop();
479
+ this.detachInterrupt();
480
+ }
481
+ animateRaf(dest) {
482
+ this.attachRafInterrupt();
330
483
  let init = false;
331
484
  let start = 0;
332
485
  let end = 0;
@@ -353,7 +506,7 @@ var ScrollFab = class {
353
506
  if (p < 1) this.scrollRaf = requestAnimationFrame(step);
354
507
  else {
355
508
  this.scrollRaf = null;
356
- this.detachInterrupt();
509
+ this.detachRafInterrupt();
357
510
  }
358
511
  };
359
512
  this.scrollRaf = requestAnimationFrame(step);
@@ -367,15 +520,33 @@ var ScrollFab = class {
367
520
  interruptSource() {
368
521
  return this.targetEl ?? window;
369
522
  }
523
+ /** 自建 Lenis 路线的打断监听 */
370
524
  attachInterrupt() {
371
525
  const src = this.interruptSource();
372
526
  for (const ev of [
373
527
  "wheel",
374
528
  "touchstart",
375
529
  "keydown"
376
- ]) src.addEventListener(ev, this.onInterrupt, { passive: true });
530
+ ]) src.addEventListener(ev, this.onLenisInterrupt, { passive: true });
377
531
  }
378
532
  detachInterrupt() {
533
+ const src = this.interruptSource();
534
+ for (const ev of [
535
+ "wheel",
536
+ "touchstart",
537
+ "keydown"
538
+ ]) src.removeEventListener(ev, this.onLenisInterrupt);
539
+ }
540
+ /** 内置 rAF 路线的打断监听 */
541
+ attachRafInterrupt() {
542
+ const src = this.interruptSource();
543
+ for (const ev of [
544
+ "wheel",
545
+ "touchstart",
546
+ "keydown"
547
+ ]) src.addEventListener(ev, this.onInterrupt, { passive: true });
548
+ }
549
+ detachRafInterrupt() {
379
550
  const src = this.interruptSource();
380
551
  for (const ev of [
381
552
  "wheel",
@@ -388,7 +559,16 @@ var ScrollFab = class {
388
559
  cancelAnimationFrame(this.scrollRaf);
389
560
  this.scrollRaf = null;
390
561
  }
391
- this.detachInterrupt();
562
+ if (this.lenisScrolling) this.endLenisScroll();
563
+ const src = this.interruptSource();
564
+ for (const ev of [
565
+ "wheel",
566
+ "touchstart",
567
+ "keydown"
568
+ ]) {
569
+ src.removeEventListener(ev, this.onInterrupt);
570
+ src.removeEventListener(ev, this.onLenisInterrupt);
571
+ }
392
572
  }
393
573
  maxScroll() {
394
574
  if (this.targetEl) return Math.max(0, this.targetEl.scrollHeight - this.targetEl.clientHeight);
@@ -405,9 +585,6 @@ var ScrollFab = class {
405
585
  if (this.targetEl) this.targetEl.scrollTop = v;
406
586
  else window.scrollTo(0, v);
407
587
  }
408
- scrollTo(v) {
409
- this.animateScroll(v === 0 ? "top" : "bottom");
410
- }
411
588
  onRecheck = () => {
412
589
  this.syncVisible();
413
590
  };