{"id":30770431,"url":"https://github.com/kadykov/natural-sticky","last_synced_at":"2025-09-04T23:06:45.065Z","repository":{"id":310551587,"uuid":"1039518515","full_name":"kadykov/natural-sticky","owner":"kadykov","description":"Ultra-lightweight (1.1KB) sticky elements with natural hide-on-scroll effects. Zero dependencies, pure TypeScript, less distracting animations.","archived":false,"fork":false,"pushed_at":"2025-08-26T16:40:25.000Z","size":4828,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-26T21:09:13.902Z","etag":null,"topics":["animation","footer","framework-agnostic","frontend","header","hide-on-scroll","javascript","lightweight","natural-scrolling","navigation","performance","scroll","scroll-effects","sticky","sticky-header","typescript","ui-components","vanilla-js","web-components","zero-dependencies"],"latest_commit_sha":null,"homepage":"https://github.kadykov.com/natural-sticky/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kadykov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-17T12:15:54.000Z","updated_at":"2025-08-26T16:48:25.000Z","dependencies_parsed_at":"2025-08-18T21:22:47.954Z","dependency_job_id":"6989660e-169a-41a5-9bee-c6b947351cfd","html_url":"https://github.com/kadykov/natural-sticky","commit_stats":null,"previous_names":["kadykov/natural-sticky"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/kadykov/natural-sticky","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kadykov%2Fnatural-sticky","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kadykov%2Fnatural-sticky/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kadykov%2Fnatural-sticky/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kadykov%2Fnatural-sticky/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kadykov","download_url":"https://codeload.github.com/kadykov/natural-sticky/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kadykov%2Fnatural-sticky/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273685604,"owners_count":25149722,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-04T02:00:08.968Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animation","footer","framework-agnostic","frontend","header","hide-on-scroll","javascript","lightweight","natural-scrolling","navigation","performance","scroll","scroll-effects","sticky","sticky-header","typescript","ui-components","vanilla-js","web-components","zero-dependencies"],"created_at":"2025-09-04T23:05:09.901Z","updated_at":"2025-09-04T23:06:45.047Z","avatar_url":"https://github.com/kadykov.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Natural Sticky\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://github.kadykov.com/natural-sticky/demo/header-1080p.webp\" alt=\"Natural Sticky Header Demo\" width=\"540\" /\u003e\n  \u003cbr\u003e\n  \u003cem\u003eNatural hide-on-scroll effect - the header moves naturally with your scroll speed\u003c/em\u003e\n\u003c/div\u003e\n\n[Live Demo](https://github.kadykov.com/natural-sticky/) | [Header CodePen](https://codepen.io/kadykov/pen/emprNoY) | [Footer CodePen](https://codepen.io/kadykov/pen/WbQJQjq)\n\n[![npm version](https://badge.fury.io/js/natural-sticky.svg)](https://badge.fury.io/js/natural-sticky)\n[![NPM Downloads](https://img.shields.io/npm/dw/natural-sticky)](https://badge.fury.io/js/natural-sticky)\n[![License](https://img.shields.io/npm/l/natural-sticky)](https://opensource.org/licenses/MIT)\n[![Gzip Size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/natural-sticky/dist/natural-sticky.top.min.js?compression=gzip)](https://cdn.jsdelivr.net/npm/natural-sticky/dist/)\n[![Minified Size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/natural-sticky/dist/natural-sticky.top.min.js)](https://cdn.jsdelivr.net/npm/natural-sticky/dist/)\n\nA lightweight, framework-agnostic package for natural hide-on-scroll effects.\n\n## Why Natural Sticky?\n\n**The problem with existing solutions:** Most sticky header libraries use CSS animations or JavaScript tweening that feel disconnected from your actual scroll behavior. They slide, fade, or pop in/out with predetermined timing that can feel jarring or distracting.\n\n**Our approach:** No animations at all. Instead, we smartly switch between `sticky` and `relative` positioning, letting the browser's native scrolling handle all movement. Headers and footers flow naturally with your scroll speed - hide naturally when scrolling down, reappear naturally when scrolling up.\n\n**Key Benefits:**\n\n- **🚀 Ultra Lightweight:** 1.0KB (header) / 1.2KB (footer) - no dependencies\n- **🎯 Natural Movement:** Flows with your scroll speed, no artificial animations\n- **🔇 Non-Distracting:** Movement feels like part of the content, not a separate UI behavior\n- **⚡ Gap-Free Transitions:** Smart prediction eliminates visual gaps during direction changes\n- **🎛️ Fine-tunable:** Optional parameters for different use cases and preferences\n\n**Compared to alternatives:**\n\n- **Headroom.js:** ~7KB, slide animations, requires configuration\n- **AOS:** ~13KB, complex animations, heavy setup\n- **Natural Sticky:** 1.0-1.2KB, zero dependencies, natural movement that doesn't break focus\n\n## Installation\n\n```bash\nnpm install natural-sticky\n```\n\n## Quick Start\n\n### Browser (CDN)\n\n```html\n\u003c!-- For headers (1.0KB) --\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/natural-sticky/dist/natural-sticky.top.min.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  const header = document.querySelector('.header');\n  window.naturalStickyTop(header);\n\u003c/script\u003e\n\n\u003c!-- For footers (1.2KB) --\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/natural-sticky/dist/natural-sticky.bottom.min.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  const footer = document.querySelector('.footer');\n  window.naturalStickyBottom(footer);\n\u003c/script\u003e\n```\n\n### Module Bundlers\n\n```javascript\nimport { naturalStickyTop, naturalStickyBottom } from 'natural-sticky';\n\n// Headers\nconst headerInstance = naturalStickyTop(document.querySelector('.header'));\n\n// Footers\nconst footerInstance = naturalStickyBottom(document.querySelector('.footer'));\n\n// Clean up when needed\nheaderInstance.destroy();\nfooterInstance.destroy();\n```\n\n## CSS Requirements\n\nElements must align with their respective screen edges:\n\n```css\n.header {\n  margin-top: 0; /* Required: must align with top edge */\n  /* Other margins preserved: margin-bottom, margin-left, margin-right */\n}\n\n.footer {\n  margin-bottom: 0; /* Required: must align with bottom edge */\n  /* Other margins preserved: margin-top, margin-left, margin-right */\n}\n```\n\n**Why:** Default margins on headings (`\u003ch1\u003e`, `\u003ch2\u003e`) or paragraphs (`\u003cp\u003e`) create gaps that disrupt positioning calculations.\n\n## Advanced Configuration\n\nFor most use cases, the defaults work perfectly. However, you can fine-tune the behavior:\n\n```javascript\nnaturalStickyTop(header, {\n  snapEagerness: 1.0, // Gap prevention: 0.0 (natural) to 3.0+ (magnetic)\n  scrollThreshold: 0, // Activation threshold: 0 (always) to 30+ (fast scroll only)\n});\n```\n\n### snapEagerness - Tuning Natural vs Gap-Free\n\nControls how aggressively the element anticipates scroll direction changes:\n\n- **`0.0`** - Pure natural movement (occasional gaps during very fast scrolling)\n- **`1.0`** - Balanced default (recommended for most cases)\n- **`2.0+`** - Aggressive gap prevention (more predictive, less natural)\n\n### scrollThreshold - Controlling Activation\n\nControls when the scroll-in effect activates based on scroll speed:\n\n- **`0`** - Always activate (default, most responsive)\n- **`5-15`** - Moderate threshold (deliberate scrolling required)\n- **`20+`** - High threshold (fast scrolling only)\n\n### Live Comparisons\n\n- [4-Headers SnapEagerness](https://github.kadykov.com/natural-sticky/demo/multi-header-snap.html) - Live side-by-side comparison\n- [SnapEagerness Demos](https://github.kadykov.com/natural-sticky/demo/comparison-snap.html) - Individual iframe comparisons\n- [4-Headers ScrollThreshold](https://github.kadykov.com/natural-sticky/demo/multi-header-threshold.html) - Live side-by-side comparison\n- [ScrollThreshold Demos](https://github.kadykov.com/natural-sticky/demo/comparison-threshold.html) - Individual iframe comparisons\n\n## How It Works\n\nThe core insight: avoid animations entirely.\n\n1. **When visible and user scrolls down:** Switch to `relative` positioning so the element scrolls naturally with content\n2. **When user scrolls up:** Position the element just above/below the viewport so it scrolls into view naturally\n3. **When element reaches viewport edge:** Switch to `sticky` positioning to keep it visible\n\nThe magic is in the timing - we use scroll speed prediction to switch between positioning modes at exactly the right moment, eliminating visual gaps without artificial animations.\n\n## Framework Integration\n\nWorks seamlessly with any framework:\n\n```javascript\n// React\nuseEffect(() =\u003e {\n  const instance = naturalStickyTop(headerRef.current);\n  return () =\u003e instance.destroy();\n}, []);\n\n// Vue\nmounted() {\n  this.headerInstance = naturalStickyTop(this.$refs.header);\n},\nbeforeDestroy() {\n  this.headerInstance.destroy();\n}\n\n// Angular\nngAfterViewInit() {\n  this.headerInstance = naturalStickyTop(this.headerElement.nativeElement);\n}\nngOnDestroy() {\n  this.headerInstance.destroy();\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkadykov%2Fnatural-sticky","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkadykov%2Fnatural-sticky","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkadykov%2Fnatural-sticky/lists"}