Release v0.0.2 #1
@@ -220,6 +220,97 @@ export function initRail(lenis) {
|
|||||||
return { jump, setActive };
|
return { jump, setActive };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 滚动守卫:避免停在两轮之间只剩背景的中间态 */
|
||||||
|
export function initRoundAutoFocus(lenis) {
|
||||||
|
const targets = rounds
|
||||||
|
.filter((round) => round.question && !round.flow)
|
||||||
|
.map((round) => ({ round, section: document.getElementById(`r-${round.id}`) }))
|
||||||
|
.filter((target) => target.section);
|
||||||
|
|
||||||
|
if (!targets.length) return () => {};
|
||||||
|
|
||||||
|
let lockUntil = 0;
|
||||||
|
let currentTarget = "";
|
||||||
|
|
||||||
|
const readY = () => (typeof lenis?.scroll === "number" ? lenis.scroll : window.scrollY);
|
||||||
|
|
||||||
|
const pinDistance = (round) => {
|
||||||
|
const match = String(round.pinLength || "").match(/^\+=([\d.]+)%$/);
|
||||||
|
const ratio = match ? Number(match[1]) / 100 : 1.6;
|
||||||
|
return ratio * (window.innerHeight || document.documentElement.clientHeight);
|
||||||
|
};
|
||||||
|
|
||||||
|
const typingOffset = (round) => {
|
||||||
|
const endProgress = Math.min(round.qEnd + 0.025, round.thinkEnd - 0.03);
|
||||||
|
return Math.max(112, Math.round(pinDistance(round) * endProgress));
|
||||||
|
};
|
||||||
|
|
||||||
|
const focusRound = ({ round, section }, sectionStartY = null) => {
|
||||||
|
const now = performance.now();
|
||||||
|
lockUntil = now + 1150;
|
||||||
|
currentTarget = section.id;
|
||||||
|
const baseY = sectionStartY ?? section.getBoundingClientRect().top + readY();
|
||||||
|
const targetY = baseY + typingOffset(round);
|
||||||
|
|
||||||
|
if (lenis) {
|
||||||
|
lenis.scrollTo(targetY, {
|
||||||
|
duration: 0.85,
|
||||||
|
easing: (t) => 1 - Math.pow(1 - t, 3),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.scrollTo({ top: targetY, behavior: "smooth" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const canFocus = ({ section }) => {
|
||||||
|
const now = performance.now();
|
||||||
|
if (now < lockUntil) return false;
|
||||||
|
const rect = section.getBoundingClientRect();
|
||||||
|
if (section.id === currentTarget && rect.top > -12 && rect.top < 12) return false;
|
||||||
|
return rect.top > 24;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findHalfEnteredRound = () => {
|
||||||
|
const vh = window.innerHeight || document.documentElement.clientHeight;
|
||||||
|
const minTop = Math.max(36, vh * 0.08);
|
||||||
|
const maxTop = vh * 0.62;
|
||||||
|
|
||||||
|
return targets.find((target) => {
|
||||||
|
const { section } = target;
|
||||||
|
const rect = section.getBoundingClientRect();
|
||||||
|
if (!canFocus(target)) return false;
|
||||||
|
return rect.top > minTop && rect.top < maxTop && rect.bottom > vh * 0.55;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkCurrent = () => {
|
||||||
|
const target = findHalfEnteredRound();
|
||||||
|
if (target) focusRound(target);
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggers = targets.map((section) =>
|
||||||
|
ScrollTrigger.create({
|
||||||
|
trigger: section.section,
|
||||||
|
start: "top 62%",
|
||||||
|
end: "top 8%",
|
||||||
|
onEnter: (self) => {
|
||||||
|
if (canFocus(section)) {
|
||||||
|
const vh = window.innerHeight || document.documentElement.clientHeight;
|
||||||
|
focusRound(section, self.start + vh * 0.62);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
window.addEventListener("resize", checkCurrent);
|
||||||
|
window.setTimeout(checkCurrent, 250);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
triggers.forEach((trigger) => trigger.kill());
|
||||||
|
window.removeEventListener("resize", checkCurrent);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/* 降级:reduced motion 直接展示全部内容 */
|
/* 降级:reduced motion 直接展示全部内容 */
|
||||||
export function applyReducedMotion() {
|
export function applyReducedMotion() {
|
||||||
rounds
|
rounds
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
applyReducedMotion,
|
applyReducedMotion,
|
||||||
initChoreography,
|
initChoreography,
|
||||||
initRail,
|
initRail,
|
||||||
|
initRoundAutoFocus,
|
||||||
initSmoothScroll,
|
initSmoothScroll,
|
||||||
playBoot,
|
playBoot,
|
||||||
} from "./choreography";
|
} from "./choreography";
|
||||||
@@ -28,6 +29,7 @@ renderRail(document.getElementById("session-rail"), rail.jump);
|
|||||||
|
|
||||||
if (!reduced) {
|
if (!reduced) {
|
||||||
initChoreography(mind, rail.setActive);
|
initChoreography(mind, rail.setActive);
|
||||||
|
initRoundAutoFocus(lenis);
|
||||||
playBoot(mind);
|
playBoot(mind);
|
||||||
} else {
|
} else {
|
||||||
applyReducedMotion();
|
applyReducedMotion();
|
||||||
|
|||||||
Reference in New Issue
Block a user