{"id":30618079,"url":"https://github.com/probably-not/stick-to-bottom","last_synced_at":"2026-02-12T02:21:50.245Z","repository":{"id":305473107,"uuid":"1021692207","full_name":"probably-not/stick-to-bottom","owner":"probably-not","description":"A lightweight vanilla JavaScript library that automatically sticks to the bottom of a container and smoothly animates content while new items are added","archived":false,"fork":false,"pushed_at":"2025-12-03T12:28:04.000Z","size":99,"stargazers_count":0,"open_issues_count":0,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-06T16:18:57.415Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/probably-not.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":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-17T19:42:18.000Z","updated_at":"2025-12-03T12:27:49.000Z","dependencies_parsed_at":"2025-07-20T10:38:52.205Z","dependency_job_id":null,"html_url":"https://github.com/probably-not/stick-to-bottom","commit_stats":null,"previous_names":["probably-not/stick-to-bottom"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/probably-not/stick-to-bottom","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/probably-not%2Fstick-to-bottom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/probably-not%2Fstick-to-bottom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/probably-not%2Fstick-to-bottom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/probably-not%2Fstick-to-bottom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/probably-not","download_url":"https://codeload.github.com/probably-not/stick-to-bottom/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/probably-not%2Fstick-to-bottom/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28607624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-20T16:10:39.856Z","status":"ssl_error","status_checked_at":"2026-01-20T16:10:39.493Z","response_time":117,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":[],"created_at":"2025-08-30T11:08:12.614Z","updated_at":"2026-01-20T17:08:01.156Z","avatar_url":"https://github.com/probably-not.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# stick-to-bottom\n\n**DISCLAIMER: THIS LIBRARY WAS 100% VIBE CODED BY CLAUDE CODE SONNET.**\n\n**[Vibe-coding methodology can be found here](./vibing/README.md)**\n\n[![npm version](https://img.shields.io/npm/v/@probably-not/stick-to-bottom.svg?style=flat-square)](https://www.npmjs.com/package/@probably-not/stick-to-bottom)\n[![npm downloads](https://img.shields.io/npm/dm/@probably-not/stick-to-bottom.svg?style=flat-square)](https://www.npmjs.com/package/@probably-not/stick-to-bottom)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nA lightweight **zero-dependency** vanilla JavaScript library that automatically sticks to the bottom of a container and smoothly animates content while new items are added. Perfect for chat applications, live logs, and any streaming content interface.\n\n## ✨ Features\n\n- **Framework Agnostic**: Pure vanilla JavaScript, works with any framework or no framework\n- **Zero Dependencies**: No external dependencies, lightweight and fast\n- **Smooth Animations**: Velocity-based spring animations with configurable parameters\n- **Smart Scroll Detection**: Distinguishes between user scrolling and programmatic scrolling\n- **Resize Handling**: Automatically handles content size changes using ResizeObserver\n- **Mobile Friendly**: Works seamlessly on touch devices\n- **TypeScript Support**: Full TypeScript definitions included\n- **Escape Detection**: Users can scroll up to \"escape\" the sticky behavior\n- **Selection Aware**: Pauses scrolling when user is selecting text\n\n## 🚀 Installation\n\n```bash\nnpm install @probably-not/stick-to-bottom\n```\n\n```bash\nyarn add @probably-not/stick-to-bottom\n```\n\n```bash\npnpm add @probably-not/stick-to-bottom\n```\n\n## 📖 Usage\n\n### Basic Usage\n\n```javascript\nimport StickToBottom from '@probably-not/stick-to-bottom';\n\n// Get your container and content elements\nconst container = document.querySelector('.chat-container');\nconst content = document.querySelector('.messages');\n\n// Create the stick-to-bottom instance\nconst stickToBottom = new StickToBottom(container, content);\n\n// Add new messages\nconst newMessage = document.createElement('div');\nnewMessage.textContent = 'Hello world!';\ncontent.appendChild(newMessage);\n\n// The container will automatically scroll to bottom with smooth animation\n```\n\n### With Options\n\n```javascript\nconst stickToBottom = new StickToBottom(container, content, {\n  // Spring animation configuration\n  damping: 0.7,        // How much to damp the animation (0-1)\n  stiffness: 0.05,     // Animation stiffness\n  mass: 1.25,          // Animation mass\n  \n  // Behavior options\n  initial: 'smooth',   // Scroll to bottom on initialization\n  resize: 'smooth',    // How to handle resize events\n  \n  // Custom target calculation\n  targetScrollTop: (target, { scrollElement, contentElement }) =\u003e {\n    return target - 50; // Leave 50px from bottom\n  }\n});\n```\n\n### Event Handling\n\n```javascript\n// Listen for state changes\nconst unsubscribe = stickToBottom.on('bottomChange', (isAtBottom) =\u003e {\n  const scrollButton = document.querySelector('.scroll-to-bottom');\n  scrollButton.style.display = isAtBottom ? 'none' : 'block';\n});\n\n// Listen for escape events\nstickToBottom.on('escapeChange', (hasEscaped) =\u003e {\n  console.log('User has escaped sticky behavior:', hasEscaped);\n});\n\n// Listen for all state changes\nstickToBottom.on('stateChange', (state) =\u003e {\n  console.log('State changed:', state);\n});\n\n// Clean up\nunsubscribe();\n```\n\n### Programmatic Scrolling\n\n```javascript\n// Scroll to bottom with default animation\nstickToBottom.scrollToBottom();\n\n// Scroll with custom animation\nstickToBottom.scrollToBottom({\n  animation: 'instant',  // or spring config object\n  duration: 500,         // wait 500ms before allowing completion\n  ignoreEscapes: true    // ignore user scroll during animation\n});\n\n// Scroll with promise handling\nstickToBottom.scrollToBottom().then((success) =\u003e {\n  if (success) {\n    console.log('Scrolled to bottom successfully');\n  } else {\n    console.log('Scroll was cancelled');\n  }\n});\n```\n\n## 📚 API Reference\n\n### Constructor\n\n```javascript\nnew StickToBottom(scrollElement, contentElement, options)\n```\n\n**Parameters:**\n- `scrollElement` (HTMLElement): The scrollable container\n- `contentElement` (HTMLElement): The content element to observe\n- `options` (Object, optional): Configuration options\n\n**Options:**\n- `damping` (number, default: 0.7): Animation damping (0-1)\n- `stiffness` (number, default: 0.05): Animation stiffness\n- `mass` (number, default: 1.25): Animation mass\n- `initial` (boolean|string|Object, default: true): Initial scroll behavior\n- `resize` (string|Object, default: inherited): Resize scroll behavior\n- `targetScrollTop` (function): Custom target scroll calculation\n\n### Methods\n\n#### `scrollToBottom(options)`\nScrolls to the bottom with optional configuration.\n\n**Parameters:**\n- `options.animation` (string|Object): Animation configuration ('instant' or spring config)\n- `options.duration` (number|Promise): Duration to wait before completion\n- `options.wait` (number|boolean): Wait time before starting\n- `options.preserveScrollPosition` (boolean): Don't change isAtBottom state\n- `options.ignoreEscapes` (boolean): Ignore user scroll during animation\n\n**Returns:** Promise\u003cboolean\u003e - Resolves to true if successful\n\n#### `on(event, handler)`\nSubscribe to events.\n\n**Events:**\n- `bottomChange`: Fired when isAtBottom state changes\n- `escapeChange`: Fired when user escapes sticky behavior\n- `nearBottomChange`: Fired when near bottom state changes\n- `stateChange`: Fired on any state change\n\n**Returns:** Function to unsubscribe\n\n#### `off(event, handler)`\nUnsubscribe from events.\n\n#### `setScrollElement(element)`\nChange the scroll element.\n\n#### `setContentElement(element)`\nChange the content element.\n\n#### `destroy()`\nClean up all event listeners and resources.\n\n### Properties\n\n#### `state`\nCurrent state object containing:\n- `isAtBottom` (boolean): Whether currently at bottom\n- `escapedFromLock` (boolean): Whether user has scrolled up\n- `isNearBottom` (boolean): Whether near bottom (within offset)\n- `scrollTop` (number): Current scroll position\n- `targetScrollTop` (number): Target scroll position\n\n#### `scrollTop` (getter/setter)\nGet or set the current scroll position.\n\n#### `isNearBottom` (getter)\nCheck if scroll position is near bottom.\n\n## 🔧 Configuration\n\n### Animation Configuration\n\n```javascript\n{\n  damping: 0.7,      // How much to slow down the animation (0 = no damping, 1 = full damping)\n  stiffness: 0.05,   // How quickly animation reaches target (higher = faster)\n  mass: 1.25         // Inertial mass (higher = slower, more momentum)\n}\n```\n\n### Behavior Options\n\n```javascript\n{\n  initial: 'smooth',     // Scroll on initialization: true, false, 'instant', 'smooth', or animation config\n  resize: 'smooth',      // Scroll on resize: 'instant', 'smooth', or animation config\n  targetScrollTop: (target, elements) =\u003e {\n    // Custom target calculation\n    return target - 100; // Leave 100px from bottom\n  }\n}\n```\n\n## 🌟 Advanced Usage\n\n### Custom Animation Presets\n\n```javascript\nconst animations = {\n  bounce: { damping: 0.5, stiffness: 0.1, mass: 0.8 },\n  gentle: { damping: 0.8, stiffness: 0.03, mass: 1.5 },\n  snappy: { damping: 0.6, stiffness: 0.08, mass: 1.0 }\n};\n\nconst stickToBottom = new StickToBottom(container, content, {\n  ...animations.gentle,\n  resize: animations.snappy\n});\n```\n\n### Dynamic Element Management\n\n```javascript\nclass DynamicChat {\n  constructor() {\n    this.stickToBottom = new StickToBottom(null, null);\n    this.setupDynamicElements();\n  }\n  \n  setupDynamicElements() {\n    // Change containers dynamically\n    document.addEventListener('tab-change', (e) =\u003e {\n      const newContainer = document.querySelector(`#${e.detail.tabId} .chat`);\n      const newContent = newContainer.querySelector('.messages');\n      \n      this.stickToBottom.setScrollElement(newContainer);\n      this.stickToBottom.setContentElement(newContent);\n    });\n  }\n}\n```\n\n## 🤝 Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add some amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## 📝 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## 🙏 Acknowledgments\n\n- Inspired by the React `useStickToBottom` hook from Stackblitz Labs\n- Built for modern web applications that need smooth scrolling behavior\n- Designed with chat applications and live content in mind","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprobably-not%2Fstick-to-bottom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprobably-not%2Fstick-to-bottom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprobably-not%2Fstick-to-bottom/lists"}