Natural Sticky Footer Demo
Scroll down to see the footer appear naturally, then scroll back up to see it hide seamlessly.
💡 Pro tip: Click and drag to scroll with precision! Perfect for testing exactly how the footer behaves at different scroll speeds.
Lightweight champion: This footer uses the default
settings: snapEagerness: 1.0
and
scrollThreshold: 0
. This footer script is only
1.2KB minified with
zero dependencies. Pure TypeScript power!
🔍 Footer Implementation Deep Dive
The footer implementation is more complex than the header because bottom-anchored positioning behaves differently. Let's look at the actual source code:
/**
* Attaches a natural hide/show behavior to a sticky element placed at the bottom.
*/
export function naturalStickyBottom(element: HTMLElement) {
let lastScrollY = window.scrollY;
let mode = 'relative';
const handleScroll = () => {
const currentScrollY = window.scrollY;
const scrollingDown = currentScrollY > lastScrollY;
const scrollingUp = currentScrollY < lastScrollY;
const elementRect = element.getBoundingClientRect();
const elementHeight = element.offsetHeight;
const isElementVisible =
elementRect.bottom > 0 && elementRect.top < window.innerHeight;
// Scenario 1: Element is sticky at bottom and user scrolls up
// Release the element from sticky position so it can scroll with content naturally
if (mode === 'sticky' && scrollingUp) {
mode = 'relative';
element.style.position = 'relative';
// When releasing from sticky bottom position, we need to calculate where
// the element should be positioned to maintain visual continuity.
// The element is currently at the bottom of the viewport, so we calculate
// its position relative to the document end to maintain that relationship.
const currentDocumentPosition = elementRect.top + currentScrollY;
// Reset any previous bottom styling since we're switching to top-based positioning
element.style.bottom = '';
// Calculate the offset from the element's natural position (at document end)
// This ensures the element appears to stay in place when transitioning from sticky
const targetOffset =
currentDocumentPosition -
(document.documentElement.scrollHeight - elementHeight);
element.style.top = `${targetOffset}px`;
}
// Scenario 2: Element is in relative mode, user scrolls down, and element is not visible
// Position the element just below the viewport so it can naturally scroll into view
else if (mode === 'relative' && scrollingDown && !isElementVisible) {
element.style.position = 'relative';
// Calculate where we want the element to appear (just below the viewport)
const targetPosition = currentScrollY + window.innerHeight;
// Get the element's current offset and natural position in the document
const currentTopOffset = parseFloat(element.style.top || '0');
const naturalElementTop =
elementRect.top + currentScrollY - currentTopOffset;
// Calculate the offset needed to position element just below viewport
const offset = targetPosition - naturalElementTop;
element.style.top = `${offset}px`;
}
// Scenario 3: Element is in relative mode and has scrolled into view at bottom
// Make it sticky so it stays at the bottom of the viewport
else if (mode === 'relative' && elementRect.bottom <= window.innerHeight) {
mode = 'sticky';
element.style.position = 'sticky';
element.style.top = 'auto'; // Reset top positioning
element.style.bottom = '0'; // Stick to bottom of viewport
}
lastScrollY = currentScrollY > 0 ? currentScrollY : 0;
};
// Run once on load to set the initial state correctly.
handleScroll();
window.addEventListener('scroll', handleScroll, { passive: true });
return {
destroy: () => {
window.removeEventListener('scroll', handleScroll);
},
};
}
🧠 Why Bottom Is More Complex
As you can see from the source code above, the bottom implementation requires more sophisticated calculations. Here's why:
The bottom sticky implementation is particularly useful for:
- Cookie banners that don't get in the way of reading
- Call-to-action bars that appear when relevant content is reached
- Media player controls that hide during content browsing
- Chat widgets that respect user focus and scroll behavior
- Shopping cart summaries in e-commerce sites
🎚️ ScrollThreshold for Footers
This footer demo uses scrollThreshold: 0
, meaning it
activates at any scroll speed. For footers, you might want to use
higher thresholds to create more intentional interactions:
- scrollThreshold: 0 - Always activate (current demo behavior)
- scrollThreshold: 10-15 - Good for general footer use
- scrollThreshold: 20-25 - More intentional, appears only during deliberate scrolling
Visit the ScrollThreshold Comparison to compare different values side-by-side.
🎭 The Three Scenarios Explained
The footer operates through three distinct scenarios:
When user scrolls up while footer is sticky, we calculate its current document position and switch to relative positioning with a complex offset calculation to maintain visual continuity.
When scrolling down and footer isn't visible, we position it just below the viewport so it naturally scrolls into view as the user continues scrolling down.
When the footer scrolls into view at the bottom, we switch it back to
position: sticky; bottom: 0
so it stays anchored to the
bottom of the viewport.
⚡ Performance Characteristics
Despite the complexity, the footer implementation is highly optimized:
- Passive event listeners for smooth scrolling performance
- Minimal DOM queries - getBoundingClientRect() only when needed
- No continuous animations - just position updates
- Debouncing not needed - calculations are lightweight
📐 Mathematical Precision
The key mathematical relationships that make this work:
// Current position in document
currentDocumentPosition = elementRect.top + currentScrollY
// Natural position at document end
naturalPosition = documentHeight - elementHeight
// Offset to maintain visual continuity
targetOffset = currentDocumentPosition - naturalPosition
These calculations ensure that when we switch positioning modes, the footer appears to stay in exactly the same place visually, creating the seamless natural effect.
Keep scrolling to see how this feels compared to traditional implementations... Notice how the footer doesn't just "pop" into existence - it naturally flows into view as if it were always part of the content you're scrolling through.
🆚 Traditional vs Natural: Less Distracting Experience
❌ Traditional Slide Animations
- Sudden slide-up from bottom edge
- Fixed animation speed (usually 300ms)
- Triggered by scroll thresholds
- Can interrupt reading flow
- Feels disconnected from user input
✅ Natural Sticky Approach
- Naturally flows into view
- Speed matches your scroll exactly
- No artificial triggers or thresholds
- Doesn't break reading concentration
- Feels like part of the content
This creates a much more cohesive and less distracting user experience, especially important for elements like footers that typically contain secondary actions or information.
Try different scroll patterns - quick scrolls, slow scrolls, bouncing back and forth. The footer responds perfectly to every movement, always feeling natural and never jarring.