@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
JavaScript
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
};