Skip to content

Instantly share code, notes, and snippets.

@wpeasy
Created July 15, 2025 03:53
Show Gist options
  • Select an option

  • Save wpeasy/ae81d11f64752e83914f047338afd076 to your computer and use it in GitHub Desktop.

Select an option

Save wpeasy/ae81d11f64752e83914f047338afd076 to your computer and use it in GitHub Desktop.
Bricks No GSAP Scrub
(() => {
const parseOffset = (offset, vh) =>
offset?.endsWith('%') ? (parseFloat(offset) / 100) * vh : parseFloat(offset || 0);
const getTargetPoint = (rect, side) => {
switch (side) {
case 'top': return rect.top;
case 'center': return rect.top + rect.height / 2;
case 'bottom': return rect.bottom;
default: return rect.top;
}
};
const updateVisibility = () => {
const vh = window.innerHeight;
const trackers = [...document.querySelectorAll('.at-vp-tracker')];
trackers.forEach(el => {
const rect = el.getBoundingClientRect();
const partiallyVisible = rect.bottom > 0 && rect.top < vh;
const side = el.dataset.trackSide || 'top';
const topOffset = parseOffset(el.dataset.trackTopOffset, vh);
const bottomOffset = vh - parseOffset(el.dataset.trackBottomOffset, vh);
const point = getTargetPoint(rect, side);
// Always update scroll position
el.style.setProperty('--at-scroll-position', point.toFixed(2));
// Invert logic
let scrub = 0;
const isInverted = el.hasAttribute('data-invert');
if (isInverted) {
if (point >= topOffset && point <= bottomOffset) {
scrub = 1;
el.removeAttribute('data-outside-top');
el.removeAttribute('data-outside-bottom');
} else {
const isAbove = point < topOffset;
const distFromEdge = isAbove
? topOffset - point
: point - bottomOffset;
const maxDist = isAbove
? topOffset
: vh - bottomOffset;
scrub = 1 - Math.min(distFromEdge / maxDist, 1);
// Set directional attributes
if (isAbove) {
el.setAttribute('data-outside-top', '');
el.removeAttribute('data-outside-bottom');
} else {
el.setAttribute('data-outside-bottom', '');
el.removeAttribute('data-outside-top');
}
}
} else {
const clamped = Math.min(Math.max(point, topOffset), bottomOffset);
scrub = (clamped - topOffset) / (bottomOffset - topOffset);
el.removeAttribute('data-outside-top');
el.removeAttribute('data-outside-bottom');
}
const scrubNeg = 1 - scrub;
el.style.setProperty('--at-scrub', scrub.toFixed(4));
el.style.setProperty('--at-scrub-negative', scrubNeg.toFixed(4));
// Viewport visibility
if (partiallyVisible) {
el.setAttribute('data-in-vp', '');
} else {
el.removeAttribute('data-in-vp');
}
// Bounds visibility
if (point >= topOffset && point <= bottomOffset) {
el.setAttribute('data-in-bounds', '');
} else {
el.removeAttribute('data-in-bounds');
}
});
};
window.addEventListener('scroll', updateVisibility, { passive: true });
window.addEventListener('resize', updateVisibility);
document.addEventListener('DOMContentLoaded', updateVisibility); // Safe initialization
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment