marquee-selection 0.0.11
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/README.html +442 -0
- package/README.md +204 -0
- package/dist/index.d.ts +215 -0
- package/dist/infinite-canvas.es.js +475 -0
- package/dist/infinite-canvas.es.js.map +1 -0
- package/dist/infinite-canvas.umd.js +2 -0
- package/dist/infinite-canvas.umd.js.map +1 -0
- package/dist/marquee-selection.es.js +672 -0
- package/dist/marquee-selection.es.js.map +1 -0
- package/dist/marquee-selection.umd.js +2 -0
- package/dist/marquee-selection.umd.js.map +1 -0
- package/index.html +740 -0
- package/package.json +49 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"infinite-canvas.es.js","sources":["../src/InfiniteCanvas.ts"],"sourcesContent":["/**\n * InfiniteCanvas\n *\n * 在指定 HTML 容器内启用双指缩放与双指平移(触摸手势)。\n * 会将容器现有子节点搬运到一个内部 content 容器中,并对该 content 施加 CSS 变换。\n */\nexport type InfiniteCanvasOptions = {\n container: HTMLElement; // 容器节点(必须)\n // 初始缩放\n initialScale?: number;\n // 允许缩放边界\n minScale?: number;\n maxScale?: number;\n // 初始平移\n initialTranslate?: { x: number; y: number };\n // 是否强制为容器添加 overflow: hidden\n clampOverflowHidden?: boolean;\n // 为内部 content 添加的类名(可用于样式定制)\n contentClassName?: string;\n // 初始化时自动“铺满可见”(默认不启用)。\n // - true 等价于 { mode: 'contain', alignX: 'center', alignY: 'center', padding: 0 }\n // - mode:\n // 'contain' => 完全展示(按较小比例缩放,使内容完整可见)\n // 'width' => 以宽度适配\n // 'height' => 以高度适配\n // - 对齐:alignX: left|center|right;alignY: top|center|bottom\n // - padding: 留白(px)\n autoFit?:\n | boolean\n | {\n mode?: \"contain\" | \"width\" | \"height\" | \"cover\";\n alignX?: \"left\" | \"center\" | \"right\";\n alignY?: \"top\" | \"center\" | \"bottom\";\n padding?: number;\n };\n // 尺寸变化时自动重新适配(基于最近一次 fitToView/autoFit 的参数)\n autoRefitOnResize?: boolean;\n // 初始化完成回调:参数为包装后的 content 节点\n onReady?: (content: HTMLDivElement) => void;\n // 每次变换后的回调(缩放/平移),入参为当前变换与 content 节点\n onTransform?: (state: {\n scale: number;\n x: number;\n y: number;\n content: HTMLDivElement;\n }) => void;\n // 变换开始回调(触摸双指/滚轮缩放/平移刚开始时触发一次)\n onTransformStart?: (state: {\n scale: number;\n x: number;\n y: number;\n content: HTMLDivElement;\n }) => void;\n // 变换结束回调(松手或滚轮/平移停止后的短暂去抖后触发一次)\n onTransformEnd?: (state: {\n scale: number;\n x: number;\n y: number;\n content: HTMLDivElement;\n }) => void;\n};\n\nexport class InfiniteCanvas {\n private container: HTMLElement;\n private content: HTMLDivElement;\n private scale = 1;\n private tx = 0;\n private ty = 0;\n private minScale: number;\n private maxScale: number;\n private destroyed = false;\n private onTransformCb?: (state: {\n scale: number;\n x: number;\n y: number;\n content: HTMLDivElement;\n }) => void;\n private onTransformStartCb?: (state: {\n scale: number;\n x: number;\n y: number;\n content: HTMLDivElement;\n }) => void;\n private onTransformEndCb?: (state: {\n scale: number;\n x: number;\n y: number;\n content: HTMLDivElement;\n }) => void;\n private transforming = false;\n private transformEndTimer: any = null;\n private refitTimer: any = null;\n private autoFitPreset: {\n mode: \"contain\" | \"width\" | \"height\" | \"cover\";\n alignX: \"left\" | \"center\" | \"right\";\n alignY: \"top\" | \"center\" | \"bottom\";\n padding: number;\n } | null = null;\n private resizeObserver?: ResizeObserver;\n private resizeHandler?: () => void;\n // 捕获手势的透明命中图层\n private hitLayer!: HTMLDivElement;\n\n // 手势状态\n private gesture: null | {\n startMid: { x: number; y: number };\n startDist: number;\n startScale: number;\n startTx: number;\n startTy: number;\n worldAtMid: { x: number; y: number };\n } = null;\n private inContainerGesture = false; // 是否在容器内发起的多指手势(用于 iOS gesture 事件拦截)\n // 窗口级捕获:方法声明\n private onWindowTouchStart?: (ev: TouchEvent) => void;\n private onWindowTouchMove?: (ev: TouchEvent) => void;\n private onWindowTouchEnd?: (ev: TouchEvent) => void;\n private onWindowWheel?: (ev: WheelEvent) => void;\n\n constructor(options: InfiniteCanvasOptions) {\n const {\n container,\n initialScale = 1,\n minScale = 0.2,\n maxScale = 6,\n initialTranslate = { x: 0, y: 0 },\n clampOverflowHidden = true,\n contentClassName,\n autoFit,\n autoRefitOnResize = false,\n onReady,\n onTransform,\n onTransformStart,\n onTransformEnd,\n } = options;\n\n if (!container) throw new Error(\"InfiniteCanvas: container is required\");\n this.container = container;\n this.scale = initialScale;\n this.tx = initialTranslate.x;\n this.ty = initialTranslate.y;\n this.minScale = Math.min(minScale, maxScale);\n this.maxScale = Math.max(minScale, maxScale);\n this.onTransformCb = onTransform;\n this.onTransformStartCb = onTransformStart;\n this.onTransformEndCb = onTransformEnd;\n\n // 包装现有子节点\n const content = document.createElement(\"div\");\n content.style.position = \"relative\";\n content.style.transformOrigin = \"0 0\";\n content.style.willChange = \"transform\";\n if (contentClassName) content.className = contentClassName;\n\n // 将容器的现有子节点搬到 content 中\n const children: Node[] = [];\n while (container.firstChild) {\n children.push(container.firstChild);\n container.removeChild(container.firstChild);\n }\n children.forEach((n) => content.appendChild(n));\n container.appendChild(content);\n this.content = content;\n\n // 基础样式与交互策略\n container.style.touchAction = \"none\"; // 阻止默认触摸手势(Pointer 事件)\n // 避免容器处发生浏览器滚动产生的回弹/页面滚动\n (container.style as any).overscrollBehavior =\n (container.style as any).overscrollBehavior || \"none\";\n if (clampOverflowHidden) {\n if (!container.style.overflow) container.style.overflow = \"hidden\";\n }\n\n // 若启用 autoFit,计算并设置初始 transform\n if (autoFit) {\n const af = this.normalizeFitParams(autoFit);\n const fit = this.computeFitTransform(af);\n if (fit) {\n this.scale = this.clampScale(fit.scale);\n this.tx = fit.x;\n this.ty = fit.y;\n // 记录预设,便于后续 autoRefitOnResize 重算\n this.autoFitPreset = af;\n }\n }\n this.applyTransform();\n\n // 自动重算:容器或窗口尺寸变化\n if (this.autoFitPreset && autoRefitOnResize) {\n const schedule = () => {\n if (this.refitTimer) clearTimeout(this.refitTimer);\n this.refitTimer = setTimeout(() => {\n this.fitToView(this.autoFitPreset || undefined);\n }, 100);\n };\n try {\n this.resizeObserver = new ResizeObserver(() => schedule());\n this.resizeObserver.observe(this.container);\n } catch {}\n this.resizeHandler = schedule;\n window.addEventListener(\"resize\", this.resizeHandler);\n }\n\n // 事件\n this.onTouchStart = this.onTouchStart.bind(this);\n this.onTouchMove = this.onTouchMove.bind(this);\n this.onTouchEnd = this.onTouchEnd.bind(this);\n this.onWheel = this.onWheel.bind(this);\n this.onGesture = this.onGesture.bind(this);\n // 窗口级捕获监听(绑定 this)\n this.onWindowTouchStart = this._windowTouchStart.bind(this);\n this.onWindowTouchMove = this._windowTouchMove.bind(this);\n this.onWindowTouchEnd = this._windowTouchEnd.bind(this);\n this.onWindowWheel = this._windowWheel.bind(this);\n\n // 命中图层:覆盖容器区域,专门接收触摸/滚轮手势\n try {\n if (getComputedStyle(container).position === \"static\") {\n // 确保绝对定位子元素可覆盖\n container.style.position = \"relative\";\n }\n } catch {}\n // 窗口级捕获:在任何层级之上先拿到事件,再判定是否命中容器\n window.addEventListener(\n \"touchstart\",\n this.onWindowTouchStart as any,\n {\n passive: false,\n capture: true,\n } as any\n );\n window.addEventListener(\n \"touchmove\",\n this.onWindowTouchMove as any,\n {\n passive: false,\n capture: true,\n } as any\n );\n window.addEventListener(\n \"touchend\",\n this.onWindowTouchEnd as any,\n {\n passive: false,\n capture: true,\n } as any\n );\n window.addEventListener(\n \"touchcancel\",\n this.onWindowTouchEnd as any,\n {\n passive: false,\n capture: true,\n } as any\n );\n window.addEventListener(\n \"wheel\",\n this.onWindowWheel as any,\n {\n passive: false,\n capture: true,\n } as any\n );\n // iOS Safari 专有:阻止页面级捏合缩放\n // 使用 window 级捕获,保证能先于页面处理,且仅当手势在容器内发起时拦截\n window.addEventListener(\n \"gesturestart\" as any,\n this.onGesture as any,\n { passive: false, capture: true } as any\n );\n window.addEventListener(\n \"gesturechange\" as any,\n this.onGesture as any,\n { passive: false, capture: true } as any\n );\n window.addEventListener(\n \"gestureend\" as any,\n this.onGesture as any,\n { passive: false, capture: true } as any\n );\n // 构造结束:通知外部已就绪\n if (typeof onReady === \"function\") {\n try {\n onReady(this.content);\n } catch {}\n }\n }\n\n // 对外 API\n public destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n const c = this.container;\n // 清理窗口捕获监听\n window.removeEventListener(\n \"touchstart\" as any,\n this.onWindowTouchStart as any,\n true as any\n );\n window.removeEventListener(\n \"touchmove\" as any,\n this.onWindowTouchMove as any,\n true as any\n );\n window.removeEventListener(\n \"touchend\" as any,\n this.onWindowTouchEnd as any,\n true as any\n );\n window.removeEventListener(\n \"touchcancel\" as any,\n this.onWindowTouchEnd as any,\n true as any\n );\n window.removeEventListener(\n \"wheel\" as any,\n this.onWindowWheel as any,\n true as any\n );\n window.removeEventListener(\n \"gesturestart\" as any,\n this.onGesture as any,\n true as any\n );\n window.removeEventListener(\n \"gesturechange\" as any,\n this.onGesture as any,\n true as any\n );\n window.removeEventListener(\n \"gestureend\" as any,\n this.onGesture as any,\n true as any\n );\n if (this.resizeObserver) {\n try {\n this.resizeObserver.disconnect();\n } catch {}\n this.resizeObserver = undefined;\n }\n if (this.resizeHandler) {\n window.removeEventListener(\"resize\", this.resizeHandler);\n this.resizeHandler = undefined;\n }\n // 不自动还原 DOM 结构,避免破坏外部状态;如需,还可提供 resetDOM()\n }\n\n public getTransform() {\n return { scale: this.scale, x: this.tx, y: this.ty };\n }\n\n public setTransform(t: { scale?: number; x?: number; y?: number }) {\n if (typeof t.scale === \"number\") this.scale = this.clampScale(t.scale);\n if (typeof t.x === \"number\") this.tx = t.x;\n if (typeof t.y === \"number\") this.ty = t.y;\n this.applyTransform();\n }\n\n public reset() {\n this.scale = 1;\n this.tx = 0;\n this.ty = 0;\n this.applyTransform();\n }\n\n // 内部工具\n private clampScale(s: number) {\n return Math.min(this.maxScale, Math.max(this.minScale, s));\n }\n\n private normalizeFitParams(\n p:\n | boolean\n | {\n mode?: \"contain\" | \"width\" | \"height\" | \"cover\";\n alignX?: \"left\" | \"center\" | \"right\";\n alignY?: \"top\" | \"center\" | \"bottom\";\n padding?: number;\n }\n ) {\n const base =\n typeof p === \"object\"\n ? p\n : {\n mode: \"contain\" as const,\n alignX: \"center\" as const,\n alignY: \"center\" as const,\n padding: 0,\n };\n return {\n mode: (base.mode || \"contain\") as\n | \"contain\"\n | \"width\"\n | \"height\"\n | \"cover\",\n alignX: (base.alignX || \"center\") as \"left\" | \"center\" | \"right\",\n alignY: (base.alignY || \"center\") as \"top\" | \"center\" | \"bottom\",\n padding: Math.max(0, base.padding ?? 0),\n };\n }\n\n // 计算使内容按给定策略适配到容器可见区域的变换(不应用)\n public computeFitTransform(params?: {\n mode?: \"contain\" | \"width\" | \"height\" | \"cover\";\n alignX?: \"left\" | \"center\" | \"right\";\n alignY?: \"top\" | \"center\" | \"bottom\";\n padding?: number;\n }): { scale: number; x: number; y: number } | null {\n const c = this.container;\n const content = this.content;\n const cw = Math.max(0, c.clientWidth);\n const ch = Math.max(0, c.clientHeight);\n if (cw === 0 || ch === 0) return null;\n // 测量内容固有尺寸(未考虑当前 transform),以 content 本地坐标计算其后代并集\n const cRect = content.getBoundingClientRect();\n const s0 = this.scale || 1; // 当前缩放,用于反算本地尺寸\n let minLX = Number.POSITIVE_INFINITY;\n let minTY = Number.POSITIVE_INFINITY;\n let maxRX = Number.NEGATIVE_INFINITY;\n let maxBY = Number.NEGATIVE_INFINITY;\n const all = Array.from(content.querySelectorAll(\"*\")) as Element[];\n for (const el of all) {\n const r = el.getBoundingClientRect();\n if (r.width <= 0 || r.height <= 0) continue;\n const lx = (r.left - cRect.left) / s0;\n const ty = (r.top - cRect.top) / s0;\n const rx = (r.right - cRect.left) / s0;\n const by = (r.bottom - cRect.top) / s0;\n if (lx < minLX) minLX = lx;\n if (ty < minTY) minTY = ty;\n if (rx > maxRX) maxRX = rx;\n if (by > maxBY) maxBY = by;\n }\n // 若集合为空,则退回使用 content 自身尺寸\n if (\n !isFinite(minLX) ||\n !isFinite(minTY) ||\n !isFinite(maxRX) ||\n !isFinite(maxBY)\n ) {\n const w0 = Math.max(\n content.scrollWidth,\n content.offsetWidth,\n content.clientWidth\n );\n const h0 = Math.max(\n content.scrollHeight,\n content.offsetHeight,\n content.clientHeight\n );\n if (w0 === 0 || h0 === 0) return null;\n minLX = 0;\n minTY = 0;\n maxRX = w0;\n maxBY = h0;\n }\n const w = Math.max(0, maxRX - minLX);\n const h = Math.max(0, maxBY - minTY);\n\n const mode = params?.mode ?? \"contain\";\n const alignX = params?.alignX ?? \"center\";\n const alignY = params?.alignY ?? \"center\";\n const pad = Math.max(0, params?.padding ?? 0);\n const availW = Math.max(0, cw - pad * 2);\n const availH = Math.max(0, ch - pad * 2);\n\n const sx = availW / w;\n const sy = availH / h;\n let s = 1;\n if (mode === \"width\") s = sx;\n else if (mode === \"height\") s = sy;\n else if (mode === \"cover\") s = Math.max(sx, sy); // 铺满容器,可能裁切\n else s = Math.min(sx, sy); // contain 完全展示\n s = this.clampScale(s);\n\n const scaledW = w * s;\n const scaledH = h * s;\n // 先计算对齐后的“目标可视区域左上角”\n let baseX = pad;\n let baseY = pad;\n // 水平对齐\n if (alignX === \"center\") baseX = Math.round((cw - scaledW) / 2);\n else if (alignX === \"right\") baseX = Math.round(cw - scaledW - pad);\n // 垂直对齐\n if (alignY === \"center\") baseY = Math.round((ch - scaledH) / 2);\n else if (alignY === \"bottom\") baseY = Math.round(ch - scaledH - pad);\n // 将并集框的左上角(minLX,minTY)对齐到 baseX/baseY,因此内容需要额外平移 -minL*scale\n const x = baseX - Math.round(minLX * s);\n const y = baseY - Math.round(minTY * s);\n return { scale: s, x, y };\n }\n\n // 应用适配(基于 computeFitTransform 计算结果)\n public fitToView(params?: {\n mode?: \"contain\" | \"width\" | \"height\" | \"cover\";\n alignX?: \"left\" | \"center\" | \"right\";\n alignY?: \"top\" | \"center\" | \"bottom\";\n padding?: number;\n }) {\n const norm = this.normalizeFitParams(params || { mode: \"contain\" });\n const fit = this.computeFitTransform(norm);\n if (!fit) return;\n this.scale = this.clampScale(fit.scale);\n this.tx = fit.x;\n this.ty = fit.y;\n this.autoFitPreset = norm;\n this.applyTransform();\n }\n\n // 仅用于移除 window.resize 监听占位(便于彻底清理)\n private _noop() {}\n\n // 命中检测:点是否在容器可见区域内\n private _isPointInContainer(x: number, y: number) {\n const r = this.container.getBoundingClientRect();\n return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;\n }\n\n // 窗口级触摸/滚轮捕获:仅当命中容器时转发\n private _windowTouchStart(ev: TouchEvent) {\n if (ev.touches.length === 0) return;\n let hit = false;\n for (let i = 0; i < ev.touches.length; i++) {\n const t = ev.touches.item ? ev.touches.item(i) : (ev.touches as any)[i];\n if (!t) continue;\n if (this._isPointInContainer((t as any).clientX, (t as any).clientY)) {\n hit = true;\n break;\n }\n }\n if (!hit) return;\n this.onTouchStart(ev);\n }\n private _windowTouchMove(ev: TouchEvent) {\n if (ev.touches.length === 0) return;\n let hit = false;\n for (let i = 0; i < ev.touches.length; i++) {\n const t = ev.touches.item ? ev.touches.item(i) : (ev.touches as any)[i];\n if (!t) continue;\n if (this._isPointInContainer((t as any).clientX, (t as any).clientY)) {\n hit = true;\n break;\n }\n }\n if (!hit) return;\n this.onTouchMove(ev);\n }\n private _windowTouchEnd(ev: TouchEvent) {\n // end 没有 touches 命中点可用,尝试用 changedTouches 任一指头判断是否来源于容器内\n const ct = (ev as any).changedTouches as TouchList | undefined;\n if (ct && ct.length > 0) {\n const tt = (ct.item ? ct.item(0) : (ct as any)[0]) as any;\n if (tt && !this._isPointInContainer(tt.clientX, tt.clientY)) return;\n }\n this.onTouchEnd(ev);\n }\n private _windowWheel(ev: WheelEvent) {\n if (!this._isPointInContainer(ev.clientX, ev.clientY)) return;\n this.onWheel(ev);\n }\n\n private applyTransform() {\n this.content.style.transform = `translate(${this.tx}px, ${this.ty}px) scale(${this.scale})`;\n // 触发回调与事件,供外部(如圈选覆盖层)刷新\n if (this.onTransformCb) {\n try {\n this.onTransformCb({\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n });\n } catch {}\n }\n try {\n const ev = new CustomEvent(\"infiniteCanvas:transform\", {\n detail: {\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n },\n bubbles: true,\n });\n this.container.dispatchEvent(ev);\n } catch {}\n // 若处于变换中,启动/重置结束检测定时器\n if (this.transforming) {\n if (this.transformEndTimer) clearTimeout(this.transformEndTimer);\n this.transformEndTimer = setTimeout(() => {\n this.transforming = false;\n try {\n const endEv = new CustomEvent(\"infiniteCanvas:transformEnd\", {\n detail: {\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n },\n bubbles: true,\n });\n this.container.dispatchEvent(endEv);\n } catch {}\n // 回调 onTransformEnd(与事件同步)\n if (this.onTransformEndCb) {\n try {\n this.onTransformEndCb({\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n });\n } catch {}\n }\n }, 120);\n }\n }\n\n private getTouches(ev: TouchEvent) {\n const t = ev.touches;\n const arr: { x: number; y: number }[] = [];\n for (let i = 0; i < t.length; i++) {\n const touch = t.item ? t.item(i) : (t as any)[i];\n if (touch) arr.push({ x: touch.clientX, y: touch.clientY });\n }\n return arr;\n }\n\n private midpoint(a: { x: number; y: number }, b: { x: number; y: number }) {\n return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n }\n\n private distance(a: { x: number; y: number }, b: { x: number; y: number }) {\n const dx = a.x - b.x;\n const dy = a.y - b.y;\n return Math.hypot(dx, dy);\n }\n\n private onTouchStart(ev: TouchEvent) {\n // 双指起手\n if (ev.touches.length >= 2) {\n ev.preventDefault();\n this.inContainerGesture = true; // 在容器内开始多指\n // 广播开始\n if (!this.transforming) {\n this.transforming = true;\n try {\n const startEv = new CustomEvent(\"infiniteCanvas:transformStart\", {\n detail: {\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n },\n bubbles: true,\n });\n this.container.dispatchEvent(startEv);\n } catch {}\n // 回调 onTransformStart(与事件同步)\n if (this.onTransformStartCb) {\n try {\n this.onTransformStartCb({\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n });\n } catch {}\n }\n }\n const pts = this.getTouches(ev);\n if (pts.length < 2) return;\n const p1 = pts[0]!;\n const p2 = pts[1]!;\n const mid = this.midpoint(p1, p2);\n const dist = this.distance(p1, p2);\n // 记录当前世界坐标(保持手指下点不漂移)\n const worldX = (mid.x - this.tx) / this.scale;\n const worldY = (mid.y - this.ty) / this.scale;\n this.gesture = {\n startMid: mid,\n startDist: Math.max(1, dist),\n startScale: this.scale,\n startTx: this.tx,\n startTy: this.ty,\n worldAtMid: { x: worldX, y: worldY },\n };\n }\n }\n\n private onTouchMove(ev: TouchEvent) {\n if (!this.gesture) {\n // 若用户在 move 时直接形成双指(某些设备上可能发生),尝试启动\n if (ev.touches.length >= 2) {\n this.onTouchStart(ev);\n }\n return;\n }\n if (ev.touches.length < 2) return;\n ev.preventDefault();\n\n const pts = this.getTouches(ev);\n if (pts.length < 2) return;\n const p1 = pts[0]!;\n const p2 = pts[1]!;\n const mid = this.midpoint(p1, p2);\n const dist = this.distance(p1, p2);\n\n const g = this.gesture;\n const targetScale = this.clampScale(g.startScale * (dist / g.startDist));\n const newTx = mid.x - targetScale * g.worldAtMid.x;\n const newTy = mid.y - targetScale * g.worldAtMid.y;\n\n this.scale = targetScale;\n this.tx = newTx;\n this.ty = newTy;\n this.applyTransform();\n }\n\n private onTouchEnd(ev: TouchEvent) {\n // 双指结束即结束会话\n if (ev.touches.length < 2) {\n this.gesture = null;\n this.inContainerGesture = false;\n if (this.transforming) {\n this.transforming = false;\n if (this.transformEndTimer) {\n clearTimeout(this.transformEndTimer);\n this.transformEndTimer = null;\n }\n try {\n const endEv = new CustomEvent(\"infiniteCanvas:transformEnd\", {\n detail: {\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n },\n bubbles: true,\n });\n this.container.dispatchEvent(endEv);\n } catch {}\n // 回调 onTransformEnd(立即)\n if (this.onTransformEndCb) {\n try {\n this.onTransformEndCb({\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n });\n } catch {}\n }\n }\n }\n }\n\n // 轨迹板/鼠标:两指滚动 => 平移;Ctrl/⌘ + 滚轮 => 缩放(Chrome/Edge)\n private onWheel(ev: WheelEvent) {\n if (!this.container.contains(ev.target as Node)) {\n return;\n }\n // 若用户进行浏览器级缩放(有的浏览器仍会在 ctrlKey=true 时派发 wheel),拦截并转为容器缩放\n if (ev.ctrlKey) {\n ev.preventDefault();\n if (!this.transforming) {\n this.transforming = true;\n try {\n const startEv = new CustomEvent(\"infiniteCanvas:transformStart\", {\n detail: {\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n },\n bubbles: true,\n });\n this.container.dispatchEvent(startEv);\n } catch {}\n if (this.onTransformStartCb) {\n try {\n this.onTransformStartCb({\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n });\n } catch {}\n }\n }\n // 以鼠标位置为锚点缩放\n const anchorX = (ev.clientX - this.tx) / this.scale;\n const anchorY = (ev.clientY - this.ty) / this.scale;\n // 指数缩放更平滑;deltaY>0 => 缩小\n const factor = Math.exp(-ev.deltaY * 0.002);\n const nextScale = this.clampScale(this.scale * factor);\n // 调整平移:保持锚点在屏幕坐标不动\n this.tx = ev.clientX - nextScale * anchorX;\n this.ty = ev.clientY - nextScale * anchorY;\n this.scale = nextScale;\n this.applyTransform();\n return;\n }\n // 普通两指滚动 => 平移\n ev.preventDefault();\n if (!this.transforming) {\n this.transforming = true;\n try {\n const startEv = new CustomEvent(\"infiniteCanvas:transformStart\", {\n detail: {\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n },\n bubbles: true,\n });\n this.container.dispatchEvent(startEv);\n } catch {}\n if (this.onTransformStartCb) {\n try {\n this.onTransformStartCb({\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n });\n } catch {}\n }\n }\n // 屏蔽浏览器在容器内的页面滚动,将 delta 直接转为画布平移\n this.tx -= ev.deltaX;\n this.ty -= ev.deltaY;\n this.applyTransform();\n }\n\n // iOS Safari 的非标准手势事件:阻止页面级缩放\n private onGesture(e: Event) {\n // Safari (macOS/iOS) 非标准手势。若事件发生在容器或其后代上,则阻止页面级缩放。\n const target = e.target as Node | null;\n const inContainer = !!(target && this.container.contains(target as Node));\n if (inContainer || this.inContainerGesture) {\n // @ts-ignore\n if (typeof (e as any).preventDefault === \"function\")\n (e as any).preventDefault();\n }\n }\n}\n"],"names":["InfiniteCanvas","options","container","initialScale","minScale","maxScale","initialTranslate","clampOverflowHidden","contentClassName","autoFit","autoRefitOnResize","onReady","onTransform","onTransformStart","onTransformEnd","content","children","n","af","fit","schedule","s","p","base","params","c","cw","ch","cRect","s0","minLX","minTY","maxRX","maxBY","all","el","r","lx","ty","rx","by","w0","h0","h","mode","alignX","alignY","pad","availW","availH","sx","sy","scaledW","scaledH","baseX","baseY","x","y","norm","ev","hit","t","ct","tt","endEv","arr","i","touch","a","b","dx","dy","startEv","pts","p1","p2","mid","dist","worldX","worldY","g","targetScale","newTx","newTy","anchorX","anchorY","factor","nextScale","e","target"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(T,w){typeof exports=="object"&&typeof module<"u"?w(exports):typeof define=="function"&&define.amd?define(["exports"],w):(T=typeof globalThis<"u"?globalThis:T||self,w(T.InfiniteCanvas={}))})(this,function(T){"use strict";class w{constructor(t){this.scale=1,this.tx=0,this.ty=0,this.destroyed=!1,this.transforming=!1,this.transformEndTimer=null,this.refitTimer=null,this.autoFitPreset=null,this.gesture=null,this.inContainerGesture=!1;const{container:e,initialScale:i=1,minScale:n=.2,maxScale:s=6,initialTranslate:r={x:0,y:0},clampOverflowHidden:h=!0,contentClassName:o,autoFit:a,autoRefitOnResize:f=!1,onReady:y,onTransform:E,onTransformStart:p,onTransformEnd:g}=t;if(!e)throw new Error("InfiniteCanvas: container is required");this.container=e,this.scale=i,this.tx=r.x,this.ty=r.y,this.minScale=Math.min(n,s),this.maxScale=Math.max(n,s),this.onTransformCb=E,this.onTransformStartCb=p,this.onTransformEndCb=g;const c=document.createElement("div");c.style.position="relative",c.style.transformOrigin="0 0",c.style.willChange="transform",o&&(c.className=o);const x=[];for(;e.firstChild;)x.push(e.firstChild),e.removeChild(e.firstChild);if(x.forEach(d=>c.appendChild(d)),e.appendChild(c),this.content=c,e.style.touchAction="none",e.style.overscrollBehavior=e.style.overscrollBehavior||"none",h&&(e.style.overflow||(e.style.overflow="hidden")),a){const d=this.normalizeFitParams(a),l=this.computeFitTransform(d);l&&(this.scale=this.clampScale(l.scale),this.tx=l.x,this.ty=l.y,this.autoFitPreset=d)}if(this.applyTransform(),this.autoFitPreset&&f){const d=()=>{this.refitTimer&&clearTimeout(this.refitTimer),this.refitTimer=setTimeout(()=>{this.fitToView(this.autoFitPreset||void 0)},100)};try{this.resizeObserver=new ResizeObserver(()=>d()),this.resizeObserver.observe(this.container)}catch{}this.resizeHandler=d,window.addEventListener("resize",this.resizeHandler)}this.onTouchStart=this.onTouchStart.bind(this),this.onTouchMove=this.onTouchMove.bind(this),this.onTouchEnd=this.onTouchEnd.bind(this),this.onWheel=this.onWheel.bind(this),this.onGesture=this.onGesture.bind(this),this.onWindowTouchStart=this._windowTouchStart.bind(this),this.onWindowTouchMove=this._windowTouchMove.bind(this),this.onWindowTouchEnd=this._windowTouchEnd.bind(this),this.onWindowWheel=this._windowWheel.bind(this);try{getComputedStyle(e).position==="static"&&(e.style.position="relative")}catch{}if(window.addEventListener("touchstart",this.onWindowTouchStart,{passive:!1,capture:!0}),window.addEventListener("touchmove",this.onWindowTouchMove,{passive:!1,capture:!0}),window.addEventListener("touchend",this.onWindowTouchEnd,{passive:!1,capture:!0}),window.addEventListener("touchcancel",this.onWindowTouchEnd,{passive:!1,capture:!0}),window.addEventListener("wheel",this.onWindowWheel,{passive:!1,capture:!0}),window.addEventListener("gesturestart",this.onGesture,{passive:!1,capture:!0}),window.addEventListener("gesturechange",this.onGesture,{passive:!1,capture:!0}),window.addEventListener("gestureend",this.onGesture,{passive:!1,capture:!0}),typeof y=="function")try{y(this.content)}catch{}}destroy(){if(!this.destroyed){if(this.destroyed=!0,this.container,window.removeEventListener("touchstart",this.onWindowTouchStart,!0),window.removeEventListener("touchmove",this.onWindowTouchMove,!0),window.removeEventListener("touchend",this.onWindowTouchEnd,!0),window.removeEventListener("touchcancel",this.onWindowTouchEnd,!0),window.removeEventListener("wheel",this.onWindowWheel,!0),window.removeEventListener("gesturestart",this.onGesture,!0),window.removeEventListener("gesturechange",this.onGesture,!0),window.removeEventListener("gestureend",this.onGesture,!0),this.resizeObserver){try{this.resizeObserver.disconnect()}catch{}this.resizeObserver=void 0}this.resizeHandler&&(window.removeEventListener("resize",this.resizeHandler),this.resizeHandler=void 0)}}getTransform(){return{scale:this.scale,x:this.tx,y:this.ty}}setTransform(t){typeof t.scale=="number"&&(this.scale=this.clampScale(t.scale)),typeof t.x=="number"&&(this.tx=t.x),typeof t.y=="number"&&(this.ty=t.y),this.applyTransform()}reset(){this.scale=1,this.tx=0,this.ty=0,this.applyTransform()}clampScale(t){return Math.min(this.maxScale,Math.max(this.minScale,t))}normalizeFitParams(t){const e=typeof t=="object"?t:{mode:"contain",alignX:"center",alignY:"center",padding:0};return{mode:e.mode||"contain",alignX:e.alignX||"center",alignY:e.alignY||"center",padding:Math.max(0,e.padding??0)}}computeFitTransform(t){const e=this.container,i=this.content,n=Math.max(0,e.clientWidth),s=Math.max(0,e.clientHeight);if(n===0||s===0)return null;const r=i.getBoundingClientRect(),h=this.scale||1;let o=Number.POSITIVE_INFINITY,a=Number.POSITIVE_INFINITY,f=Number.NEGATIVE_INFINITY,y=Number.NEGATIVE_INFINITY;const E=Array.from(i.querySelectorAll("*"));for(const v of E){const m=v.getBoundingClientRect();if(m.width<=0||m.height<=0)continue;const Y=(m.left-r.left)/h,F=(m.top-r.top)/h,L=(m.right-r.left)/h,X=(m.bottom-r.top)/h;Y<o&&(o=Y),F<a&&(a=F),L>f&&(f=L),X>y&&(y=X)}if(!isFinite(o)||!isFinite(a)||!isFinite(f)||!isFinite(y)){const v=Math.max(i.scrollWidth,i.offsetWidth,i.clientWidth),m=Math.max(i.scrollHeight,i.offsetHeight,i.clientHeight);if(v===0||m===0)return null;o=0,a=0,f=v,y=m}const p=Math.max(0,f-o),g=Math.max(0,y-a),c=(t==null?void 0:t.mode)??"contain",x=(t==null?void 0:t.alignX)??"center",d=(t==null?void 0:t.alignY)??"center",l=Math.max(0,(t==null?void 0:t.padding)??0),_=Math.max(0,n-l*2),z=Math.max(0,s-l*2),b=_/p,C=z/g;let u=1;c==="width"?u=b:c==="height"?u=C:c==="cover"?u=Math.max(b,C):u=Math.min(b,C),u=this.clampScale(u);const W=p*u,I=g*u;let S=l,M=l;x==="center"?S=Math.round((n-W)/2):x==="right"&&(S=Math.round(n-W-l)),d==="center"?M=Math.round((s-I)/2):d==="bottom"&&(M=Math.round(s-I-l));const N=S-Math.round(o*u),P=M-Math.round(a*u);return{scale:u,x:N,y:P}}fitToView(t){const e=this.normalizeFitParams(t||{mode:"contain"}),i=this.computeFitTransform(e);i&&(this.scale=this.clampScale(i.scale),this.tx=i.x,this.ty=i.y,this.autoFitPreset=e,this.applyTransform())}_noop(){}_isPointInContainer(t,e){const i=this.container.getBoundingClientRect();return t>=i.left&&t<=i.right&&e>=i.top&&e<=i.bottom}_windowTouchStart(t){if(t.touches.length===0)return;let e=!1;for(let i=0;i<t.touches.length;i++){const n=t.touches.item?t.touches.item(i):t.touches[i];if(n&&this._isPointInContainer(n.clientX,n.clientY)){e=!0;break}}e&&this.onTouchStart(t)}_windowTouchMove(t){if(t.touches.length===0)return;let e=!1;for(let i=0;i<t.touches.length;i++){const n=t.touches.item?t.touches.item(i):t.touches[i];if(n&&this._isPointInContainer(n.clientX,n.clientY)){e=!0;break}}e&&this.onTouchMove(t)}_windowTouchEnd(t){const e=t.changedTouches;if(e&&e.length>0){const i=e.item?e.item(0):e[0];if(i&&!this._isPointInContainer(i.clientX,i.clientY))return}this.onTouchEnd(t)}_windowWheel(t){this._isPointInContainer(t.clientX,t.clientY)&&this.onWheel(t)}applyTransform(){if(this.content.style.transform=`translate(${this.tx}px, ${this.ty}px) scale(${this.scale})`,this.onTransformCb)try{this.onTransformCb({scale:this.scale,x:this.tx,y:this.ty,content:this.content})}catch{}try{const t=new CustomEvent("infiniteCanvas:transform",{detail:{scale:this.scale,x:this.tx,y:this.ty,content:this.content},bubbles:!0});this.container.dispatchEvent(t)}catch{}this.transforming&&(this.transformEndTimer&&clearTimeout(this.transformEndTimer),this.transformEndTimer=setTimeout(()=>{this.transforming=!1;try{const t=new CustomEvent("infiniteCanvas:transformEnd",{detail:{scale:this.scale,x:this.tx,y:this.ty,content:this.content},bubbles:!0});this.container.dispatchEvent(t)}catch{}if(this.onTransformEndCb)try{this.onTransformEndCb({scale:this.scale,x:this.tx,y:this.ty,content:this.content})}catch{}},120))}getTouches(t){const e=t.touches,i=[];for(let n=0;n<e.length;n++){const s=e.item?e.item(n):e[n];s&&i.push({x:s.clientX,y:s.clientY})}return i}midpoint(t,e){return{x:(t.x+e.x)/2,y:(t.y+e.y)/2}}distance(t,e){const i=t.x-e.x,n=t.y-e.y;return Math.hypot(i,n)}onTouchStart(t){if(t.touches.length>=2){if(t.preventDefault(),this.inContainerGesture=!0,!this.transforming){this.transforming=!0;try{const a=new CustomEvent("infiniteCanvas:transformStart",{detail:{scale:this.scale,x:this.tx,y:this.ty,content:this.content},bubbles:!0});this.container.dispatchEvent(a)}catch{}if(this.onTransformStartCb)try{this.onTransformStartCb({scale:this.scale,x:this.tx,y:this.ty,content:this.content})}catch{}}const e=this.getTouches(t);if(e.length<2)return;const i=e[0],n=e[1],s=this.midpoint(i,n),r=this.distance(i,n),h=(s.x-this.tx)/this.scale,o=(s.y-this.ty)/this.scale;this.gesture={startMid:s,startDist:Math.max(1,r),startScale:this.scale,startTx:this.tx,startTy:this.ty,worldAtMid:{x:h,y:o}}}}onTouchMove(t){if(!this.gesture){t.touches.length>=2&&this.onTouchStart(t);return}if(t.touches.length<2)return;t.preventDefault();const e=this.getTouches(t);if(e.length<2)return;const i=e[0],n=e[1],s=this.midpoint(i,n),r=this.distance(i,n),h=this.gesture,o=this.clampScale(h.startScale*(r/h.startDist)),a=s.x-o*h.worldAtMid.x,f=s.y-o*h.worldAtMid.y;this.scale=o,this.tx=a,this.ty=f,this.applyTransform()}onTouchEnd(t){if(t.touches.length<2&&(this.gesture=null,this.inContainerGesture=!1,this.transforming)){this.transforming=!1,this.transformEndTimer&&(clearTimeout(this.transformEndTimer),this.transformEndTimer=null);try{const e=new CustomEvent("infiniteCanvas:transformEnd",{detail:{scale:this.scale,x:this.tx,y:this.ty,content:this.content},bubbles:!0});this.container.dispatchEvent(e)}catch{}if(this.onTransformEndCb)try{this.onTransformEndCb({scale:this.scale,x:this.tx,y:this.ty,content:this.content})}catch{}}}onWheel(t){if(this.container.contains(t.target)){if(t.ctrlKey){if(t.preventDefault(),!this.transforming){this.transforming=!0;try{const r=new CustomEvent("infiniteCanvas:transformStart",{detail:{scale:this.scale,x:this.tx,y:this.ty,content:this.content},bubbles:!0});this.container.dispatchEvent(r)}catch{}if(this.onTransformStartCb)try{this.onTransformStartCb({scale:this.scale,x:this.tx,y:this.ty,content:this.content})}catch{}}const e=(t.clientX-this.tx)/this.scale,i=(t.clientY-this.ty)/this.scale,n=Math.exp(-t.deltaY*.002),s=this.clampScale(this.scale*n);this.tx=t.clientX-s*e,this.ty=t.clientY-s*i,this.scale=s,this.applyTransform();return}if(t.preventDefault(),!this.transforming){this.transforming=!0;try{const e=new CustomEvent("infiniteCanvas:transformStart",{detail:{scale:this.scale,x:this.tx,y:this.ty,content:this.content},bubbles:!0});this.container.dispatchEvent(e)}catch{}if(this.onTransformStartCb)try{this.onTransformStartCb({scale:this.scale,x:this.tx,y:this.ty,content:this.content})}catch{}}this.tx-=t.deltaX,this.ty-=t.deltaY,this.applyTransform()}}onGesture(t){const e=t.target;(!!(e&&this.container.contains(e))||this.inContainerGesture)&&typeof t.preventDefault=="function"&&t.preventDefault()}}T.InfiniteCanvas=w,Object.defineProperty(T,Symbol.toStringTag,{value:"Module"})});
|
|
2
|
+
//# sourceMappingURL=infinite-canvas.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"infinite-canvas.umd.js","sources":["../src/InfiniteCanvas.ts"],"sourcesContent":["/**\n * InfiniteCanvas\n *\n * 在指定 HTML 容器内启用双指缩放与双指平移(触摸手势)。\n * 会将容器现有子节点搬运到一个内部 content 容器中,并对该 content 施加 CSS 变换。\n */\nexport type InfiniteCanvasOptions = {\n container: HTMLElement; // 容器节点(必须)\n // 初始缩放\n initialScale?: number;\n // 允许缩放边界\n minScale?: number;\n maxScale?: number;\n // 初始平移\n initialTranslate?: { x: number; y: number };\n // 是否强制为容器添加 overflow: hidden\n clampOverflowHidden?: boolean;\n // 为内部 content 添加的类名(可用于样式定制)\n contentClassName?: string;\n // 初始化时自动“铺满可见”(默认不启用)。\n // - true 等价于 { mode: 'contain', alignX: 'center', alignY: 'center', padding: 0 }\n // - mode:\n // 'contain' => 完全展示(按较小比例缩放,使内容完整可见)\n // 'width' => 以宽度适配\n // 'height' => 以高度适配\n // - 对齐:alignX: left|center|right;alignY: top|center|bottom\n // - padding: 留白(px)\n autoFit?:\n | boolean\n | {\n mode?: \"contain\" | \"width\" | \"height\" | \"cover\";\n alignX?: \"left\" | \"center\" | \"right\";\n alignY?: \"top\" | \"center\" | \"bottom\";\n padding?: number;\n };\n // 尺寸变化时自动重新适配(基于最近一次 fitToView/autoFit 的参数)\n autoRefitOnResize?: boolean;\n // 初始化完成回调:参数为包装后的 content 节点\n onReady?: (content: HTMLDivElement) => void;\n // 每次变换后的回调(缩放/平移),入参为当前变换与 content 节点\n onTransform?: (state: {\n scale: number;\n x: number;\n y: number;\n content: HTMLDivElement;\n }) => void;\n // 变换开始回调(触摸双指/滚轮缩放/平移刚开始时触发一次)\n onTransformStart?: (state: {\n scale: number;\n x: number;\n y: number;\n content: HTMLDivElement;\n }) => void;\n // 变换结束回调(松手或滚轮/平移停止后的短暂去抖后触发一次)\n onTransformEnd?: (state: {\n scale: number;\n x: number;\n y: number;\n content: HTMLDivElement;\n }) => void;\n};\n\nexport class InfiniteCanvas {\n private container: HTMLElement;\n private content: HTMLDivElement;\n private scale = 1;\n private tx = 0;\n private ty = 0;\n private minScale: number;\n private maxScale: number;\n private destroyed = false;\n private onTransformCb?: (state: {\n scale: number;\n x: number;\n y: number;\n content: HTMLDivElement;\n }) => void;\n private onTransformStartCb?: (state: {\n scale: number;\n x: number;\n y: number;\n content: HTMLDivElement;\n }) => void;\n private onTransformEndCb?: (state: {\n scale: number;\n x: number;\n y: number;\n content: HTMLDivElement;\n }) => void;\n private transforming = false;\n private transformEndTimer: any = null;\n private refitTimer: any = null;\n private autoFitPreset: {\n mode: \"contain\" | \"width\" | \"height\" | \"cover\";\n alignX: \"left\" | \"center\" | \"right\";\n alignY: \"top\" | \"center\" | \"bottom\";\n padding: number;\n } | null = null;\n private resizeObserver?: ResizeObserver;\n private resizeHandler?: () => void;\n // 捕获手势的透明命中图层\n private hitLayer!: HTMLDivElement;\n\n // 手势状态\n private gesture: null | {\n startMid: { x: number; y: number };\n startDist: number;\n startScale: number;\n startTx: number;\n startTy: number;\n worldAtMid: { x: number; y: number };\n } = null;\n private inContainerGesture = false; // 是否在容器内发起的多指手势(用于 iOS gesture 事件拦截)\n // 窗口级捕获:方法声明\n private onWindowTouchStart?: (ev: TouchEvent) => void;\n private onWindowTouchMove?: (ev: TouchEvent) => void;\n private onWindowTouchEnd?: (ev: TouchEvent) => void;\n private onWindowWheel?: (ev: WheelEvent) => void;\n\n constructor(options: InfiniteCanvasOptions) {\n const {\n container,\n initialScale = 1,\n minScale = 0.2,\n maxScale = 6,\n initialTranslate = { x: 0, y: 0 },\n clampOverflowHidden = true,\n contentClassName,\n autoFit,\n autoRefitOnResize = false,\n onReady,\n onTransform,\n onTransformStart,\n onTransformEnd,\n } = options;\n\n if (!container) throw new Error(\"InfiniteCanvas: container is required\");\n this.container = container;\n this.scale = initialScale;\n this.tx = initialTranslate.x;\n this.ty = initialTranslate.y;\n this.minScale = Math.min(minScale, maxScale);\n this.maxScale = Math.max(minScale, maxScale);\n this.onTransformCb = onTransform;\n this.onTransformStartCb = onTransformStart;\n this.onTransformEndCb = onTransformEnd;\n\n // 包装现有子节点\n const content = document.createElement(\"div\");\n content.style.position = \"relative\";\n content.style.transformOrigin = \"0 0\";\n content.style.willChange = \"transform\";\n if (contentClassName) content.className = contentClassName;\n\n // 将容器的现有子节点搬到 content 中\n const children: Node[] = [];\n while (container.firstChild) {\n children.push(container.firstChild);\n container.removeChild(container.firstChild);\n }\n children.forEach((n) => content.appendChild(n));\n container.appendChild(content);\n this.content = content;\n\n // 基础样式与交互策略\n container.style.touchAction = \"none\"; // 阻止默认触摸手势(Pointer 事件)\n // 避免容器处发生浏览器滚动产生的回弹/页面滚动\n (container.style as any).overscrollBehavior =\n (container.style as any).overscrollBehavior || \"none\";\n if (clampOverflowHidden) {\n if (!container.style.overflow) container.style.overflow = \"hidden\";\n }\n\n // 若启用 autoFit,计算并设置初始 transform\n if (autoFit) {\n const af = this.normalizeFitParams(autoFit);\n const fit = this.computeFitTransform(af);\n if (fit) {\n this.scale = this.clampScale(fit.scale);\n this.tx = fit.x;\n this.ty = fit.y;\n // 记录预设,便于后续 autoRefitOnResize 重算\n this.autoFitPreset = af;\n }\n }\n this.applyTransform();\n\n // 自动重算:容器或窗口尺寸变化\n if (this.autoFitPreset && autoRefitOnResize) {\n const schedule = () => {\n if (this.refitTimer) clearTimeout(this.refitTimer);\n this.refitTimer = setTimeout(() => {\n this.fitToView(this.autoFitPreset || undefined);\n }, 100);\n };\n try {\n this.resizeObserver = new ResizeObserver(() => schedule());\n this.resizeObserver.observe(this.container);\n } catch {}\n this.resizeHandler = schedule;\n window.addEventListener(\"resize\", this.resizeHandler);\n }\n\n // 事件\n this.onTouchStart = this.onTouchStart.bind(this);\n this.onTouchMove = this.onTouchMove.bind(this);\n this.onTouchEnd = this.onTouchEnd.bind(this);\n this.onWheel = this.onWheel.bind(this);\n this.onGesture = this.onGesture.bind(this);\n // 窗口级捕获监听(绑定 this)\n this.onWindowTouchStart = this._windowTouchStart.bind(this);\n this.onWindowTouchMove = this._windowTouchMove.bind(this);\n this.onWindowTouchEnd = this._windowTouchEnd.bind(this);\n this.onWindowWheel = this._windowWheel.bind(this);\n\n // 命中图层:覆盖容器区域,专门接收触摸/滚轮手势\n try {\n if (getComputedStyle(container).position === \"static\") {\n // 确保绝对定位子元素可覆盖\n container.style.position = \"relative\";\n }\n } catch {}\n // 窗口级捕获:在任何层级之上先拿到事件,再判定是否命中容器\n window.addEventListener(\n \"touchstart\",\n this.onWindowTouchStart as any,\n {\n passive: false,\n capture: true,\n } as any\n );\n window.addEventListener(\n \"touchmove\",\n this.onWindowTouchMove as any,\n {\n passive: false,\n capture: true,\n } as any\n );\n window.addEventListener(\n \"touchend\",\n this.onWindowTouchEnd as any,\n {\n passive: false,\n capture: true,\n } as any\n );\n window.addEventListener(\n \"touchcancel\",\n this.onWindowTouchEnd as any,\n {\n passive: false,\n capture: true,\n } as any\n );\n window.addEventListener(\n \"wheel\",\n this.onWindowWheel as any,\n {\n passive: false,\n capture: true,\n } as any\n );\n // iOS Safari 专有:阻止页面级捏合缩放\n // 使用 window 级捕获,保证能先于页面处理,且仅当手势在容器内发起时拦截\n window.addEventListener(\n \"gesturestart\" as any,\n this.onGesture as any,\n { passive: false, capture: true } as any\n );\n window.addEventListener(\n \"gesturechange\" as any,\n this.onGesture as any,\n { passive: false, capture: true } as any\n );\n window.addEventListener(\n \"gestureend\" as any,\n this.onGesture as any,\n { passive: false, capture: true } as any\n );\n // 构造结束:通知外部已就绪\n if (typeof onReady === \"function\") {\n try {\n onReady(this.content);\n } catch {}\n }\n }\n\n // 对外 API\n public destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n const c = this.container;\n // 清理窗口捕获监听\n window.removeEventListener(\n \"touchstart\" as any,\n this.onWindowTouchStart as any,\n true as any\n );\n window.removeEventListener(\n \"touchmove\" as any,\n this.onWindowTouchMove as any,\n true as any\n );\n window.removeEventListener(\n \"touchend\" as any,\n this.onWindowTouchEnd as any,\n true as any\n );\n window.removeEventListener(\n \"touchcancel\" as any,\n this.onWindowTouchEnd as any,\n true as any\n );\n window.removeEventListener(\n \"wheel\" as any,\n this.onWindowWheel as any,\n true as any\n );\n window.removeEventListener(\n \"gesturestart\" as any,\n this.onGesture as any,\n true as any\n );\n window.removeEventListener(\n \"gesturechange\" as any,\n this.onGesture as any,\n true as any\n );\n window.removeEventListener(\n \"gestureend\" as any,\n this.onGesture as any,\n true as any\n );\n if (this.resizeObserver) {\n try {\n this.resizeObserver.disconnect();\n } catch {}\n this.resizeObserver = undefined;\n }\n if (this.resizeHandler) {\n window.removeEventListener(\"resize\", this.resizeHandler);\n this.resizeHandler = undefined;\n }\n // 不自动还原 DOM 结构,避免破坏外部状态;如需,还可提供 resetDOM()\n }\n\n public getTransform() {\n return { scale: this.scale, x: this.tx, y: this.ty };\n }\n\n public setTransform(t: { scale?: number; x?: number; y?: number }) {\n if (typeof t.scale === \"number\") this.scale = this.clampScale(t.scale);\n if (typeof t.x === \"number\") this.tx = t.x;\n if (typeof t.y === \"number\") this.ty = t.y;\n this.applyTransform();\n }\n\n public reset() {\n this.scale = 1;\n this.tx = 0;\n this.ty = 0;\n this.applyTransform();\n }\n\n // 内部工具\n private clampScale(s: number) {\n return Math.min(this.maxScale, Math.max(this.minScale, s));\n }\n\n private normalizeFitParams(\n p:\n | boolean\n | {\n mode?: \"contain\" | \"width\" | \"height\" | \"cover\";\n alignX?: \"left\" | \"center\" | \"right\";\n alignY?: \"top\" | \"center\" | \"bottom\";\n padding?: number;\n }\n ) {\n const base =\n typeof p === \"object\"\n ? p\n : {\n mode: \"contain\" as const,\n alignX: \"center\" as const,\n alignY: \"center\" as const,\n padding: 0,\n };\n return {\n mode: (base.mode || \"contain\") as\n | \"contain\"\n | \"width\"\n | \"height\"\n | \"cover\",\n alignX: (base.alignX || \"center\") as \"left\" | \"center\" | \"right\",\n alignY: (base.alignY || \"center\") as \"top\" | \"center\" | \"bottom\",\n padding: Math.max(0, base.padding ?? 0),\n };\n }\n\n // 计算使内容按给定策略适配到容器可见区域的变换(不应用)\n public computeFitTransform(params?: {\n mode?: \"contain\" | \"width\" | \"height\" | \"cover\";\n alignX?: \"left\" | \"center\" | \"right\";\n alignY?: \"top\" | \"center\" | \"bottom\";\n padding?: number;\n }): { scale: number; x: number; y: number } | null {\n const c = this.container;\n const content = this.content;\n const cw = Math.max(0, c.clientWidth);\n const ch = Math.max(0, c.clientHeight);\n if (cw === 0 || ch === 0) return null;\n // 测量内容固有尺寸(未考虑当前 transform),以 content 本地坐标计算其后代并集\n const cRect = content.getBoundingClientRect();\n const s0 = this.scale || 1; // 当前缩放,用于反算本地尺寸\n let minLX = Number.POSITIVE_INFINITY;\n let minTY = Number.POSITIVE_INFINITY;\n let maxRX = Number.NEGATIVE_INFINITY;\n let maxBY = Number.NEGATIVE_INFINITY;\n const all = Array.from(content.querySelectorAll(\"*\")) as Element[];\n for (const el of all) {\n const r = el.getBoundingClientRect();\n if (r.width <= 0 || r.height <= 0) continue;\n const lx = (r.left - cRect.left) / s0;\n const ty = (r.top - cRect.top) / s0;\n const rx = (r.right - cRect.left) / s0;\n const by = (r.bottom - cRect.top) / s0;\n if (lx < minLX) minLX = lx;\n if (ty < minTY) minTY = ty;\n if (rx > maxRX) maxRX = rx;\n if (by > maxBY) maxBY = by;\n }\n // 若集合为空,则退回使用 content 自身尺寸\n if (\n !isFinite(minLX) ||\n !isFinite(minTY) ||\n !isFinite(maxRX) ||\n !isFinite(maxBY)\n ) {\n const w0 = Math.max(\n content.scrollWidth,\n content.offsetWidth,\n content.clientWidth\n );\n const h0 = Math.max(\n content.scrollHeight,\n content.offsetHeight,\n content.clientHeight\n );\n if (w0 === 0 || h0 === 0) return null;\n minLX = 0;\n minTY = 0;\n maxRX = w0;\n maxBY = h0;\n }\n const w = Math.max(0, maxRX - minLX);\n const h = Math.max(0, maxBY - minTY);\n\n const mode = params?.mode ?? \"contain\";\n const alignX = params?.alignX ?? \"center\";\n const alignY = params?.alignY ?? \"center\";\n const pad = Math.max(0, params?.padding ?? 0);\n const availW = Math.max(0, cw - pad * 2);\n const availH = Math.max(0, ch - pad * 2);\n\n const sx = availW / w;\n const sy = availH / h;\n let s = 1;\n if (mode === \"width\") s = sx;\n else if (mode === \"height\") s = sy;\n else if (mode === \"cover\") s = Math.max(sx, sy); // 铺满容器,可能裁切\n else s = Math.min(sx, sy); // contain 完全展示\n s = this.clampScale(s);\n\n const scaledW = w * s;\n const scaledH = h * s;\n // 先计算对齐后的“目标可视区域左上角”\n let baseX = pad;\n let baseY = pad;\n // 水平对齐\n if (alignX === \"center\") baseX = Math.round((cw - scaledW) / 2);\n else if (alignX === \"right\") baseX = Math.round(cw - scaledW - pad);\n // 垂直对齐\n if (alignY === \"center\") baseY = Math.round((ch - scaledH) / 2);\n else if (alignY === \"bottom\") baseY = Math.round(ch - scaledH - pad);\n // 将并集框的左上角(minLX,minTY)对齐到 baseX/baseY,因此内容需要额外平移 -minL*scale\n const x = baseX - Math.round(minLX * s);\n const y = baseY - Math.round(minTY * s);\n return { scale: s, x, y };\n }\n\n // 应用适配(基于 computeFitTransform 计算结果)\n public fitToView(params?: {\n mode?: \"contain\" | \"width\" | \"height\" | \"cover\";\n alignX?: \"left\" | \"center\" | \"right\";\n alignY?: \"top\" | \"center\" | \"bottom\";\n padding?: number;\n }) {\n const norm = this.normalizeFitParams(params || { mode: \"contain\" });\n const fit = this.computeFitTransform(norm);\n if (!fit) return;\n this.scale = this.clampScale(fit.scale);\n this.tx = fit.x;\n this.ty = fit.y;\n this.autoFitPreset = norm;\n this.applyTransform();\n }\n\n // 仅用于移除 window.resize 监听占位(便于彻底清理)\n private _noop() {}\n\n // 命中检测:点是否在容器可见区域内\n private _isPointInContainer(x: number, y: number) {\n const r = this.container.getBoundingClientRect();\n return x >= r.left && x <= r.right && y >= r.top && y <= r.bottom;\n }\n\n // 窗口级触摸/滚轮捕获:仅当命中容器时转发\n private _windowTouchStart(ev: TouchEvent) {\n if (ev.touches.length === 0) return;\n let hit = false;\n for (let i = 0; i < ev.touches.length; i++) {\n const t = ev.touches.item ? ev.touches.item(i) : (ev.touches as any)[i];\n if (!t) continue;\n if (this._isPointInContainer((t as any).clientX, (t as any).clientY)) {\n hit = true;\n break;\n }\n }\n if (!hit) return;\n this.onTouchStart(ev);\n }\n private _windowTouchMove(ev: TouchEvent) {\n if (ev.touches.length === 0) return;\n let hit = false;\n for (let i = 0; i < ev.touches.length; i++) {\n const t = ev.touches.item ? ev.touches.item(i) : (ev.touches as any)[i];\n if (!t) continue;\n if (this._isPointInContainer((t as any).clientX, (t as any).clientY)) {\n hit = true;\n break;\n }\n }\n if (!hit) return;\n this.onTouchMove(ev);\n }\n private _windowTouchEnd(ev: TouchEvent) {\n // end 没有 touches 命中点可用,尝试用 changedTouches 任一指头判断是否来源于容器内\n const ct = (ev as any).changedTouches as TouchList | undefined;\n if (ct && ct.length > 0) {\n const tt = (ct.item ? ct.item(0) : (ct as any)[0]) as any;\n if (tt && !this._isPointInContainer(tt.clientX, tt.clientY)) return;\n }\n this.onTouchEnd(ev);\n }\n private _windowWheel(ev: WheelEvent) {\n if (!this._isPointInContainer(ev.clientX, ev.clientY)) return;\n this.onWheel(ev);\n }\n\n private applyTransform() {\n this.content.style.transform = `translate(${this.tx}px, ${this.ty}px) scale(${this.scale})`;\n // 触发回调与事件,供外部(如圈选覆盖层)刷新\n if (this.onTransformCb) {\n try {\n this.onTransformCb({\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n });\n } catch {}\n }\n try {\n const ev = new CustomEvent(\"infiniteCanvas:transform\", {\n detail: {\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n },\n bubbles: true,\n });\n this.container.dispatchEvent(ev);\n } catch {}\n // 若处于变换中,启动/重置结束检测定时器\n if (this.transforming) {\n if (this.transformEndTimer) clearTimeout(this.transformEndTimer);\n this.transformEndTimer = setTimeout(() => {\n this.transforming = false;\n try {\n const endEv = new CustomEvent(\"infiniteCanvas:transformEnd\", {\n detail: {\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n },\n bubbles: true,\n });\n this.container.dispatchEvent(endEv);\n } catch {}\n // 回调 onTransformEnd(与事件同步)\n if (this.onTransformEndCb) {\n try {\n this.onTransformEndCb({\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n });\n } catch {}\n }\n }, 120);\n }\n }\n\n private getTouches(ev: TouchEvent) {\n const t = ev.touches;\n const arr: { x: number; y: number }[] = [];\n for (let i = 0; i < t.length; i++) {\n const touch = t.item ? t.item(i) : (t as any)[i];\n if (touch) arr.push({ x: touch.clientX, y: touch.clientY });\n }\n return arr;\n }\n\n private midpoint(a: { x: number; y: number }, b: { x: number; y: number }) {\n return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };\n }\n\n private distance(a: { x: number; y: number }, b: { x: number; y: number }) {\n const dx = a.x - b.x;\n const dy = a.y - b.y;\n return Math.hypot(dx, dy);\n }\n\n private onTouchStart(ev: TouchEvent) {\n // 双指起手\n if (ev.touches.length >= 2) {\n ev.preventDefault();\n this.inContainerGesture = true; // 在容器内开始多指\n // 广播开始\n if (!this.transforming) {\n this.transforming = true;\n try {\n const startEv = new CustomEvent(\"infiniteCanvas:transformStart\", {\n detail: {\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n },\n bubbles: true,\n });\n this.container.dispatchEvent(startEv);\n } catch {}\n // 回调 onTransformStart(与事件同步)\n if (this.onTransformStartCb) {\n try {\n this.onTransformStartCb({\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n });\n } catch {}\n }\n }\n const pts = this.getTouches(ev);\n if (pts.length < 2) return;\n const p1 = pts[0]!;\n const p2 = pts[1]!;\n const mid = this.midpoint(p1, p2);\n const dist = this.distance(p1, p2);\n // 记录当前世界坐标(保持手指下点不漂移)\n const worldX = (mid.x - this.tx) / this.scale;\n const worldY = (mid.y - this.ty) / this.scale;\n this.gesture = {\n startMid: mid,\n startDist: Math.max(1, dist),\n startScale: this.scale,\n startTx: this.tx,\n startTy: this.ty,\n worldAtMid: { x: worldX, y: worldY },\n };\n }\n }\n\n private onTouchMove(ev: TouchEvent) {\n if (!this.gesture) {\n // 若用户在 move 时直接形成双指(某些设备上可能发生),尝试启动\n if (ev.touches.length >= 2) {\n this.onTouchStart(ev);\n }\n return;\n }\n if (ev.touches.length < 2) return;\n ev.preventDefault();\n\n const pts = this.getTouches(ev);\n if (pts.length < 2) return;\n const p1 = pts[0]!;\n const p2 = pts[1]!;\n const mid = this.midpoint(p1, p2);\n const dist = this.distance(p1, p2);\n\n const g = this.gesture;\n const targetScale = this.clampScale(g.startScale * (dist / g.startDist));\n const newTx = mid.x - targetScale * g.worldAtMid.x;\n const newTy = mid.y - targetScale * g.worldAtMid.y;\n\n this.scale = targetScale;\n this.tx = newTx;\n this.ty = newTy;\n this.applyTransform();\n }\n\n private onTouchEnd(ev: TouchEvent) {\n // 双指结束即结束会话\n if (ev.touches.length < 2) {\n this.gesture = null;\n this.inContainerGesture = false;\n if (this.transforming) {\n this.transforming = false;\n if (this.transformEndTimer) {\n clearTimeout(this.transformEndTimer);\n this.transformEndTimer = null;\n }\n try {\n const endEv = new CustomEvent(\"infiniteCanvas:transformEnd\", {\n detail: {\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n },\n bubbles: true,\n });\n this.container.dispatchEvent(endEv);\n } catch {}\n // 回调 onTransformEnd(立即)\n if (this.onTransformEndCb) {\n try {\n this.onTransformEndCb({\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n });\n } catch {}\n }\n }\n }\n }\n\n // 轨迹板/鼠标:两指滚动 => 平移;Ctrl/⌘ + 滚轮 => 缩放(Chrome/Edge)\n private onWheel(ev: WheelEvent) {\n if (!this.container.contains(ev.target as Node)) {\n return;\n }\n // 若用户进行浏览器级缩放(有的浏览器仍会在 ctrlKey=true 时派发 wheel),拦截并转为容器缩放\n if (ev.ctrlKey) {\n ev.preventDefault();\n if (!this.transforming) {\n this.transforming = true;\n try {\n const startEv = new CustomEvent(\"infiniteCanvas:transformStart\", {\n detail: {\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n },\n bubbles: true,\n });\n this.container.dispatchEvent(startEv);\n } catch {}\n if (this.onTransformStartCb) {\n try {\n this.onTransformStartCb({\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n });\n } catch {}\n }\n }\n // 以鼠标位置为锚点缩放\n const anchorX = (ev.clientX - this.tx) / this.scale;\n const anchorY = (ev.clientY - this.ty) / this.scale;\n // 指数缩放更平滑;deltaY>0 => 缩小\n const factor = Math.exp(-ev.deltaY * 0.002);\n const nextScale = this.clampScale(this.scale * factor);\n // 调整平移:保持锚点在屏幕坐标不动\n this.tx = ev.clientX - nextScale * anchorX;\n this.ty = ev.clientY - nextScale * anchorY;\n this.scale = nextScale;\n this.applyTransform();\n return;\n }\n // 普通两指滚动 => 平移\n ev.preventDefault();\n if (!this.transforming) {\n this.transforming = true;\n try {\n const startEv = new CustomEvent(\"infiniteCanvas:transformStart\", {\n detail: {\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n },\n bubbles: true,\n });\n this.container.dispatchEvent(startEv);\n } catch {}\n if (this.onTransformStartCb) {\n try {\n this.onTransformStartCb({\n scale: this.scale,\n x: this.tx,\n y: this.ty,\n content: this.content,\n });\n } catch {}\n }\n }\n // 屏蔽浏览器在容器内的页面滚动,将 delta 直接转为画布平移\n this.tx -= ev.deltaX;\n this.ty -= ev.deltaY;\n this.applyTransform();\n }\n\n // iOS Safari 的非标准手势事件:阻止页面级缩放\n private onGesture(e: Event) {\n // Safari (macOS/iOS) 非标准手势。若事件发生在容器或其后代上,则阻止页面级缩放。\n const target = e.target as Node | null;\n const inContainer = !!(target && this.container.contains(target as Node));\n if (inContainer || this.inContainerGesture) {\n // @ts-ignore\n if (typeof (e as any).preventDefault === \"function\")\n (e as any).preventDefault();\n }\n }\n}\n"],"names":["InfiniteCanvas","options","container","initialScale","minScale","maxScale","initialTranslate","clampOverflowHidden","contentClassName","autoFit","autoRefitOnResize","onReady","onTransform","onTransformStart","onTransformEnd","content","children","n","af","fit","schedule","s","p","base","params","c","cw","ch","cRect","s0","minLX","minTY","maxRX","maxBY","all","el","r","lx","ty","rx","by","w0","h0","w","h","mode","alignX","alignY","pad","availW","availH","sx","sy","scaledW","scaledH","baseX","baseY","x","y","norm","ev","hit","t","ct","tt","endEv","arr","i","touch","a","b","dx","dy","startEv","pts","p1","p2","mid","dist","worldX","worldY","g","targetScale","newTx","newTy","anchorX","anchorY","factor","nextScale","e","target"],"mappings":"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"}
|