@qingwu-ui/scroll-fab 0.9.0-beta.1
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.cjs +420 -0
- package/dist/index.d.cts +111 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +111 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +421 -0
- package/dist/index.mjs.map +1 -0
- package/dist/style.css +127 -0
- package/package.json +70 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region ../../icon/icons.ts
|
|
3
|
+
const S = (d) => `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${d}</svg>`;
|
|
4
|
+
const S16 = (d) => `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${d}</svg>`;
|
|
5
|
+
const S18 = (d) => `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${d}</svg>`;
|
|
6
|
+
const S36 = (d) => `<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${d}</svg>`;
|
|
7
|
+
S("<rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\"/><path d=\"M8 2v4M16 2v4M3 10h18\"/>");
|
|
8
|
+
S("<rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\"/><line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\"/><line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\"/><line x1=\"8\" y1=\"14\" x2=\"8.01\" y2=\"14\"/>");
|
|
9
|
+
S("<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.35-4.35\"/>");
|
|
10
|
+
S("<path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"17 8 12 3 7 8\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"/>");
|
|
11
|
+
S("<path d=\"M12 16V4\"/><path d=\"m6 9 6-5 6 5\"/><path d=\"M4 20h16\"/>");
|
|
12
|
+
S("<path d=\"M3 7h18M3 12h18M3 17h18\"/>");
|
|
13
|
+
S("<path d=\"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9\"/><path d=\"M10.3 21a1.94 1.94 0 0 0 3.4 0\"/>");
|
|
14
|
+
S("<line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>");
|
|
15
|
+
S("<polyline points=\"20 6 9 17 4 12\"/>");
|
|
16
|
+
S("<line x1=\"12\" y1=\"5\" x2=\"12\" y2=\"19\"/><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/>");
|
|
17
|
+
S("<line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/>");
|
|
18
|
+
S("<path d=\"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z\"/>");
|
|
19
|
+
S("<path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"/><path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"/>");
|
|
20
|
+
S("<path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/>");
|
|
21
|
+
S("<path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"/>");
|
|
22
|
+
S("<circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"8\" x2=\"12.01\" y2=\"8\"/>");
|
|
23
|
+
S("<path d=\"M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>");
|
|
24
|
+
S("<polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/><line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\"/><line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\"/>");
|
|
25
|
+
S("<path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"/><polyline points=\"7 10 12 15 17 10\"/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"/>");
|
|
26
|
+
S("<rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"/><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"/>");
|
|
27
|
+
S("<path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>");
|
|
28
|
+
S("<polygon points=\"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2\"/>");
|
|
29
|
+
S("<path d=\"M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z\"/>");
|
|
30
|
+
S("<path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/><polyline points=\"15 3 21 3 21 9\"/><line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"/>");
|
|
31
|
+
S("<path d=\"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4\"/><path d=\"M9 18c-4.51 2-5-2-7-2\"/>");
|
|
32
|
+
S16("<path d=\"m15 18-6-6 6-6\"/>");
|
|
33
|
+
S16("<path d=\"m9 18 6-6-6-6\"/>");
|
|
34
|
+
S("<polyline points=\"18 15 12 9 6 15\"/>");
|
|
35
|
+
const ICON_CHEVRON_DOWN = S("<polyline points=\"6 9 12 15 18 9\"/>");
|
|
36
|
+
S("<polyline points=\"16 18 22 12 16 6\"/><polyline points=\"8 6 2 12 8 18\"/>");
|
|
37
|
+
S("<polyline points=\"15 3 21 3 21 9\"/><polyline points=\"9 21 3 21 3 15\"/><line x1=\"21\" y1=\"3\" x2=\"14\" y2=\"10\"/><line x1=\"3\" y1=\"21\" x2=\"10\" y2=\"14\"/>");
|
|
38
|
+
S("<polyline points=\"4 8 4 3 9 3\"/><polyline points=\"20 16 20 21 15 21\"/><line x1=\"4\" y1=\"3\" x2=\"11\" y2=\"10\"/><line x1=\"20\" y1=\"21\" x2=\"13\" y2=\"14\"/>");
|
|
39
|
+
S("<circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 6v6l4 2\"/>");
|
|
40
|
+
S("<polyline points=\"4 7 4 4 20 4 20 7\"/><line x1=\"9\" y1=\"20\" x2=\"15\" y2=\"20\"/><line x1=\"12\" y1=\"4\" x2=\"12\" y2=\"20\"/>");
|
|
41
|
+
S("<path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"/>");
|
|
42
|
+
S("<path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\"/><path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/><line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\"/><line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\"/>");
|
|
43
|
+
S("<path d=\"M9 18V5l12-2v13\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><circle cx=\"18\" cy=\"16\" r=\"3\"/>");
|
|
44
|
+
S("<polygon points=\"23 7 16 12 23 17 23 7\"/><rect x=\"1\" y=\"5\" width=\"15\" height=\"14\" rx=\"2\" ry=\"2\"/>");
|
|
45
|
+
S("<rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"/><polyline points=\"21 15 16 10 5 21\"/>");
|
|
46
|
+
S("<circle cx=\"12\" cy=\"12\" r=\"5\"/><line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\"/><line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\"/><line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\"/><line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\"/><line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\"/><line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\"/><line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\"/><line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\"/>");
|
|
47
|
+
S("<path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/>");
|
|
48
|
+
S("<rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M9 3v18\"/><path d=\"m14 9-3 3 3 3\"/>");
|
|
49
|
+
S("<line x1=\"8\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"8\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"8\" y1=\"18\" x2=\"21\" y2=\"18\"/><line x1=\"3\" y1=\"6\" x2=\"3.01\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"3.01\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"3.01\" y2=\"18\"/>");
|
|
50
|
+
S("<path d=\"M12 2l2.4 7.2h7.6l-6 4.8 2.4 7.2-6.4-4.8-6.4 4.8 2.4-7.2-6-4.8h7.6z\"/>");
|
|
51
|
+
S("<polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/>");
|
|
52
|
+
S("<polyline points=\"23 4 23 10 17 10\"/><polyline points=\"1 20 1 14 7 14\"/><path d=\"M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15\"/>");
|
|
53
|
+
S("<polyline points=\"1 4 1 10 7 10\"/><path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\"/>");
|
|
54
|
+
S("<circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"/>");
|
|
55
|
+
S("<path d=\"M3 3v5h5\"/><path d=\"M3.05 13A9 9 0 1 0 6.11 6.11L3 8\"/><path d=\"M12 7v5l4 2\"/>");
|
|
56
|
+
S("<line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"/><polygon points=\"22 2 15 22 11 13 2 9 22 2\"/>");
|
|
57
|
+
S("<path d=\"M5 8l6 6\"/><path d=\"M4 14l6-6 2-3\"/><path d=\"M2 5h12\"/><path d=\"M7 2h1\"/><path d=\"M22 22l-5-10-5 10\"/><path d=\"M14 18h6\"/>");
|
|
58
|
+
S("<path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"/><polyline points=\"14 2 14 8 20 8\"/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"/><polyline points=\"10 9 9 9 8 9\"/>");
|
|
59
|
+
S("<path d=\"M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z\"/>");
|
|
60
|
+
S("<circle cx=\"12\" cy=\"12\" r=\"1\"/><path d=\"M20 12a8 8 0 1 1-8-8\"/><path d=\"m16 2 4 4-4 4\"/>");
|
|
61
|
+
S("<path d=\"M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z\"/><line x1=\"7\" y1=\"7\" x2=\"7.01\" y2=\"7\"/>");
|
|
62
|
+
S("<circle cx=\"9\" cy=\"12\" r=\"1\"/><circle cx=\"15\" cy=\"12\" r=\"1\"/><circle cx=\"9\" cy=\"6\" r=\"1\"/><circle cx=\"15\" cy=\"6\" r=\"1\"/><circle cx=\"9\" cy=\"18\" r=\"1\"/><circle cx=\"15\" cy=\"18\" r=\"1\"/>");
|
|
63
|
+
S("<rect x=\"2\" y=\"2\" width=\"8\" height=\"8\" rx=\"1\"/><rect x=\"14\" y=\"2\" width=\"8\" height=\"8\" rx=\"1\"/><rect x=\"2\" y=\"14\" width=\"8\" height=\"8\" rx=\"1\"/><rect x=\"14\" y=\"14\" width=\"8\" height=\"8\" rx=\"1\"/>");
|
|
64
|
+
S("<circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"2\" y1=\"12\" x2=\"22\" y2=\"12\"/><path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\"/>");
|
|
65
|
+
S("<path d=\"M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48\"/>");
|
|
66
|
+
S36("<path d=\"M12 16V4\"/><path d=\"m6 9 6-5 6 5\"/><path d=\"M4 20h16\"/>");
|
|
67
|
+
const S18c = (d) => `<svg class="qs-ico" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${d}</svg>`;
|
|
68
|
+
const S20c = (d) => `<svg class="qw-cal-svg" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${d}</svg>`;
|
|
69
|
+
S18c("<circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m20 20-3.2-3.2\"/>");
|
|
70
|
+
S18("<path d=\"M4 7h16M4 12h16M4 17h16\"/>");
|
|
71
|
+
S20c("<rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\"/><path d=\"M8 2v4M16 2v4M3 10h18\"/>");
|
|
72
|
+
S16("<path d=\"m15 18-6-6 6-6\"/>");
|
|
73
|
+
S16("<path d=\"m9 18 6-6-6-6\"/>");
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/scroll-fab.ts
|
|
76
|
+
/** 青梧UI ScrollFab 悬浮滚动栏:默认“滚动到底部”,桌面悬停/触屏按住绕满描边一圈翻转为“返回顶部”,点击(轻点)直接执行当前模式动作 */
|
|
77
|
+
const RADIUS = 21;
|
|
78
|
+
const DEFAULT_LABEL_BOTTOM = "滚动到底部(悬停或按住绕圈可切换为返回顶部)";
|
|
79
|
+
const DEFAULT_LABEL_TOP = "返回顶部(悬停或按住绕圈可切换为滚动到底部)";
|
|
80
|
+
function easeInOutCubic(p) {
|
|
81
|
+
return p < .5 ? 4 * p * p * p : 1 - (-2 * p + 2) ** 3 / 2;
|
|
82
|
+
}
|
|
83
|
+
var ScrollFab = class {
|
|
84
|
+
el;
|
|
85
|
+
ring;
|
|
86
|
+
track;
|
|
87
|
+
icon;
|
|
88
|
+
len;
|
|
89
|
+
targetEl;
|
|
90
|
+
ringMs;
|
|
91
|
+
decayMs;
|
|
92
|
+
cancelPx;
|
|
93
|
+
animate;
|
|
94
|
+
labelBottom;
|
|
95
|
+
labelTop;
|
|
96
|
+
onModeChange;
|
|
97
|
+
mode = "to-bottom";
|
|
98
|
+
progress = 0;
|
|
99
|
+
hovering = false;
|
|
100
|
+
pressing = false;
|
|
101
|
+
flipped = false;
|
|
102
|
+
pointerId = null;
|
|
103
|
+
startX = 0;
|
|
104
|
+
startY = 0;
|
|
105
|
+
ringRaf = null;
|
|
106
|
+
lastTs = null;
|
|
107
|
+
scrollRaf = null;
|
|
108
|
+
flashTimer = null;
|
|
109
|
+
tapResetTimer = null;
|
|
110
|
+
touchTap = false;
|
|
111
|
+
ro = null;
|
|
112
|
+
constructor(options = {}) {
|
|
113
|
+
this.targetEl = options.target ?? null;
|
|
114
|
+
this.ringMs = Math.max(100, Math.min(5e3, options.ringDuration ?? 800));
|
|
115
|
+
this.decayMs = Math.max(100, options.decayDuration ?? 300);
|
|
116
|
+
this.cancelPx = Math.max(4, options.cancelThreshold ?? 10);
|
|
117
|
+
this.animate = options.animate ?? true;
|
|
118
|
+
this.labelBottom = options.ariaLabelToBottom ?? DEFAULT_LABEL_BOTTOM;
|
|
119
|
+
this.labelTop = options.ariaLabelToTop ?? DEFAULT_LABEL_TOP;
|
|
120
|
+
this.onModeChange = options.onModeChange;
|
|
121
|
+
this.el = document.createElement("button");
|
|
122
|
+
this.el.type = "button";
|
|
123
|
+
this.el.className = `qsf-root${options.className ? ` ${options.className}` : ""}`;
|
|
124
|
+
this.el.dataset.mode = this.mode;
|
|
125
|
+
this.el.setAttribute("aria-label", this.labelBottom);
|
|
126
|
+
this.el.style.setProperty("--qsf-size", `${Math.max(44, options.size ?? 48)}px`);
|
|
127
|
+
if (options.zIndex != null) this.el.style.setProperty("--qsf-z", String(options.zIndex));
|
|
128
|
+
if (options.position?.right != null) this.el.style.setProperty("--qsf-right", `${options.position.right}px`);
|
|
129
|
+
if (options.position?.bottom != null) this.el.style.setProperty("--qsf-bottom", `${options.position.bottom}px`);
|
|
130
|
+
if (options.position?.left != null) this.el.style.setProperty("--qsf-left", `${options.position.left}px`);
|
|
131
|
+
if (options.position?.top != null) this.el.style.setProperty("--qsf-top", `${options.position.top}px`);
|
|
132
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
133
|
+
svg.setAttribute("class", "qsf-svg");
|
|
134
|
+
svg.setAttribute("viewBox", "0 0 48 48");
|
|
135
|
+
svg.setAttribute("aria-hidden", "true");
|
|
136
|
+
this.track = this.makeCircle("qsf-track");
|
|
137
|
+
this.ring = this.makeCircle("qsf-bar");
|
|
138
|
+
svg.append(this.track, this.ring);
|
|
139
|
+
let len = 2 * Math.PI * RADIUS;
|
|
140
|
+
try {
|
|
141
|
+
const measured = this.ring.getTotalLength();
|
|
142
|
+
if (Number.isFinite(measured)) len = measured;
|
|
143
|
+
} catch {}
|
|
144
|
+
this.len = len;
|
|
145
|
+
this.ring.style.strokeDasharray = String(this.len);
|
|
146
|
+
this.renderRing();
|
|
147
|
+
this.icon = document.createElement("span");
|
|
148
|
+
this.icon.className = "qsf-icon";
|
|
149
|
+
this.icon.setAttribute("aria-hidden", "true");
|
|
150
|
+
if (options.content != null) if (typeof options.content === "string") this.icon.innerHTML = options.content;
|
|
151
|
+
else this.icon.append(options.content);
|
|
152
|
+
else this.icon.innerHTML = ICON_CHEVRON_DOWN;
|
|
153
|
+
this.el.append(svg, this.icon);
|
|
154
|
+
document.body.append(this.el);
|
|
155
|
+
this.el.addEventListener("pointerenter", this.onEnter);
|
|
156
|
+
this.el.addEventListener("pointerleave", this.onLeave);
|
|
157
|
+
this.el.addEventListener("pointerdown", this.onDown);
|
|
158
|
+
this.el.addEventListener("pointermove", this.onMove);
|
|
159
|
+
this.el.addEventListener("pointerup", this.onUp);
|
|
160
|
+
this.el.addEventListener("pointercancel", this.onCancel);
|
|
161
|
+
this.el.addEventListener("keydown", this.onKey);
|
|
162
|
+
this.el.addEventListener("click", this.onClick);
|
|
163
|
+
window.addEventListener("resize", this.onRecheck);
|
|
164
|
+
(this.targetEl ?? window).addEventListener("scroll", this.onRecheck, { passive: true });
|
|
165
|
+
if (typeof ResizeObserver === "function") {
|
|
166
|
+
this.ro = new ResizeObserver(this.onRecheck);
|
|
167
|
+
this.ro.observe(this.targetEl ?? document.documentElement);
|
|
168
|
+
}
|
|
169
|
+
this.syncVisible();
|
|
170
|
+
}
|
|
171
|
+
/** 当前模式 */
|
|
172
|
+
get currentMode() {
|
|
173
|
+
return this.mode;
|
|
174
|
+
}
|
|
175
|
+
/** 内容懒加载等外部高度变化后可显式触发重估(滚动/resize 时也会自动重估) */
|
|
176
|
+
refresh() {
|
|
177
|
+
this.onRecheck();
|
|
178
|
+
}
|
|
179
|
+
scrollToBottom() {
|
|
180
|
+
this.animateScroll("bottom");
|
|
181
|
+
}
|
|
182
|
+
scrollToTop() {
|
|
183
|
+
this.scrollTo(0);
|
|
184
|
+
}
|
|
185
|
+
destroy() {
|
|
186
|
+
this.el.removeEventListener("pointerenter", this.onEnter);
|
|
187
|
+
this.el.removeEventListener("pointerleave", this.onLeave);
|
|
188
|
+
this.el.removeEventListener("pointerdown", this.onDown);
|
|
189
|
+
this.el.removeEventListener("pointermove", this.onMove);
|
|
190
|
+
this.el.removeEventListener("pointerup", this.onUp);
|
|
191
|
+
this.el.removeEventListener("pointercancel", this.onCancel);
|
|
192
|
+
this.el.removeEventListener("keydown", this.onKey);
|
|
193
|
+
this.el.removeEventListener("click", this.onClick);
|
|
194
|
+
window.removeEventListener("resize", this.onRecheck);
|
|
195
|
+
(this.targetEl ?? window).removeEventListener("scroll", this.onRecheck);
|
|
196
|
+
this.ro?.disconnect();
|
|
197
|
+
this.cancelScroll();
|
|
198
|
+
if (this.ringRaf != null) cancelAnimationFrame(this.ringRaf);
|
|
199
|
+
this.ringRaf = null;
|
|
200
|
+
if (this.flashTimer != null) clearTimeout(this.flashTimer);
|
|
201
|
+
if (this.tapResetTimer != null) clearTimeout(this.tapResetTimer);
|
|
202
|
+
this.el.remove();
|
|
203
|
+
}
|
|
204
|
+
makeCircle(cls) {
|
|
205
|
+
const c = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
206
|
+
c.setAttribute("class", cls);
|
|
207
|
+
c.setAttribute("cx", "24");
|
|
208
|
+
c.setAttribute("cy", "24");
|
|
209
|
+
c.setAttribute("r", String(RADIUS));
|
|
210
|
+
return c;
|
|
211
|
+
}
|
|
212
|
+
onEnter = (e) => {
|
|
213
|
+
if (e.pointerType !== "mouse") return;
|
|
214
|
+
this.hovering = true;
|
|
215
|
+
this.kickRing();
|
|
216
|
+
};
|
|
217
|
+
onLeave = (e) => {
|
|
218
|
+
if (e.pointerType !== "mouse") return;
|
|
219
|
+
this.hovering = false;
|
|
220
|
+
this.flipped = false;
|
|
221
|
+
};
|
|
222
|
+
onDown = (e) => {
|
|
223
|
+
if (e.button !== 0 || this.pointerId !== null) return;
|
|
224
|
+
if (e.pointerType === "mouse") return;
|
|
225
|
+
e.preventDefault();
|
|
226
|
+
this.touchTap = true;
|
|
227
|
+
if (this.tapResetTimer != null) clearTimeout(this.tapResetTimer);
|
|
228
|
+
this.pointerId = e.pointerId;
|
|
229
|
+
this.startX = e.clientX;
|
|
230
|
+
this.startY = e.clientY;
|
|
231
|
+
this.pressing = true;
|
|
232
|
+
this.flipped = false;
|
|
233
|
+
this.kickRing();
|
|
234
|
+
};
|
|
235
|
+
onMove = (e) => {
|
|
236
|
+
if (e.pointerId !== this.pointerId || !this.pressing) return;
|
|
237
|
+
if (Math.hypot(e.clientX - this.startX, e.clientY - this.startY) > this.cancelPx) {
|
|
238
|
+
this.pressing = false;
|
|
239
|
+
this.pointerId = null;
|
|
240
|
+
this.flipped = false;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
onUp = (e) => {
|
|
244
|
+
if (e.pointerId !== this.pointerId) return;
|
|
245
|
+
this.pointerId = null;
|
|
246
|
+
this.pressing = false;
|
|
247
|
+
const consumed = this.flipped;
|
|
248
|
+
this.flipped = false;
|
|
249
|
+
this.tapResetTimer = setTimeout(() => {
|
|
250
|
+
this.touchTap = false;
|
|
251
|
+
this.tapResetTimer = null;
|
|
252
|
+
}, 400);
|
|
253
|
+
if (consumed) return;
|
|
254
|
+
this.doAction();
|
|
255
|
+
};
|
|
256
|
+
onCancel = (e) => {
|
|
257
|
+
if (e.pointerId !== this.pointerId) return;
|
|
258
|
+
this.pointerId = null;
|
|
259
|
+
this.pressing = false;
|
|
260
|
+
this.flipped = false;
|
|
261
|
+
};
|
|
262
|
+
onClick = (e) => {
|
|
263
|
+
if (this.touchTap) {
|
|
264
|
+
e.preventDefault();
|
|
265
|
+
e.stopPropagation();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
this.doAction();
|
|
269
|
+
};
|
|
270
|
+
onKey = (e) => {
|
|
271
|
+
if (e.repeat || e.key !== "Enter" && e.key !== " ") return;
|
|
272
|
+
e.preventDefault();
|
|
273
|
+
this.setMode(this.mode === "to-bottom" ? "to-top" : "to-bottom");
|
|
274
|
+
};
|
|
275
|
+
holding() {
|
|
276
|
+
return this.hovering || this.pressing;
|
|
277
|
+
}
|
|
278
|
+
kickRing() {
|
|
279
|
+
if (this.ringRaf != null) return;
|
|
280
|
+
this.lastTs = null;
|
|
281
|
+
this.ringRaf = requestAnimationFrame(this.stepRing);
|
|
282
|
+
}
|
|
283
|
+
stepRing = (ts) => {
|
|
284
|
+
const dt = this.lastTs == null ? 0 : ts - this.lastTs;
|
|
285
|
+
this.lastTs = ts;
|
|
286
|
+
if (this.holding()) {
|
|
287
|
+
if (!this.flipped) {
|
|
288
|
+
this.progress = Math.min(1, this.progress + dt / this.ringMs);
|
|
289
|
+
if (this.progress >= 1) this.completeRing();
|
|
290
|
+
}
|
|
291
|
+
} else this.progress = Math.max(0, this.progress - dt / this.decayMs);
|
|
292
|
+
this.renderRing();
|
|
293
|
+
if (this.holding() || this.progress > 0) this.ringRaf = requestAnimationFrame(this.stepRing);
|
|
294
|
+
else this.ringRaf = null;
|
|
295
|
+
};
|
|
296
|
+
/** 绕满一圈 = 纯模式翻转(不附带任何滚动) */
|
|
297
|
+
completeRing() {
|
|
298
|
+
this.flipped = true;
|
|
299
|
+
const next = this.mode === "to-bottom" ? "to-top" : "to-bottom";
|
|
300
|
+
this.setMode(next);
|
|
301
|
+
this.el.classList.add("is-complete");
|
|
302
|
+
if (this.flashTimer != null) clearTimeout(this.flashTimer);
|
|
303
|
+
this.flashTimer = setTimeout(() => {
|
|
304
|
+
this.el.classList.remove("is-complete");
|
|
305
|
+
this.flashTimer = null;
|
|
306
|
+
}, 350);
|
|
307
|
+
}
|
|
308
|
+
renderRing() {
|
|
309
|
+
const vis = this.progress > 0;
|
|
310
|
+
this.track.toggleAttribute("hidden", !vis);
|
|
311
|
+
this.ring.toggleAttribute("hidden", !vis);
|
|
312
|
+
this.ring.style.strokeDashoffset = String(this.len * (1 - this.progress));
|
|
313
|
+
}
|
|
314
|
+
setMode(mode) {
|
|
315
|
+
if (mode === this.mode) return;
|
|
316
|
+
this.mode = mode;
|
|
317
|
+
this.el.dataset.mode = mode;
|
|
318
|
+
this.el.setAttribute("aria-label", mode === "to-bottom" ? this.labelBottom : this.labelTop);
|
|
319
|
+
this.onModeChange?.(mode);
|
|
320
|
+
}
|
|
321
|
+
doAction() {
|
|
322
|
+
this.animateScroll(this.mode === "to-bottom" ? "bottom" : "top");
|
|
323
|
+
}
|
|
324
|
+
animateScroll(dest) {
|
|
325
|
+
this.cancelScroll();
|
|
326
|
+
if (!this.animate || this.reducedMotion()) {
|
|
327
|
+
this.setScrollPos(this.targetPos(dest));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
this.attachInterrupt();
|
|
331
|
+
let init = false;
|
|
332
|
+
let start = 0;
|
|
333
|
+
let end = 0;
|
|
334
|
+
let t0 = 0;
|
|
335
|
+
let dur = 0;
|
|
336
|
+
const step = (ts) => {
|
|
337
|
+
if (!init) {
|
|
338
|
+
init = true;
|
|
339
|
+
start = this.scrollPos();
|
|
340
|
+
end = this.targetPos(dest);
|
|
341
|
+
t0 = ts;
|
|
342
|
+
dur = Math.min(1200, Math.max(400, Math.abs(end - start) / 3));
|
|
343
|
+
} else if (dest === "bottom") {
|
|
344
|
+
const live = this.targetPos(dest);
|
|
345
|
+
if (Math.abs(live - end) > 2) {
|
|
346
|
+
end = live;
|
|
347
|
+
start = this.scrollPos();
|
|
348
|
+
t0 = ts;
|
|
349
|
+
dur = Math.min(1200, Math.max(400, Math.abs(end - start) / 3));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const p = Math.min(1, (ts - t0) / dur);
|
|
353
|
+
this.setScrollPos(start + (end - start) * easeInOutCubic(p));
|
|
354
|
+
if (p < 1) this.scrollRaf = requestAnimationFrame(step);
|
|
355
|
+
else {
|
|
356
|
+
this.scrollRaf = null;
|
|
357
|
+
this.detachInterrupt();
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
this.scrollRaf = requestAnimationFrame(step);
|
|
361
|
+
}
|
|
362
|
+
reducedMotion() {
|
|
363
|
+
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
364
|
+
}
|
|
365
|
+
onInterrupt = () => {
|
|
366
|
+
this.cancelScroll();
|
|
367
|
+
};
|
|
368
|
+
interruptSource() {
|
|
369
|
+
return this.targetEl ?? window;
|
|
370
|
+
}
|
|
371
|
+
attachInterrupt() {
|
|
372
|
+
const src = this.interruptSource();
|
|
373
|
+
for (const ev of [
|
|
374
|
+
"wheel",
|
|
375
|
+
"touchstart",
|
|
376
|
+
"keydown"
|
|
377
|
+
]) src.addEventListener(ev, this.onInterrupt, { passive: true });
|
|
378
|
+
}
|
|
379
|
+
detachInterrupt() {
|
|
380
|
+
const src = this.interruptSource();
|
|
381
|
+
for (const ev of [
|
|
382
|
+
"wheel",
|
|
383
|
+
"touchstart",
|
|
384
|
+
"keydown"
|
|
385
|
+
]) src.removeEventListener(ev, this.onInterrupt);
|
|
386
|
+
}
|
|
387
|
+
cancelScroll() {
|
|
388
|
+
if (this.scrollRaf != null) {
|
|
389
|
+
cancelAnimationFrame(this.scrollRaf);
|
|
390
|
+
this.scrollRaf = null;
|
|
391
|
+
}
|
|
392
|
+
this.detachInterrupt();
|
|
393
|
+
}
|
|
394
|
+
maxScroll() {
|
|
395
|
+
if (this.targetEl) return Math.max(0, this.targetEl.scrollHeight - this.targetEl.clientHeight);
|
|
396
|
+
return Math.max(0, document.documentElement.scrollHeight - window.innerHeight);
|
|
397
|
+
}
|
|
398
|
+
targetPos(dest) {
|
|
399
|
+
return dest === "bottom" ? this.maxScroll() : 0;
|
|
400
|
+
}
|
|
401
|
+
scrollPos() {
|
|
402
|
+
if (this.targetEl) return this.targetEl.scrollTop;
|
|
403
|
+
return window.scrollY || document.documentElement.scrollTop || 0;
|
|
404
|
+
}
|
|
405
|
+
setScrollPos(v) {
|
|
406
|
+
if (this.targetEl) this.targetEl.scrollTop = v;
|
|
407
|
+
else window.scrollTo(0, v);
|
|
408
|
+
}
|
|
409
|
+
scrollTo(v) {
|
|
410
|
+
this.animateScroll(v === 0 ? "top" : "bottom");
|
|
411
|
+
}
|
|
412
|
+
onRecheck = () => {
|
|
413
|
+
this.syncVisible();
|
|
414
|
+
};
|
|
415
|
+
syncVisible() {
|
|
416
|
+
this.el.hidden = this.maxScroll() <= 1;
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
//#endregion
|
|
420
|
+
exports.ScrollFab = ScrollFab;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type ScrollFabMode = "to-bottom" | "to-top";
|
|
3
|
+
/** 悬浮位置(px);bottom 缺省时自动叠加 safe-area,显式传入则按原值生效 */
|
|
4
|
+
interface ScrollFabPosition {
|
|
5
|
+
top?: number;
|
|
6
|
+
right?: number;
|
|
7
|
+
bottom?: number;
|
|
8
|
+
left?: number;
|
|
9
|
+
}
|
|
10
|
+
interface ScrollFabOptions {
|
|
11
|
+
/** 滚动容器,null(默认)为 window */
|
|
12
|
+
target?: HTMLElement | null;
|
|
13
|
+
/** 本体直径 px,默认 48,内部钳制最小 44(触控命中区) */
|
|
14
|
+
size?: number;
|
|
15
|
+
/** 绕满一圈的时长 ms(桌面悬停推进 / 触屏按住推进),默认 800 */
|
|
16
|
+
ringDuration?: number;
|
|
17
|
+
/** 离开/松手后描边衰减倒转的时长 ms,默认 300 */
|
|
18
|
+
decayDuration?: number;
|
|
19
|
+
/** 触屏按住期间指针位移超过该值(px)视为放弃描边、放行页面手势,默认 10 */
|
|
20
|
+
cancelThreshold?: number;
|
|
21
|
+
/** 中心内容自定义:HTML 字符串或节点,缺省为方向箭头 */
|
|
22
|
+
content?: string | HTMLElement;
|
|
23
|
+
/** 悬浮位置,默认右下 right 24 / bottom 24(含 safe-area) */
|
|
24
|
+
position?: ScrollFabPosition;
|
|
25
|
+
/** z-index,默认 9999 */
|
|
26
|
+
zIndex?: number;
|
|
27
|
+
/** 附加类名 */
|
|
28
|
+
className?: string;
|
|
29
|
+
/** 动画开关,关闭或 prefers-reduced-motion 时程序滚动瞬时到位,默认 true */
|
|
30
|
+
animate?: boolean;
|
|
31
|
+
/** 模式①(滚动到底部)无障碍标签 */
|
|
32
|
+
ariaLabelToBottom?: string;
|
|
33
|
+
/** 模式②(返回顶部)无障碍标签 */
|
|
34
|
+
ariaLabelToTop?: string;
|
|
35
|
+
/** 模式翻转回调 */
|
|
36
|
+
onModeChange?: (mode: ScrollFabMode) => void;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/scroll-fab.d.ts
|
|
40
|
+
declare class ScrollFab {
|
|
41
|
+
readonly el: HTMLButtonElement;
|
|
42
|
+
private readonly ring;
|
|
43
|
+
private readonly track;
|
|
44
|
+
private readonly icon;
|
|
45
|
+
private readonly len;
|
|
46
|
+
private readonly targetEl;
|
|
47
|
+
private readonly ringMs;
|
|
48
|
+
private readonly decayMs;
|
|
49
|
+
private readonly cancelPx;
|
|
50
|
+
private readonly animate;
|
|
51
|
+
private readonly labelBottom;
|
|
52
|
+
private readonly labelTop;
|
|
53
|
+
private readonly onModeChange?;
|
|
54
|
+
private mode;
|
|
55
|
+
private progress;
|
|
56
|
+
private hovering;
|
|
57
|
+
private pressing;
|
|
58
|
+
private flipped;
|
|
59
|
+
private pointerId;
|
|
60
|
+
private startX;
|
|
61
|
+
private startY;
|
|
62
|
+
private ringRaf;
|
|
63
|
+
private lastTs;
|
|
64
|
+
private scrollRaf;
|
|
65
|
+
private flashTimer;
|
|
66
|
+
private tapResetTimer;
|
|
67
|
+
private touchTap;
|
|
68
|
+
private ro;
|
|
69
|
+
constructor(options?: ScrollFabOptions);
|
|
70
|
+
/** 当前模式 */
|
|
71
|
+
get currentMode(): ScrollFabMode;
|
|
72
|
+
/** 内容懒加载等外部高度变化后可显式触发重估(滚动/resize 时也会自动重估) */
|
|
73
|
+
refresh(): void;
|
|
74
|
+
scrollToBottom(): void;
|
|
75
|
+
scrollToTop(): void;
|
|
76
|
+
destroy(): void;
|
|
77
|
+
private makeCircle;
|
|
78
|
+
private onEnter;
|
|
79
|
+
private onLeave;
|
|
80
|
+
private onDown;
|
|
81
|
+
private onMove;
|
|
82
|
+
private onUp;
|
|
83
|
+
private onCancel;
|
|
84
|
+
private onClick;
|
|
85
|
+
private onKey;
|
|
86
|
+
private holding;
|
|
87
|
+
private kickRing;
|
|
88
|
+
private stepRing;
|
|
89
|
+
/** 绕满一圈 = 纯模式翻转(不附带任何滚动) */
|
|
90
|
+
private completeRing;
|
|
91
|
+
private renderRing;
|
|
92
|
+
private setMode;
|
|
93
|
+
private doAction;
|
|
94
|
+
private animateScroll;
|
|
95
|
+
private reducedMotion;
|
|
96
|
+
private onInterrupt;
|
|
97
|
+
private interruptSource;
|
|
98
|
+
private attachInterrupt;
|
|
99
|
+
private detachInterrupt;
|
|
100
|
+
private cancelScroll;
|
|
101
|
+
private maxScroll;
|
|
102
|
+
private targetPos;
|
|
103
|
+
private scrollPos;
|
|
104
|
+
private setScrollPos;
|
|
105
|
+
private scrollTo;
|
|
106
|
+
private onRecheck;
|
|
107
|
+
private syncVisible;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
export { ScrollFab, type ScrollFabMode, type ScrollFabOptions, type ScrollFabPosition };
|
|
111
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","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"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type ScrollFabMode = "to-bottom" | "to-top";
|
|
3
|
+
/** 悬浮位置(px);bottom 缺省时自动叠加 safe-area,显式传入则按原值生效 */
|
|
4
|
+
interface ScrollFabPosition {
|
|
5
|
+
top?: number;
|
|
6
|
+
right?: number;
|
|
7
|
+
bottom?: number;
|
|
8
|
+
left?: number;
|
|
9
|
+
}
|
|
10
|
+
interface ScrollFabOptions {
|
|
11
|
+
/** 滚动容器,null(默认)为 window */
|
|
12
|
+
target?: HTMLElement | null;
|
|
13
|
+
/** 本体直径 px,默认 48,内部钳制最小 44(触控命中区) */
|
|
14
|
+
size?: number;
|
|
15
|
+
/** 绕满一圈的时长 ms(桌面悬停推进 / 触屏按住推进),默认 800 */
|
|
16
|
+
ringDuration?: number;
|
|
17
|
+
/** 离开/松手后描边衰减倒转的时长 ms,默认 300 */
|
|
18
|
+
decayDuration?: number;
|
|
19
|
+
/** 触屏按住期间指针位移超过该值(px)视为放弃描边、放行页面手势,默认 10 */
|
|
20
|
+
cancelThreshold?: number;
|
|
21
|
+
/** 中心内容自定义:HTML 字符串或节点,缺省为方向箭头 */
|
|
22
|
+
content?: string | HTMLElement;
|
|
23
|
+
/** 悬浮位置,默认右下 right 24 / bottom 24(含 safe-area) */
|
|
24
|
+
position?: ScrollFabPosition;
|
|
25
|
+
/** z-index,默认 9999 */
|
|
26
|
+
zIndex?: number;
|
|
27
|
+
/** 附加类名 */
|
|
28
|
+
className?: string;
|
|
29
|
+
/** 动画开关,关闭或 prefers-reduced-motion 时程序滚动瞬时到位,默认 true */
|
|
30
|
+
animate?: boolean;
|
|
31
|
+
/** 模式①(滚动到底部)无障碍标签 */
|
|
32
|
+
ariaLabelToBottom?: string;
|
|
33
|
+
/** 模式②(返回顶部)无障碍标签 */
|
|
34
|
+
ariaLabelToTop?: string;
|
|
35
|
+
/** 模式翻转回调 */
|
|
36
|
+
onModeChange?: (mode: ScrollFabMode) => void;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/scroll-fab.d.ts
|
|
40
|
+
declare class ScrollFab {
|
|
41
|
+
readonly el: HTMLButtonElement;
|
|
42
|
+
private readonly ring;
|
|
43
|
+
private readonly track;
|
|
44
|
+
private readonly icon;
|
|
45
|
+
private readonly len;
|
|
46
|
+
private readonly targetEl;
|
|
47
|
+
private readonly ringMs;
|
|
48
|
+
private readonly decayMs;
|
|
49
|
+
private readonly cancelPx;
|
|
50
|
+
private readonly animate;
|
|
51
|
+
private readonly labelBottom;
|
|
52
|
+
private readonly labelTop;
|
|
53
|
+
private readonly onModeChange?;
|
|
54
|
+
private mode;
|
|
55
|
+
private progress;
|
|
56
|
+
private hovering;
|
|
57
|
+
private pressing;
|
|
58
|
+
private flipped;
|
|
59
|
+
private pointerId;
|
|
60
|
+
private startX;
|
|
61
|
+
private startY;
|
|
62
|
+
private ringRaf;
|
|
63
|
+
private lastTs;
|
|
64
|
+
private scrollRaf;
|
|
65
|
+
private flashTimer;
|
|
66
|
+
private tapResetTimer;
|
|
67
|
+
private touchTap;
|
|
68
|
+
private ro;
|
|
69
|
+
constructor(options?: ScrollFabOptions);
|
|
70
|
+
/** 当前模式 */
|
|
71
|
+
get currentMode(): ScrollFabMode;
|
|
72
|
+
/** 内容懒加载等外部高度变化后可显式触发重估(滚动/resize 时也会自动重估) */
|
|
73
|
+
refresh(): void;
|
|
74
|
+
scrollToBottom(): void;
|
|
75
|
+
scrollToTop(): void;
|
|
76
|
+
destroy(): void;
|
|
77
|
+
private makeCircle;
|
|
78
|
+
private onEnter;
|
|
79
|
+
private onLeave;
|
|
80
|
+
private onDown;
|
|
81
|
+
private onMove;
|
|
82
|
+
private onUp;
|
|
83
|
+
private onCancel;
|
|
84
|
+
private onClick;
|
|
85
|
+
private onKey;
|
|
86
|
+
private holding;
|
|
87
|
+
private kickRing;
|
|
88
|
+
private stepRing;
|
|
89
|
+
/** 绕满一圈 = 纯模式翻转(不附带任何滚动) */
|
|
90
|
+
private completeRing;
|
|
91
|
+
private renderRing;
|
|
92
|
+
private setMode;
|
|
93
|
+
private doAction;
|
|
94
|
+
private animateScroll;
|
|
95
|
+
private reducedMotion;
|
|
96
|
+
private onInterrupt;
|
|
97
|
+
private interruptSource;
|
|
98
|
+
private attachInterrupt;
|
|
99
|
+
private detachInterrupt;
|
|
100
|
+
private cancelScroll;
|
|
101
|
+
private maxScroll;
|
|
102
|
+
private targetPos;
|
|
103
|
+
private scrollPos;
|
|
104
|
+
private setScrollPos;
|
|
105
|
+
private scrollTo;
|
|
106
|
+
private onRecheck;
|
|
107
|
+
private syncVisible;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
export { ScrollFab, type ScrollFabMode, type ScrollFabOptions, type ScrollFabPosition };
|
|
111
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +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"}
|