@ybgnb/utils 0.1.9 → 0.2.0

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/dom.js CHANGED
@@ -1,225 +1,2 @@
1
- // src/dom/utils/color.ts
2
- var mixColor = (color1, color2, percentage, colorSpace = "srgb", percentage2) => {
3
- return `color-mix(in ${colorSpace}, ${color1} ${percentage}%, ${color2} ${percentage2 ? percentage2 : 100 - percentage}%)`;
4
- };
5
- var hexToRgb = (hex) => {
6
- const r = parseInt(hex.slice(1, 3), 16);
7
- const g = parseInt(hex.slice(3, 5), 16);
8
- const b = parseInt(hex.slice(5, 7), 16);
9
- return { r, g, b };
10
- };
11
-
12
- // src/dom/utils/css.ts
13
- var setCssVar = (k, v) => {
14
- document.documentElement.style.setProperty(k, v);
15
- };
16
-
17
- // src/dom/utils/img.ts
18
- function whenImageLoaded(img) {
19
- return new Promise((resolve, reject) => {
20
- if (img.dataset["state"] === "loading") {
21
- img.src = img.dataset["src"];
22
- }
23
- if (img.complete && img.naturalWidth > 0) {
24
- resolve(img);
25
- return;
26
- }
27
- const onLoad = () => {
28
- cleanup();
29
- resolve(img);
30
- };
31
- const onError = () => {
32
- cleanup();
33
- reject(new Error(`\u56FE\u7247\u52A0\u8F7D\u5931\u8D25: ${img.src}`));
34
- };
35
- const cleanup = () => {
36
- img.removeEventListener("load", onLoad);
37
- img.removeEventListener("error", onError);
38
- };
39
- img.addEventListener("load", onLoad);
40
- img.addEventListener("error", onError);
41
- });
42
- }
43
- async function getImageArrayBuffer(img, type = "image/png") {
44
- const canvas = document.createElement("canvas");
45
- canvas.width = img.naturalWidth;
46
- canvas.height = img.naturalHeight;
47
- const ctx = canvas.getContext("2d");
48
- if (!ctx) throw new Error("\u65E0\u6CD5\u521B\u5EFA Canvas 2D \u4E0A\u4E0B\u6587");
49
- ctx.drawImage(img, 0, 0);
50
- const blob = await new Promise((resolve, reject) => {
51
- canvas.toBlob((b) => b ? resolve(b) : reject(new Error("toBlob \u5931\u8D25")), type);
52
- });
53
- return await blob.arrayBuffer();
54
- }
55
- async function getImgArrayBufferAfterLoad(img, type) {
56
- const loaded = await whenImageLoaded(img);
57
- return await getImageArrayBuffer(loaded, type);
58
- }
59
-
60
- // src/dom/utils/network.ts
61
- function isConnection(obj) {
62
- return obj !== null && typeof obj === "object" && "downlink" in obj && "rtt" in obj && "effectiveType" in obj;
63
- }
64
- function getNetworkInfo() {
65
- if (!navigator.onLine) {
66
- return "offline";
67
- }
68
- if ("connection" in navigator && isConnection(navigator.connection)) {
69
- const connection = navigator.connection;
70
- return {
71
- downlink: connection.downlink,
72
- rtt: connection.rtt,
73
- effectiveType: connection.effectiveType
74
- };
75
- } else {
76
- return "online";
77
- }
78
- }
79
- function onNetworkChange(listener) {
80
- const handleChange = () => {
81
- listener(getNetworkInfo());
82
- };
83
- if ("connection" in navigator && isConnection(navigator.connection)) {
84
- const connection = navigator.connection;
85
- connection.addEventListener("change", handleChange);
86
- return () => connection.removeEventListener("change", handleChange);
87
- } else {
88
- window.addEventListener("online", handleChange);
89
- window.addEventListener("offline", handleChange);
90
- return () => {
91
- window.removeEventListener("online", handleChange);
92
- window.removeEventListener("offline", handleChange);
93
- };
94
- }
95
- }
96
-
97
- // src/dom/utils/page.ts
98
- function onVisibilityChange(listener) {
99
- let hiddenPropName = void 0, hiddenEventName = void 0;
100
- if (typeof document.hidden !== "undefined") {
101
- hiddenPropName = "hidden";
102
- hiddenEventName = "visibilitychange";
103
- } else if ("msHidden" in document && typeof document.msHidden !== "undefined") {
104
- hiddenPropName = "msHidden";
105
- hiddenEventName = "msvisibilitychange";
106
- } else if ("webkitHidden" in document && typeof document.webkitHidden !== "undefined") {
107
- hiddenPropName = "webkitHidden";
108
- hiddenEventName = "webkitvisibilitychange";
109
- }
110
- if (!hiddenPropName || !hiddenEventName) {
111
- return null;
112
- }
113
- const handler = () => {
114
- listener(document[hiddenPropName]);
115
- };
116
- document.addEventListener(hiddenEventName, handler);
117
- return () => {
118
- document.removeEventListener(hiddenEventName, handler);
119
- };
120
- }
121
-
122
- // src/dom/utils/scroll.ts
123
- async function humanScrollElIntoCenter(el) {
124
- if (!el) return;
125
- const rect = el.getBoundingClientRect();
126
- const doc = document.documentElement;
127
- const body = document.body;
128
- const scrollX = window.scrollX || doc.scrollLeft || body.scrollLeft;
129
- const scrollY = window.scrollY || doc.scrollTop || body.scrollTop;
130
- const viewW = window.innerWidth;
131
- const viewH = window.innerHeight;
132
- const targetX = rect.left + scrollX + rect.width / 2 - viewW / 2;
133
- const targetY = rect.top + scrollY + rect.height / 2 - viewH / 2;
134
- const maxX = Math.max(0, body.scrollWidth - viewW);
135
- const maxY = Math.max(0, body.scrollHeight - viewH);
136
- if (maxX === 0 && maxY === 0) return;
137
- const newX = maxX > 0 ? Math.min(Math.max(targetX, 0), maxX) : scrollX;
138
- const newY = maxY > 0 ? Math.min(Math.max(targetY, 0), maxY) : scrollY;
139
- const needScrollX = Math.abs(window.scrollX - newX) > 1;
140
- const needScrollY = Math.abs(window.scrollY - newY) > 1;
141
- if (!needScrollX && !needScrollY) return;
142
- await humanScrollTo(newX, newY);
143
- }
144
- function waitScrollTo(targetX, targetY) {
145
- return new Promise((resolve) => {
146
- const currentX = window.scrollX;
147
- const currentY = window.scrollY;
148
- if (Math.abs(currentX - targetX) < 1 && Math.abs(currentY - targetY) < 1) {
149
- resolve();
150
- return;
151
- }
152
- let timer;
153
- let finished = false;
154
- const cleanup = () => {
155
- if (finished) return;
156
- finished = true;
157
- window.removeEventListener("scroll", handler);
158
- if (timer) clearTimeout(timer);
159
- resolve();
160
- };
161
- const handler = () => {
162
- if (timer) clearTimeout(timer);
163
- timer = window.setTimeout(cleanup, 50);
164
- };
165
- const failSafe = window.setTimeout(() => {
166
- console.debug("[waitScrollTo] fallback timeout reached");
167
- cleanup();
168
- }, 1e3);
169
- const cleanupWithTimeout = () => {
170
- cleanup();
171
- clearTimeout(failSafe);
172
- };
173
- const finalHandler = () => {
174
- if (timer) clearTimeout(timer);
175
- timer = window.setTimeout(cleanupWithTimeout, 50);
176
- };
177
- window.addEventListener("scroll", finalHandler, { passive: true });
178
- window.scrollTo({ left: targetX, top: targetY });
179
- requestAnimationFrame(() => {
180
- const nowX = window.scrollX;
181
- const nowY = window.scrollY;
182
- if (Math.abs(nowX - targetX) < 1 && Math.abs(nowY - targetY) < 1) {
183
- cleanupWithTimeout();
184
- }
185
- });
186
- });
187
- }
188
- async function humanScrollTo(targetX, targetY) {
189
- return new Promise((resolve) => {
190
- let currentX = window.scrollX;
191
- let currentY = window.scrollY;
192
- async function step() {
193
- const dx = targetX - currentX;
194
- const dy = targetY - currentY;
195
- if (Math.abs(dx) < 1 && Math.abs(dy) < 1) {
196
- window.scrollTo(targetX, targetY);
197
- resolve();
198
- return;
199
- }
200
- const stepX = Math.sign(dx) * Math.min(Math.max(2, Math.random() * Math.abs(dx) * 0.2), Math.abs(dx));
201
- const stepY = Math.sign(dy) * Math.min(Math.max(2, Math.random() * Math.abs(dy) * 0.2), Math.abs(dy));
202
- currentX += stepX;
203
- currentY += stepY;
204
- await waitScrollTo(currentX, currentY);
205
- const delay = 10 + Math.random() * 20;
206
- setTimeout(step, delay);
207
- }
208
- step();
209
- });
210
- }
211
- export {
212
- getImageArrayBuffer,
213
- getImgArrayBufferAfterLoad,
214
- getNetworkInfo,
215
- hexToRgb,
216
- humanScrollElIntoCenter,
217
- humanScrollTo,
218
- mixColor,
219
- onNetworkChange,
220
- onVisibilityChange,
221
- setCssVar,
222
- waitScrollTo,
223
- whenImageLoaded
224
- };
1
+ var M=(e,n,t,o="srgb",i)=>`color-mix(in ${o}, ${e} ${t}%, ${n} ${i||100-t}%)`,E=e=>{let n=parseInt(e.slice(1,3),16),t=parseInt(e.slice(3,5),16),o=parseInt(e.slice(5,7),16);return{r:n,g:t,b:o}};var L=(e,n)=>{document.documentElement.style.setProperty(e,n)};function v(e){return new Promise((n,t)=>{if(e.dataset.state==="loading"&&(e.src=e.dataset.src),e.complete&&e.naturalWidth>0){n(e);return}let o=()=>{r(),n(e)},i=()=>{r(),t(new Error(`\u56FE\u7247\u52A0\u8F7D\u5931\u8D25: ${e.src}`))},r=()=>{e.removeEventListener("load",o),e.removeEventListener("error",i)};e.addEventListener("load",o),e.addEventListener("error",i)})}async function p(e,n="image/png"){let t=document.createElement("canvas");t.width=e.naturalWidth,t.height=e.naturalHeight;let o=t.getContext("2d");if(!o)throw new Error("\u65E0\u6CD5\u521B\u5EFA Canvas 2D \u4E0A\u4E0B\u6587");return o.drawImage(e,0,0),await(await new Promise((r,a)=>{t.toBlob(s=>s?r(s):a(new Error("toBlob \u5931\u8D25")),n)})).arrayBuffer()}async function H(e,n){let t=await v(e);return await p(t,n)}function w(e){return e!==null&&typeof e=="object"&&"downlink"in e&&"rtt"in e&&"effectiveType"in e}function g(){if(!navigator.onLine)return"offline";if("connection"in navigator&&w(navigator.connection)){let e=navigator.connection;return{downlink:e.downlink,rtt:e.rtt,effectiveType:e.effectiveType}}else return"online"}function C(e){let n=()=>{e(g())};if("connection"in navigator&&w(navigator.connection)){let t=navigator.connection;return t.addEventListener("change",n),()=>t.removeEventListener("change",n)}else return window.addEventListener("online",n),window.addEventListener("offline",n),()=>{window.removeEventListener("online",n),window.removeEventListener("offline",n)}}function X(e){let n,t;if(typeof document.hidden<"u"?(n="hidden",t="visibilitychange"):"msHidden"in document&&typeof document.msHidden<"u"?(n="msHidden",t="msvisibilitychange"):"webkitHidden"in document&&typeof document.webkitHidden<"u"&&(n="webkitHidden",t="webkitvisibilitychange"),!n||!t)return null;let o=()=>{e(document[n])};return document.addEventListener(t,o),()=>{document.removeEventListener(t,o)}}async function N(e){if(!e)return;let n=e.getBoundingClientRect(),t=document.documentElement,o=document.body,i=window.scrollX||t.scrollLeft||o.scrollLeft,r=window.scrollY||t.scrollTop||o.scrollTop,a=window.innerWidth,s=window.innerHeight,d=n.left+i+n.width/2-a/2,l=n.top+r+n.height/2-s/2,c=Math.max(0,o.scrollWidth-a),u=Math.max(0,o.scrollHeight-s);if(c===0&&u===0)return;let f=c>0?Math.min(Math.max(d,0),c):i,m=u>0?Math.min(Math.max(l,0),u):r,h=Math.abs(window.scrollX-f)>1,b=Math.abs(window.scrollY-m)>1;!h&&!b||await y(f,m)}function x(e,n){return new Promise(t=>{let o=window.scrollX,i=window.scrollY;if(Math.abs(o-e)<1&&Math.abs(i-n)<1){t();return}let r,a=!1,s=()=>{a||(a=!0,window.removeEventListener("scroll",d),r&&clearTimeout(r),t())},d=()=>{r&&clearTimeout(r),r=window.setTimeout(s,50)},l=window.setTimeout(()=>{console.debug("[waitScrollTo] fallback timeout reached"),s()},1e3),c=()=>{s(),clearTimeout(l)},u=()=>{r&&clearTimeout(r),r=window.setTimeout(c,50)};window.addEventListener("scroll",u,{passive:!0}),window.scrollTo({left:e,top:n}),requestAnimationFrame(()=>{let f=window.scrollX,m=window.scrollY;Math.abs(f-e)<1&&Math.abs(m-n)<1&&c()})})}async function y(e,n){return new Promise(t=>{let o=window.scrollX,i=window.scrollY;async function r(){let a=e-o,s=n-i;if(Math.abs(a)<1&&Math.abs(s)<1){window.scrollTo(e,n),t();return}let d=Math.sign(a)*Math.min(Math.max(2,Math.random()*Math.abs(a)*.2),Math.abs(a)),l=Math.sign(s)*Math.min(Math.max(2,Math.random()*Math.abs(s)*.2),Math.abs(s));o+=d,i+=l,await x(o,i);let c=10+Math.random()*20;setTimeout(r,c)}r()})}export{p as getImageArrayBuffer,H as getImgArrayBufferAfterLoad,g as getNetworkInfo,E as hexToRgb,N as humanScrollElIntoCenter,y as humanScrollTo,M as mixColor,C as onNetworkChange,X as onVisibilityChange,L as setCssVar,x as waitScrollTo,v as whenImageLoaded};
225
2
  //# sourceMappingURL=dom.js.map
