UNPKG

@joyday/vue-loop-scroll

Version:

A high-performance Vue component for loop scrolling, supporting large data sets, adaptive resizing, real-time data updates, and flexible scrolling controls.

452 lines (451 loc) 14.5 kB
import '../assets/main.css';var Te = Object.defineProperty; var Be = (s, t, n) => t in s ? Te(s, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : s[t] = n; var se = (s, t, n) => Be(s, typeof t != "symbol" ? t + "" : t, n); import { ref as K, computed as M, watch as $, toValue as We, onBeforeUnmount as re, defineComponent as _e, shallowRef as Ae, reactive as ae, onMounted as He, createElementBlock as j, createCommentVNode as De, openBlock as F, normalizeClass as Ee, createElementVNode as Le, normalizeStyle as Ne, Fragment as Ve, renderList as je, renderSlot as Fe, mergeProps as Ue, createTextVNode as Ke, toDisplayString as $e, nextTick as qe } from "vue"; class _ extends Error { constructor(n) { super(n); se(this, "cancelled"); this.cancelled = !0, Object.setPrototypeOf(this, _.prototype); } } const Xe = (s) => { let t = 0, n = !1; return { execute: async function(...f) { const y = t; n = !0; try { const T = await s.apply(this, f); if (y !== t) throw new _("Async operation cancelled"); return T; } finally { n = !1; } }, cancel: () => { n && (t++, n = !1); }, isCancellable: () => n, isCancelledError: (f) => f instanceof _ }; }, Ye = () => { const s = K(0); return { updateCounter: s, triggerUpdate: () => { s.value++; } }; }; function ie(s, t) { let n = null; const c = M(() => "ResizeObserver" in window), i = () => { n && (n.disconnect(), n = null); }, u = $( () => We(s), (f) => { i(), f && c.value && (n = new window.ResizeObserver(t), n.observe(f)); }, { immediate: !0, flush: "post" } ); return re(() => { u(), i(); }), { isSupported: c, stop: () => { u(); } }; } function U(s) { if (!(s != null && s.offsetParent)) return { width: 0, height: 0 }; const t = s.getBoundingClientRect(), n = window.getComputedStyle(s), c = (b) => parseFloat(b) || 0, i = c(n.borderLeftWidth) + c(n.borderRightWidth), u = c(n.borderTopWidth) + c(n.borderBottomWidth); return { width: t.width - i, // 宽度 = 总宽度 - 左右边框 height: t.height - u // 高度 = 总高度 - 上下边框 }; } let Je = 0; const Ge = () => String(Je++), Qe = (s, t, n = "forward") => { const c = s.length - 1; let i = 0; if (n === "forward") for (; i <= c; ) t(s[i], i), i++; else { let u = c; for (; u >= 0; ) t(s[u], u), u--; } }; function Ze(s) { const t = typeof s; return s != null && t === "object"; } const et = (s, t) => { const n = setTimeout(() => t == null ? void 0 : t(), s); return () => clearTimeout(n); }, q = /* @__PURE__ */ new WeakMap(); function tt(s, t) { let n = !0; const { resolve: c, promise: i } = ot(), b = et(s, () => { c(!1); }), f = Object.defineProperty(i, "isRunning", { get: () => n }); return q.set(f, () => { n && (n = !1, b(), c(!0)); }), i.finally(() => { n = !1, q.delete(f); }), f; } function nt(s) { var t; (t = q.get(s)) == null || t(); } function ot() { let s, t; return { promise: new Promise((c, i) => { s = c, t = i; }), resolve: s, reject: t }; } const st = ["data-uid", "data-key"], at = /* @__PURE__ */ _e({ __name: "index", props: { dataSource: {}, itemKey: {}, direction: { default: "up" }, speed: { default: 1 }, waitMode: { default: "item" }, waitTime: { default: 0 }, pausedOnHover: { type: Boolean, default: !0 }, loadCount: { default: 1 } }, setup(s) { const t = s, n = K(null), c = K(null), i = Ae([]); let u, b, f = []; const y = ae({ viewportWidth: 0, viewportHeight: 0, trackWidth: 0, trackHeight: 0 }), T = { scrollOffset: 0, // 当前滚动偏移量(像素) scrollOffsetInPageMode: 0, // page 模式下的滚动量 isScrolling: !1, // 滚动动画状态标识 isPaused: !1 // 是否暂停 }, d = ae({ ...T }), ce = () => { Object.assign(d, { ...T }); }, I = M(() => ["up", "down"].includes(t.direction)), x = M(() => ["up", "left"].includes(t.direction)), O = M( () => I.value ? y.viewportHeight : y.viewportWidth ), X = M( () => I.value ? y.trackHeight : y.trackWidth ), le = M(() => X.value > O.value), ue = M(() => { let e = d.scrollOffset; return x.value ? e *= -1 : e += O.value - X.value, { transform: `translate3D(${I.value ? `0, ${e}px, 0` : `${e}px, 0, 0`})` }; }), R = M(() => t.waitTime > 0), A = M( () => Math.max(Math.min(t.speed, O.value), Number.MIN_VALUE) ), { execute: Y, cancel: de, isCancellable: fe } = Xe(qe), { updateCounter: he, triggerUpdate: H } = Ye(), ge = () => { if (!c.value) return 0; const { height: o, width: a } = U(c.value); return I.value ? o : a; }, ve = () => { const e = f[f.length - 1]; return (e == null ? void 0 : e.key) ?? ""; }, pe = () => { t.pausedOnHover && !d.isPaused && d.isScrolling && (d.isPaused = !0, B()); }, me = () => { t.pausedOnHover && d.isPaused && d.isScrolling && (d.isPaused = !1, N(u == null ? void 0 : u.isRunning)); }, J = async () => (u = tt(t.waitTime), await u), we = () => { u != null && u.isRunning && (nt(u), u = null); }, G = (e) => { try { return Ze(e) && t.itemKey && e[t.itemKey] != null ? String(e[t.itemKey]) : JSON.stringify(e); } catch { return ""; } }, D = (e) => ({ value: e, uid: Ge(), key: G(e) }), E = () => { var e; return Array.from(((e = c.value) == null ? void 0 : e.children) ?? []); }, Q = (e) => { const { totalWidth: o, totalHeight: a } = e.reduce( (r, g) => (r.totalHeight += g.height, r.totalWidth += g.width, r), { totalWidth: 0, totalHeight: 0 } ); return I.value ? a : o; }, Se = () => U(n.value), ye = () => U(c.value), Z = () => { const e = Se(), o = ye(); Object.assign(y, { viewportHeight: e.height, viewportWidth: e.width, trackHeight: o.height, trackWidth: o.width }); }, ee = (e) => t.dataSource.findIndex((o) => G(o) === e), Ie = (e) => { f = te(e); }, xe = () => { b && (cancelAnimationFrame(b), b = void 0); }, B = () => { xe(), we(); }, L = () => { B(), ce(), f = []; }, te = (e) => { const o = [], a = c.value; if (!a) return o; const r = ge(); let g; const { top: l, left: m } = a.getBoundingClientRect(), C = (h) => { const v = x.value ? h.bottom - l : r - (h.top - l), p = x.value ? h.right - m : r - (h.left - m); return { bottom: v, right: p }; }, w = (h) => { const v = x.value ? h.previousElementSibling : h.nextElementSibling; return v ? C(v.getBoundingClientRect()) : { bottom: 0, right: 0 }; }; return Qe( e, (h) => { const v = h.getBoundingClientRect(); let { bottom: p, right: P } = g ?? w(h); const S = p, k = P, { bottom: z, right: V } = C(v), Me = z - S, Re = V - k; g = { bottom: z, right: V }, o.push({ uid: h.dataset.uid || "", key: h.dataset.key || "", width: Re, height: Me, top: S, bottom: z, left: k, right: V }); }, x.value ? "forward" : "reverse" ), o; }, W = async (e, o, a) => { const r = t.dataSource.length, g = i.value.length, l = a === "next"; let m = 0; const C = t.dataSource.length * 2, w = async () => { if (m++ > C) return; let h = t.loadCount; const v = []; for (; h-- > 0; ) { e = (e + r) % r; const z = x.value ? "push" : "unshift"; v[z]( D(t.dataSource[e]) ), l ? e++ : e--; } const p = x.value && l || !x.value && !l; i.value = p ? i.value.concat(v) : v.concat(i.value), await Y(); const P = p ? E().slice(g) : E().slice( 0, i.value.length - g ), S = te(P), k = Q(S); P.length > 0 && o > k ? await w() : (Ie(E()), Z()); }; await w(); }, N = async (e = !0) => { if (d.isScrolling = !0, d.isPaused) return; const o = (l, m) => { const C = f.length, w = (S) => I.value ? S.height : S.width; let h = m, v = 0, p = 0; const P = t.dataSource.length; for (; !(p++ > P); ) { const S = w(f[h]); v += S; const k = (h + 1) % C, z = v + w(f[k]); if (l < v) break; if (l < z) return { hasCrossedItem: !0, nextItemIndex: k, remainingOffset: l - v // 返回剩余的偏移量 }; h = k; } return { hasCrossedItem: !1, nextItemIndex: m, remainingOffset: l }; }; let { remainingOffset: a, nextItemIndex: r } = o(d.scrollOffset, 0); const g = async () => { d.scrollOffset += A.value, a += A.value, d.scrollOffsetInPageMode += A.value; const { hasCrossedItem: l, remainingOffset: m, nextItemIndex: C } = o(a, r); if (l) { r = C, a = m; const v = t.waitMode === "item" && R.value, p = t.waitMode === "page" && R.value && d.scrollOffsetInPageMode >= O.value; (v || p) && (a -= m, d.scrollOffset -= m), p && (d.scrollOffsetInPageMode -= m); } if (d.scrollOffset >= O.value) { const v = x.value ? i.value.slice(r) : i.value.slice(0, -r), p = f.slice(r), P = Q(p), S = O.value * 2 - (P - a); if (S > 0) { i.value = v, f = p, r = 0, d.scrollOffset = a; const k = ee(ve()), z = k > -1 ? k + 1 : 0; await W(z, S, "next"); } } const w = t.waitMode === "item" && l && R.value, h = t.waitMode === "page" && l && d.scrollOffsetInPageMode >= O.value && R.value; h && (d.scrollOffsetInPageMode = 0), !((w || h) && await J()) && await new Promise((v, p) => { b = requestAnimationFrame(async () => { try { await g(), v(); } catch (P) { p(P); } }); }); }; R.value && e && await J() || await g(); }, Oe = async (e, o, a) => { const r = D(e); i.value = [r]; const { marginBefore: g, marginAfter: l } = a; g > 0 && await W(o - 1, g, "pre"), l > 0 && await W(o + 1, l, "next"); const C = (() => { const w = f.find((h) => h.uid === r.uid); return w ? I.value ? w.top : w.left : 0; })() - g; d.scrollOffset = C; }, ke = () => { let e = d.scrollOffset; const o = f.length, a = []; let r = !1, g = 0; for (; g < o; ) { const l = f[g]; r && a.push(l); const m = I.value ? l.height : l.width; if (e -= m, e < 0) { if (r) break; a.push(l), r = !0, e += O.value; } g++, g >= o && (g = 0); } return a; }, be = (e) => { const o = I.value ? e.height : e.width, r = (I.value ? e.top : e.left) - d.scrollOffset, l = O.value - r - o + O.value; return { marginBefore: r, marginAfter: l }; }, Ce = () => { const e = ke(); for (const [, o] of e.entries()) { const a = ee(o.key); if (a > -1) return { status: "found", item: t.dataSource[a], index: a, margins: be(o) }; } return { status: "not-found" }; }, Pe = async () => { const e = Ce(); e.status === "found" ? (await Oe(e.item, e.index, e.margins), await N(u == null ? void 0 : u.isRunning)) : (L(), i.value = [], await ne()); }, ne = async () => { await W(0, O.value * 2, "next"), await N(); }, oe = (e) => e.map((o) => D(o)), ze = async () => { const e = Math.ceil(t.dataSource.length / t.loadCount); let o = 0; const a = i.value; for (; o++ < e; ) { const r = t.dataSource.slice( 0, t.loadCount * (o + 1) ); if (x.value || r.reverse(), i.value = oe(r), await Y(), Z(), le.value) return i.value = a, !0; } return i.value = oe(t.dataSource), !1; }; return He(() => { $( () => [t.dataSource, he.value], async () => { try { if (fe() && de(), d.isScrolling && B(), !await ze()) { L(); return; } d.isScrolling ? await Pe() : await ne(); } catch { } }, { immediate: !0, deep: !0 } ), $( () => t.direction, () => { L(), H(); } ), ie(n, (e) => { const { blockSize: o, inlineSize: a } = e[0].contentBoxSize[0]; (o !== y.viewportHeight || a !== y.viewportWidth) && H(); }), ie(c, (e) => { const { blockSize: o, inlineSize: a } = e[0].contentBoxSize[0]; (o !== y.trackHeight || a !== y.trackWidth) && H(); }); }), re(() => { B(); }), (e, o) => i.value.length > 0 ? (F(), j("div", { key: 0, ref_key: "scrollViewportRef", ref: n, class: Ee(["scroll-loop-viewport", [ I.value ? "direction-vertical" : "direction-horizontal", x.value ? "direction-forward" : "direction-backward" ]]), onMouseenter: pe, onMouseleave: me }, [ Le("div", { ref_key: "scrollTrackRef", ref: c, class: "scroll-loop-track", style: Ne(ue.value) }, [ (F(!0), j(Ve, null, je(i.value, (a) => (F(), j("div", { class: "scroll-loop-item", "data-uid": a.uid, "data-key": a.key, key: a.uid }, [ Fe(e.$slots, "default", Ue({ ref_for: !0 }, { item: a.value }), () => [ Ke($e(a.value), 1) ], !0) ], 8, st))), 128)) ], 4) ], 34)) : De("", !0); } }), it = (s, t) => { const n = s.__vccOpts || s; for (const [c, i] of t) n[c] = i; return n; }, lt = /* @__PURE__ */ it(at, [["__scopeId", "data-v-17f09cf5"]]); export { lt as LoopScroll };