feakyyy (#1300)
This commit is contained in:
+150
-40
@@ -417,6 +417,8 @@
|
||||
};
|
||||
</script>
|
||||
<script src="/js/main.js?v=3"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>
|
||||
|
||||
<!-- Serpentine timeline renderer -->
|
||||
<script>
|
||||
@@ -430,9 +432,16 @@
|
||||
|
||||
var nodes = Array.prototype.slice.call(timeline.querySelectorAll('.timeline-node'));
|
||||
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
var useGsap = !!(window.gsap && window.ScrollTrigger);
|
||||
var drawProgress = 0; // 0 to 1, how much of the line is drawn
|
||||
var maxDrawProgress = 0;
|
||||
var ticking = false;
|
||||
var targetDrawProgress = 0;
|
||||
var pathLength = 0;
|
||||
var nodeProgresses = [];
|
||||
var nodeRevealed = [];
|
||||
var state = { progress: 0 };
|
||||
var lineTrigger = null;
|
||||
var nodeTriggers = [];
|
||||
var scrollNormalized = false;
|
||||
|
||||
// Build a rounded-corner path that threads through a list of points.
|
||||
function roundedPath(pts, radius) {
|
||||
@@ -473,11 +482,14 @@
|
||||
track.setAttribute('d', d);
|
||||
progress.setAttribute('d', d);
|
||||
|
||||
var len = progress.getTotalLength();
|
||||
progress.style.strokeDasharray = len;
|
||||
progress.style.strokeDashoffset = len * (1 - drawProgress);
|
||||
updateComet(len);
|
||||
return { pts: pts, len: len };
|
||||
pathLength = progress.getTotalLength();
|
||||
progress.style.strokeDasharray = pathLength;
|
||||
progress.style.strokeDashoffset = pathLength * (1 - drawProgress);
|
||||
nodeProgresses = nodes.map(function (node, index) {
|
||||
return nodePathProgress(node, index, pathLength);
|
||||
});
|
||||
updateComet(pathLength);
|
||||
return { pts: pts, len: pathLength };
|
||||
}
|
||||
|
||||
function updateComet(len) {
|
||||
@@ -511,56 +523,148 @@
|
||||
return best;
|
||||
}
|
||||
|
||||
function updateFromScroll() {
|
||||
ticking = false;
|
||||
var len = progress.getTotalLength();
|
||||
var viewport = window.innerHeight || document.documentElement.clientHeight;
|
||||
var revealLine = viewport * 0.9;
|
||||
var targetProgress = maxDrawProgress;
|
||||
function nodePathProgress(node, index, len) {
|
||||
if (index === nodes.length - 1) return 1;
|
||||
var marker = node.querySelector('.timeline-marker');
|
||||
var markerRect = (marker || node).getBoundingClientRect();
|
||||
var box = timeline.getBoundingClientRect();
|
||||
var pt = {
|
||||
x: markerRect.left - box.left + markerRect.width / 2,
|
||||
y: markerRect.top - box.top + markerRect.height / 2
|
||||
};
|
||||
return lengthAtPoint(pt, len) / len;
|
||||
}
|
||||
|
||||
function revealReachedNodes(len) {
|
||||
nodes.forEach(function (node, index) {
|
||||
var cardRect = node.getBoundingClientRect();
|
||||
if (cardRect.top > revealLine) return;
|
||||
|
||||
node.classList.add('is-visible');
|
||||
|
||||
var marker = node.querySelector('.timeline-marker');
|
||||
var markerRect = (marker || node).getBoundingClientRect();
|
||||
var box = timeline.getBoundingClientRect();
|
||||
var pt = {
|
||||
x: markerRect.left - box.left + markerRect.width / 2,
|
||||
y: markerRect.top - box.top + markerRect.height / 2
|
||||
};
|
||||
var nodeProgress = index === nodes.length - 1 ? 1 : lengthAtPoint(pt, len) / len;
|
||||
targetProgress = Math.max(targetProgress, nodeProgress);
|
||||
if (nodeRevealed[index]) return;
|
||||
if (drawProgress >= (nodeProgresses[index] || 0) - 0.012) {
|
||||
nodeRevealed[index] = true;
|
||||
node.classList.add('is-visible');
|
||||
if (useGsap) {
|
||||
gsap.to(node, {
|
||||
autoAlpha: 1,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
duration: 0.7,
|
||||
ease: 'power3.out',
|
||||
overwrite: true
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
maxDrawProgress = Math.max(maxDrawProgress, targetProgress);
|
||||
drawProgress = maxDrawProgress;
|
||||
progress.style.strokeDashoffset = len * (1 - drawProgress);
|
||||
updateComet(len);
|
||||
function renderProgress() {
|
||||
drawProgress = state.progress;
|
||||
progress.style.strokeDashoffset = pathLength * (1 - drawProgress);
|
||||
updateComet(pathLength);
|
||||
revealReachedNodes(pathLength);
|
||||
if (drawProgress >= 1) {
|
||||
comet.style.opacity = 0;
|
||||
nodes.forEach(function (n) { n.classList.add('is-visible'); });
|
||||
nodes.forEach(function (n, index) {
|
||||
nodeRevealed[index] = true;
|
||||
n.classList.add('is-visible');
|
||||
if (useGsap) gsap.set(n, { autoAlpha: 1, y: 0, scale: 1 });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function requestScrollUpdate() {
|
||||
if (ticking) return;
|
||||
ticking = true;
|
||||
requestAnimationFrame(updateFromScroll);
|
||||
function advanceTo(value, duration) {
|
||||
var next = Math.max(targetDrawProgress, value);
|
||||
targetDrawProgress = Math.min(1, Math.max(0, next));
|
||||
if (targetDrawProgress <= state.progress + 0.001) return;
|
||||
|
||||
if (!useGsap) {
|
||||
state.progress = targetDrawProgress;
|
||||
renderProgress();
|
||||
return;
|
||||
}
|
||||
|
||||
gsap.to(state, {
|
||||
progress: targetDrawProgress,
|
||||
duration: duration || 0.85,
|
||||
ease: 'power3.out',
|
||||
overwrite: true,
|
||||
onUpdate: renderProgress
|
||||
});
|
||||
}
|
||||
|
||||
function destroyScrollTriggers() {
|
||||
if (lineTrigger) {
|
||||
lineTrigger.kill();
|
||||
lineTrigger = null;
|
||||
}
|
||||
nodeTriggers.forEach(function (trigger) { trigger.kill(); });
|
||||
nodeTriggers = [];
|
||||
}
|
||||
|
||||
function createScrollTriggers() {
|
||||
if (!useGsap) return;
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
if (!scrollNormalized) {
|
||||
try {
|
||||
ScrollTrigger.normalizeScroll({
|
||||
allowNestedScroll: true,
|
||||
lockAxis: false,
|
||||
momentum: function (self) {
|
||||
return Math.min(2.4, Math.abs(self.velocityY) / 1100);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
ScrollTrigger.normalizeScroll(true);
|
||||
}
|
||||
scrollNormalized = true;
|
||||
}
|
||||
|
||||
destroyScrollTriggers();
|
||||
nodes.forEach(function (node, index) {
|
||||
gsap.set(node, nodeRevealed[index]
|
||||
? { autoAlpha: 1, y: 0, scale: 1, transformOrigin: '50% 50%' }
|
||||
: { autoAlpha: 0, y: 34, scale: 0.985, transformOrigin: '50% 50%' });
|
||||
});
|
||||
|
||||
lineTrigger = ScrollTrigger.create({
|
||||
trigger: timeline,
|
||||
start: 'top 82%',
|
||||
endTrigger: nodes[nodes.length - 1] || timeline,
|
||||
end: 'top 82%',
|
||||
scrub: 0.75,
|
||||
invalidateOnRefresh: true,
|
||||
onUpdate: function (self) {
|
||||
advanceTo(self.progress, 0.45);
|
||||
}
|
||||
});
|
||||
|
||||
nodes.forEach(function (node, index) {
|
||||
nodeTriggers.push(ScrollTrigger.create({
|
||||
trigger: node,
|
||||
start: 'top 82%',
|
||||
once: true,
|
||||
onEnter: function () {
|
||||
advanceTo(nodeProgresses[index] || 0, 0.85);
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
build();
|
||||
if (reduceMotion) {
|
||||
drawProgress = 1;
|
||||
state.progress = 1;
|
||||
progress.style.strokeDashoffset = 0;
|
||||
nodes.forEach(function (n) { n.classList.add('is-visible'); });
|
||||
return;
|
||||
}
|
||||
updateFromScroll();
|
||||
window.addEventListener('scroll', requestScrollUpdate, { passive: true });
|
||||
if (useGsap) {
|
||||
createScrollTriggers();
|
||||
renderProgress();
|
||||
ScrollTrigger.refresh();
|
||||
} else {
|
||||
advanceTo(1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Recompute geometry on resize (layout flips between 1 and 3 columns).
|
||||
@@ -570,7 +674,10 @@
|
||||
resizeTimer = setTimeout(function () {
|
||||
_samples = null;
|
||||
build();
|
||||
if (!reduceMotion) updateFromScroll();
|
||||
if (!reduceMotion && useGsap) {
|
||||
createScrollTriggers();
|
||||
ScrollTrigger.refresh();
|
||||
}
|
||||
}, 120);
|
||||
});
|
||||
|
||||
@@ -583,7 +690,10 @@
|
||||
window.addEventListener('load', function () {
|
||||
_samples = null;
|
||||
build();
|
||||
if (!reduceMotion) updateFromScroll();
|
||||
if (!reduceMotion && useGsap) {
|
||||
createScrollTriggers();
|
||||
ScrollTrigger.refresh();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user