one
Version:
One is a React framework for web, iOS, and Android apps.
104 lines (102 loc) • 2.92 kB
JavaScript
function createPrefetchIntent(options) {
const { onPrefetch, maxReach = 500, perpWeight = 5, minSpeed = 8 } = options;
const done = /* @__PURE__ */ new Set();
let rects = [];
let px = 0, py = 0, vx = 0, vy = 0;
let moveCount = 0;
let lastPrefetchMove = 0;
function setRects(newRects) {
rects = newRects.filter((r) => !done.has(r.h));
}
function move(x, y, dx, dy) {
moveCount++;
const smooth = moveCount < 3 ? .3 : .6;
vx = vx * smooth + dx * (1 - smooth);
vy = vy * smooth + dy * (1 - smooth);
px = x;
py = y;
const speed = Math.sqrt(vx * vx + vy * vy);
if (speed < minSpeed) return;
const ux = vx / speed;
const uy = vy / speed;
let best = Infinity;
let href = "";
for (const { r, h } of rects) {
const cx = (r.left + r.right) / 2 - px;
const cy = (r.top + r.bottom) / 2 - py;
const dist = Math.sqrt(cx * cx + cy * cy);
const along = cx * ux + cy * uy;
if (along < 0) continue;
const perpX = cx - along * ux;
const perpY = cy - along * uy;
const perp = Math.sqrt(perpX * perpX + perpY * perpY);
const baseRadius = Math.max(r.width, r.height) / 2 + 30;
const distanceFactor = Math.max(.2, 1 - dist / 1e3);
const radius = baseRadius * distanceFactor + 20;
if (perp > radius) continue;
const score = along + perp * perpWeight;
if (score < best) {
best = score;
href = h;
}
}
if (href && best < maxReach && moveCount - lastPrefetchMove > 1) {
done.add(href);
rects = rects.filter((r) => r.h !== href);
lastPrefetchMove = moveCount;
onPrefetch(href);
}
}
const nodes = /* @__PURE__ */ new Map();
function observe(el, href) {
nodes.set(el, href);
return () => {
nodes.delete(el);
done.delete(href);
};
}
return {
setRects,
move,
observe,
nodes,
done
};
}
let instance = null;
let started = false;
function startPrefetchIntent(onPrefetch) {
if (started) return instance;
started = true;
instance = createPrefetchIntent({ onPrefetch });
let frame = 0;
let px = 0, py = 0;
function measure() {
if (!instance.nodes.size) return setTimeout(measure, 300);
const io = new IntersectionObserver((entries) => {
io.disconnect();
instance.setRects(entries.filter((e) => e.isIntersecting).map((e) => ({
r: e.boundingClientRect,
h: instance.nodes.get(e.target)
})).filter((x) => x.h));
setTimeout(measure, 300);
});
instance.nodes.forEach((_, el) => io.observe(el));
}
measure();
document.addEventListener("mousemove", (e) => {
if (++frame % 4) return;
const dx = e.clientX - px;
const dy = e.clientY - py;
px = e.clientX;
py = e.clientY;
instance.move(px, py, dx, dy);
}, { passive: true });
return instance;
}
function observePrefetchIntent(el, href) {
if (!instance) return () => {};
return instance.observe(el, href);
}
export { createPrefetchIntent, observePrefetchIntent, startPrefetchIntent };
//# sourceMappingURL=prefetchIntent.mjs.map