package/dist/dom.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/dom/utils/color.ts","../src/dom/utils/css.ts","../src/dom/utils/img.ts","../src/dom/utils/network.ts","../src/dom/utils/page.ts","../src/dom/utils/scroll.ts"],"sourcesContent":["/**\n * 生成 CSS `color-mix()` 函数的字符串表示,用于动态计算两种颜色的混合结果\n *\n * @param {string} color1 - 参与混合的第一种颜色(支持 CSS 合法颜色值,如 HEX、RGB、HSL 等)\n * @param {string} color2 - 参与混合的第二种颜色\n * @param {number} percentage - 主颜色(color1)在混合中的占比(百分比数值,范围 0-100)\n * @param {'srgb' | 'hsl'} [colorSpace='srgb'] - 色彩空间选项:\n * - `'srgb'`: 标准 RGB 色彩空间(默认)\n * - `'hsl'`: 色相-饱和度-明度色彩空间\n * @param {number} [percentage2] - 可选参数:副颜色(color2)的独立占比。\n * 若未提供,则自动计算为 `100 - percentage`\n * @returns {string} 可直接用于 CSS 的 `color-mix()` 函数字符串(如 `color-mix(in srgb, red 30%, blue 70%)`)\n *\n * @example\n * // 基础用法(自动计算互补占比)\n * mixColor('red', 'blue', 30)\n * // 返回: \"color-mix(in srgb, red 30%, blue 70%)\"\n *\n * @example\n * // 显式指定双占比 + HSL 色彩空间\n * mixColor('#ff0000', '#0000ff', 40, 'hsl', 60)\n * // 返回: \"color-mix(in hsl, #ff0000 40%, #0000ff 60%)\"\n */\nexport const mixColor = (\n color1: string,\n color2: string,\n percentage: number,\n colorSpace: 'srgb' | 'hsl' = 'srgb',\n percentage2?: number,\n): string => {\n return `color-mix(in ${colorSpace}, ${color1} ${percentage}%, ${color2} ${percentage2 ? percentage2 : 100 - percentage}%)`\n}\n\n/**\n * 将十六进制颜色转换为 RGB\n */\nexport const hexToRgb = (hex: string): { r: number; g: number; b: number } => {\n const r = parseInt(hex.slice(1, 3), 16)\n const g = parseInt(hex.slice(3, 5), 16)\n const b = parseInt(hex.slice(5, 7), 16)\n return { r, g, b }\n}\n","/**\n * 设置 html 根元素的 css 变量\n */\nexport const setCssVar = (k: string, v: string) => {\n document.documentElement.style.setProperty(k, v)\n}\n","/**\n * 等待 <img> 加载完成\n */\nexport function whenImageLoaded(img: HTMLImageElement): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n if (img.dataset['state'] === 'loading') {\n img.src = img.dataset['src']!\n }\n\n if (img.complete && img.naturalWidth > 0) {\n resolve(img)\n return\n }\n\n const onLoad = () => {\n cleanup()\n resolve(img)\n }\n\n const onError = () => {\n cleanup()\n reject(new Error(`图片加载失败: ${img.src}`))\n }\n\n const cleanup = () => {\n img.removeEventListener('load', onLoad)\n img.removeEventListener('error', onError)\n }\n\n img.addEventListener('load', onLoad)\n img.addEventListener('error', onError)\n })\n}\n\n/**\n * 从 <img> 元素获取图片字节数据(ArrayBuffer)\n * @param img 已加载的 <img> 元素\n * @param type 图片类型,可选(默认 png)\n */\nexport async function getImageArrayBuffer(img: HTMLImageElement, type: string = 'image/png'): Promise<ArrayBuffer> {\n const canvas = document.createElement('canvas')\n canvas.width = img.naturalWidth\n canvas.height = img.naturalHeight\n const ctx = canvas.getContext('2d')\n if (!ctx) throw new Error('无法创建 Canvas 2D 上下文')\n ctx.drawImage(img, 0, 0)\n\n const blob: Blob = await new Promise((resolve, reject) => {\n canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('toBlob 失败'))), type)\n })\n\n return await blob.arrayBuffer()\n}\n\n/**\n * 等待加载完成并获取字节数据\n */\nexport async function getImgArrayBufferAfterLoad(img: HTMLImageElement, type?: string): Promise<ArrayBuffer> {\n const loaded = await whenImageLoaded(img)\n return await getImageArrayBuffer(loaded, type)\n}\n","/**\n * 网络信息\n */\nexport interface NetworkInfo extends EventTarget {\n // 带宽(估算)\n downlink: number\n // 延迟(估算)\n rtt: number\n // 类型(估算)\n effectiveType: 'slow-2g' | '2g' | '3g' | '4g'\n}\n\n/**\n * 网络状态\n * - 'offline': 无网络\n * - 'online': 在线但不支持 NetworkInformation API\n * - NetworkInformation: 含详细网络信息\n */\nexport type NetworkState = 'offline' | Omit<NetworkInfo, keyof EventTarget> | 'online'\n\nfunction isConnection(obj: unknown): obj is NetworkInfo {\n return obj !== null && typeof obj === 'object' && 'downlink' in obj && 'rtt' in obj && 'effectiveType' in obj\n}\n\n/**\n * 获取网络信息\n */\nexport function getNetworkInfo(): NetworkState {\n if (!navigator.onLine) {\n return 'offline'\n }\n\n if ('connection' in navigator && isConnection(navigator.connection)) {\n // 现代 Web API 直接获取网络连接的信息\n const connection = navigator.connection\n return {\n downlink: connection.downlink,\n rtt: connection.rtt,\n effectiveType: connection.effectiveType,\n }\n } else {\n return 'online'\n }\n}\n\n/**\n * 监听网络状态变化\n * @param listener 监听器\n * @returns 成功监听时返回解绑函数\n */\nexport function onNetworkChange(listener: (info: NetworkState) => void) {\n const handleChange = () => {\n listener(getNetworkInfo())\n }\n\n if ('connection' in navigator && isConnection(navigator.connection)) {\n // 现代 Web API\n const connection = navigator.connection\n connection.addEventListener('change', handleChange)\n return () => connection.removeEventListener('change', handleChange)\n } else {\n window.addEventListener('online', handleChange)\n window.addEventListener('offline', handleChange)\n return () => {\n window.removeEventListener('online', handleChange)\n window.removeEventListener('offline', handleChange)\n }\n }\n}\n","/** 可见性改变的监听器 */\nexport interface VisibilityChangeListener {\n (hidden: boolean): void\n}\n\n/**\n * 监听页面可见性变化(兼容旧的IE/Chrome)\n * @param listener 监听器\n * @returns 成功监听时返回解绑函数\n */\nexport function onVisibilityChange(listener: VisibilityChangeListener) {\n let hiddenPropName: string | undefined = undefined,\n hiddenEventName: string | undefined = undefined\n\n if (typeof document.hidden !== 'undefined') {\n // 现代 Web API\n hiddenPropName = 'hidden'\n hiddenEventName = 'visibilitychange'\n } else if ('msHidden' in document && typeof document.msHidden !== 'undefined') {\n // 旧 IE\n hiddenPropName = 'msHidden'\n hiddenEventName = 'msvisibilitychange'\n } else if ('webkitHidden' in document && typeof document.webkitHidden !== 'undefined') {\n // 旧 Chrome\n hiddenPropName = 'webkitHidden'\n hiddenEventName = 'webkitvisibilitychange'\n }\n\n if (!hiddenPropName || !hiddenEventName) {\n return null\n }\n\n const handler = () => {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n listener(document[hiddenPropName])\n }\n\n document.addEventListener(hiddenEventName, handler)\n\n return () => {\n document.removeEventListener(hiddenEventName, handler)\n }\n}\n","/**\n * 滚动元素到屏幕中心\n * @param el\n */\nexport async function humanScrollElIntoCenter(el: HTMLElement) {\n if (!el) return\n\n const rect = el.getBoundingClientRect()\n const doc = document.documentElement\n const body = document.body\n\n const scrollX = window.scrollX || doc.scrollLeft || body.scrollLeft\n const scrollY = window.scrollY || doc.scrollTop || body.scrollTop\n\n const viewW = window.innerWidth\n const viewH = window.innerHeight\n\n // 目标中心点相对页面的位置\n const targetX = rect.left + scrollX + rect.width / 2 - viewW / 2\n const targetY = rect.top + scrollY + rect.height / 2 - viewH / 2\n\n const maxX = Math.max(0, body.scrollWidth - viewW)\n const maxY = Math.max(0, body.scrollHeight - viewH)\n\n // 页面不需要滚动\n if (maxX === 0 && maxY === 0) return\n\n // 水平滚动:仅在可滚动时\n const newX = maxX > 0 ? Math.min(Math.max(targetX, 0), maxX) : scrollX\n // 垂直滚动\n const newY = maxY > 0 ? Math.min(Math.max(targetY, 0), maxY) : scrollY\n\n // 判断是否真正需要滚动\n const needScrollX = Math.abs(window.scrollX - newX) > 1\n const needScrollY = Math.abs(window.scrollY - newY) > 1\n if (!needScrollX && !needScrollY) return\n\n await humanScrollTo(newX, newY)\n}\n\n/**\n * 等待滚动\n */\nexport function waitScrollTo(targetX: number, targetY: number): Promise<void> {\n return new Promise((resolve) => {\n const currentX = window.scrollX\n const currentY = window.scrollY\n\n // 若无需滚动\n if (Math.abs(currentX - targetX) < 1 && Math.abs(currentY - targetY) < 1) {\n resolve()\n return\n }\n\n let timer: number | undefined\n let finished = false\n\n const cleanup = () => {\n if (finished) return\n finished = true\n window.removeEventListener('scroll', handler)\n if (timer) clearTimeout(timer)\n resolve()\n }\n\n const handler = () => {\n if (timer) clearTimeout(timer)\n // 若 50ms 内无新滚动事件,则视为滚动结束\n timer = window.setTimeout(cleanup, 50)\n }\n\n // 启动兜底超时(比如滚动事件根本不触发)\n const failSafe = window.setTimeout(() => {\n console.debug('[waitScrollTo] fallback timeout reached')\n cleanup()\n }, 1000)\n\n const cleanupWithTimeout = () => {\n cleanup()\n clearTimeout(failSafe)\n }\n\n // 替换 cleanup,确保清理超时器\n const finalHandler = () => {\n if (timer) clearTimeout(timer)\n timer = window.setTimeout(cleanupWithTimeout, 50)\n }\n\n // 注册事件\n window.addEventListener('scroll', finalHandler, { passive: true })\n\n // 立即触发滚动\n window.scrollTo({ left: targetX, top: targetY })\n\n // 有时浏览器同步滚动,不触发 scroll 事件\n requestAnimationFrame(() => {\n const nowX = window.scrollX\n const nowY = window.scrollY\n if (Math.abs(nowX - targetX) < 1 && Math.abs(nowY - targetY) < 1) {\n cleanupWithTimeout()\n }\n })\n })\n}\n\n/**\n * 模拟人类手感滚动到指定位置\n */\nexport async function humanScrollTo(targetX: number, targetY: number): Promise<void> {\n return new Promise<void>((resolve) => {\n let currentX = window.scrollX\n let currentY = window.scrollY\n\n async function step() {\n const dx = targetX - currentX\n const dy = targetY - currentY\n\n // 如果距离足够小,直接跳到目标结束\n if (Math.abs(dx) < 1 && Math.abs(dy) < 1) {\n window.scrollTo(targetX, targetY)\n resolve()\n return\n }\n\n // 随机步长 (最小 2px,最大剩余距离的 20%)\n const stepX = Math.sign(dx) * Math.min(Math.max(2, Math.random() * Math.abs(dx) * 0.2), Math.abs(dx))\n const stepY = Math.sign(dy) * Math.min(Math.max(2, Math.random() * Math.abs(dy) * 0.2), Math.abs(dy))\n\n currentX += stepX\n currentY += stepY\n await waitScrollTo(currentX, currentY)\n\n // 随机短暂停顿 10~30ms\n const delay = 10 + Math.random() * 20\n setTimeout(step, delay)\n }\n\n step()\n })\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/dom/utils/color.ts","../src/dom/utils/css.ts","../src/dom/utils/img.ts","../src/dom/utils/network.ts","../src/dom/utils/page.ts","../src/dom/utils/scroll.ts"],"sourcesContent":["/**\n * 生成 CSS `color-mix()` 函数的字符串表示,用于动态计算两种颜色的混合结果\n *\n * @param {string} color1 - 参与混合的第一种颜色(支持 CSS 合法颜色值,如 HEX、RGB、HSL 等)\n * @param {string} color2 - 参与混合的第二种颜色\n * @param {number} percentage - 主颜色(color1)在混合中的占比(百分比数值,范围 0-100)\n * @param {'srgb' | 'hsl'} [colorSpace='srgb'] - 色彩空间选项:\n * - `'srgb'`: 标准 RGB 色彩空间(默认)\n * - `'hsl'`: 色相-饱和度-明度色彩空间\n * @param {number} [percentage2] - 可选参数:副颜色(color2)的独立占比。\n * 若未提供,则自动计算为 `100 - percentage`\n * @returns {string} 可直接用于 CSS 的 `color-mix()` 函数字符串(如 `color-mix(in srgb, red 30%, blue 70%)`)\n *\n * @example\n * // 基础用法(自动计算互补占比)\n * mixColor('red', 'blue', 30)\n * // 返回: \"color-mix(in srgb, red 30%, blue 70%)\"\n *\n * @example\n * // 显式指定双占比 + HSL 色彩空间\n * mixColor('#ff0000', '#0000ff', 40, 'hsl', 60)\n * // 返回: \"color-mix(in hsl, #ff0000 40%, #0000ff 60%)\"\n */\nexport const mixColor = (\n color1: string,\n color2: string,\n percentage: number,\n colorSpace: 'srgb' | 'hsl' = 'srgb',\n percentage2?: number,\n): string => {\n return `color-mix(in ${colorSpace}, ${color1} ${percentage}%, ${color2} ${percentage2 ? percentage2 : 100 - percentage}%)`\n}\n\n/**\n * 将十六进制颜色转换为 RGB\n */\nexport const hexToRgb = (hex: string): { r: number; g: number; b: number } => {\n const r = parseInt(hex.slice(1, 3), 16)\n const g = parseInt(hex.slice(3, 5), 16)\n const b = parseInt(hex.slice(5, 7), 16)\n return { r, g, b }\n}\n","/**\n * 设置 html 根元素的 css 变量\n */\nexport const setCssVar = (k: string, v: string) => {\n document.documentElement.style.setProperty(k, v)\n}\n","/**\n * 等待 <img> 加载完成\n */\nexport function whenImageLoaded(img: HTMLImageElement): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n if (img.dataset['state'] === 'loading') {\n img.src = img.dataset['src']!\n }\n\n if (img.complete && img.naturalWidth > 0) {\n resolve(img)\n return\n }\n\n const onLoad = () => {\n cleanup()\n resolve(img)\n }\n\n const onError = () => {\n cleanup()\n reject(new Error(`图片加载失败: ${img.src}`))\n }\n\n const cleanup = () => {\n img.removeEventListener('load', onLoad)\n img.removeEventListener('error', onError)\n }\n\n img.addEventListener('load', onLoad)\n img.addEventListener('error', onError)\n })\n}\n\n/**\n * 从 <img> 元素获取图片字节数据(ArrayBuffer)\n * @param img 已加载的 <img> 元素\n * @param type 图片类型,可选(默认 png)\n */\nexport async function getImageArrayBuffer(img: HTMLImageElement, type: string = 'image/png'): Promise<ArrayBuffer> {\n const canvas = document.createElement('canvas')\n canvas.width = img.naturalWidth\n canvas.height = img.naturalHeight\n const ctx = canvas.getContext('2d')\n if (!ctx) throw new Error('无法创建 Canvas 2D 上下文')\n ctx.drawImage(img, 0, 0)\n\n const blob: Blob = await new Promise((resolve, reject) => {\n canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('toBlob 失败'))), type)\n })\n\n return await blob.arrayBuffer()\n}\n\n/**\n * 等待加载完成并获取字节数据\n */\nexport async function getImgArrayBufferAfterLoad(img: HTMLImageElement, type?: string): Promise<ArrayBuffer> {\n const loaded = await whenImageLoaded(img)\n return await getImageArrayBuffer(loaded, type)\n}\n","/**\n * 网络信息\n */\nexport interface NetworkInfo extends EventTarget {\n // 带宽(估算)\n downlink: number\n // 延迟(估算)\n rtt: number\n // 类型(估算)\n effectiveType: 'slow-2g' | '2g' | '3g' | '4g'\n}\n\n/**\n * 网络状态\n * - 'offline': 无网络\n * - 'online': 在线但不支持 NetworkInformation API\n * - NetworkInformation: 含详细网络信息\n */\nexport type NetworkState = 'offline' | Omit<NetworkInfo, keyof EventTarget> | 'online'\n\nfunction isConnection(obj: unknown): obj is NetworkInfo {\n return obj !== null && typeof obj === 'object' && 'downlink' in obj && 'rtt' in obj && 'effectiveType' in obj\n}\n\n/**\n * 获取网络信息\n */\nexport function getNetworkInfo(): NetworkState {\n if (!navigator.onLine) {\n return 'offline'\n }\n\n if ('connection' in navigator && isConnection(navigator.connection)) {\n // 现代 Web API 直接获取网络连接的信息\n const connection = navigator.connection\n return {\n downlink: connection.downlink,\n rtt: connection.rtt,\n effectiveType: connection.effectiveType,\n }\n } else {\n return 'online'\n }\n}\n\n/**\n * 监听网络状态变化\n * @param listener 监听器\n * @returns 成功监听时返回解绑函数\n */\nexport function onNetworkChange(listener: (info: NetworkState) => void) {\n const handleChange = () => {\n listener(getNetworkInfo())\n }\n\n if ('connection' in navigator && isConnection(navigator.connection)) {\n // 现代 Web API\n const connection = navigator.connection\n connection.addEventListener('change', handleChange)\n return () => connection.removeEventListener('change', handleChange)\n } else {\n window.addEventListener('online', handleChange)\n window.addEventListener('offline', handleChange)\n return () => {\n window.removeEventListener('online', handleChange)\n window.removeEventListener('offline', handleChange)\n }\n }\n}\n","/** 可见性改变的监听器 */\nexport interface VisibilityChangeListener {\n (hidden: boolean): void\n}\n\n/**\n * 监听页面可见性变化(兼容旧的IE/Chrome)\n * @param listener 监听器\n * @returns 成功监听时返回解绑函数\n */\nexport function onVisibilityChange(listener: VisibilityChangeListener) {\n let hiddenPropName: string | undefined = undefined,\n hiddenEventName: string | undefined = undefined\n\n if (typeof document.hidden !== 'undefined') {\n // 现代 Web API\n hiddenPropName = 'hidden'\n hiddenEventName = 'visibilitychange'\n } else if ('msHidden' in document && typeof document.msHidden !== 'undefined') {\n // 旧 IE\n hiddenPropName = 'msHidden'\n hiddenEventName = 'msvisibilitychange'\n } else if ('webkitHidden' in document && typeof document.webkitHidden !== 'undefined') {\n // 旧 Chrome\n hiddenPropName = 'webkitHidden'\n hiddenEventName = 'webkitvisibilitychange'\n }\n\n if (!hiddenPropName || !hiddenEventName) {\n return null\n }\n\n const handler = () => {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n listener(document[hiddenPropName])\n }\n\n document.addEventListener(hiddenEventName, handler)\n\n return () => {\n document.removeEventListener(hiddenEventName, handler)\n }\n}\n","/**\n * 滚动元素到屏幕中心\n * @param el\n */\nexport async function humanScrollElIntoCenter(el: HTMLElement) {\n if (!el) return\n\n const rect = el.getBoundingClientRect()\n const doc = document.documentElement\n const body = document.body\n\n const scrollX = window.scrollX || doc.scrollLeft || body.scrollLeft\n const scrollY = window.scrollY || doc.scrollTop || body.scrollTop\n\n const viewW = window.innerWidth\n const viewH = window.innerHeight\n\n // 目标中心点相对页面的位置\n const targetX = rect.left + scrollX + rect.width / 2 - viewW / 2\n const targetY = rect.top + scrollY + rect.height / 2 - viewH / 2\n\n const maxX = Math.max(0, body.scrollWidth - viewW)\n const maxY = Math.max(0, body.scrollHeight - viewH)\n\n // 页面不需要滚动\n if (maxX === 0 && maxY === 0) return\n\n // 水平滚动:仅在可滚动时\n const newX = maxX > 0 ? Math.min(Math.max(targetX, 0), maxX) : scrollX\n // 垂直滚动\n const newY = maxY > 0 ? Math.min(Math.max(targetY, 0), maxY) : scrollY\n\n // 判断是否真正需要滚动\n const needScrollX = Math.abs(window.scrollX - newX) > 1\n const needScrollY = Math.abs(window.scrollY - newY) > 1\n if (!needScrollX && !needScrollY) return\n\n await humanScrollTo(newX, newY)\n}\n\n/**\n * 等待滚动\n */\nexport function waitScrollTo(targetX: number, targetY: number): Promise<void> {\n return new Promise((resolve) => {\n const currentX = window.scrollX\n const currentY = window.scrollY\n\n // 若无需滚动\n if (Math.abs(currentX - targetX) < 1 && Math.abs(currentY - targetY) < 1) {\n resolve()\n return\n }\n\n let timer: number | undefined\n let finished = false\n\n const cleanup = () => {\n if (finished) return\n finished = true\n window.removeEventListener('scroll', handler)\n if (timer) clearTimeout(timer)\n resolve()\n }\n\n const handler = () => {\n if (timer) clearTimeout(timer)\n // 若 50ms 内无新滚动事件,则视为滚动结束\n timer = window.setTimeout(cleanup, 50)\n }\n\n // 启动兜底超时(比如滚动事件根本不触发)\n const failSafe = window.setTimeout(() => {\n console.debug('[waitScrollTo] fallback timeout reached')\n cleanup()\n }, 1000)\n\n const cleanupWithTimeout = () => {\n cleanup()\n clearTimeout(failSafe)\n }\n\n // 替换 cleanup,确保清理超时器\n const finalHandler = () => {\n if (timer) clearTimeout(timer)\n timer = window.setTimeout(cleanupWithTimeout, 50)\n }\n\n // 注册事件\n window.addEventListener('scroll', finalHandler, { passive: true })\n\n // 立即触发滚动\n window.scrollTo({ left: targetX, top: targetY })\n\n // 有时浏览器同步滚动,不触发 scroll 事件\n requestAnimationFrame(() => {\n const nowX = window.scrollX\n const nowY = window.scrollY\n if (Math.abs(nowX - targetX) < 1 && Math.abs(nowY - targetY) < 1) {\n cleanupWithTimeout()\n }\n })\n })\n}\n\n/**\n * 模拟人类手感滚动到指定位置\n */\nexport async function humanScrollTo(targetX: number, targetY: number): Promise<void> {\n return new Promise<void>((resolve) => {\n let currentX = window.scrollX\n let currentY = window.scrollY\n\n async function step() {\n const dx = targetX - currentX\n const dy = targetY - currentY\n\n // 如果距离足够小,直接跳到目标结束\n if (Math.abs(dx) < 1 && Math.abs(dy) < 1) {\n window.scrollTo(targetX, targetY)\n resolve()\n return\n }\n\n // 随机步长 (最小 2px,最大剩余距离的 20%)\n const stepX = Math.sign(dx) * Math.min(Math.max(2, Math.random() * Math.abs(dx) * 0.2), Math.abs(dx))\n const stepY = Math.sign(dy) * Math.min(Math.max(2, Math.random() * Math.abs(dy) * 0.2), Math.abs(dy))\n\n currentX += stepX\n currentY += stepY\n await waitScrollTo(currentX, currentY)\n\n // 随机短暂停顿 10~30ms\n const delay = 10 + Math.random() * 20\n setTimeout(step, delay)\n }\n\n step()\n })\n}\n"],"mappings":"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","names":["mixColor","color1","color2","percentage","colorSpace","percentage2","hexToRgb","hex","r","g","b","setCssVar","k","v","whenImageLoaded","img","resolve","reject","onLoad","cleanup","onError","getImageArrayBuffer","type","canvas","ctx","b","getImgArrayBufferAfterLoad","loaded","isConnection","obj","getNetworkInfo","connection","onNetworkChange","listener","handleChange","onVisibilityChange","listener","hiddenPropName","hiddenEventName","handler","humanScrollElIntoCenter","el","rect","doc","body","scrollX","scrollY","viewW","viewH","targetX","targetY","maxX","maxY","newX","newY","needScrollX","needScrollY","humanScrollTo","waitScrollTo","resolve","currentX","currentY","timer","finished","cleanup","handler","failSafe","cleanupWithTimeout","finalHandler","nowX","nowY","step","dx","dy","stepX","stepY","delay"]}