https://github.com/probably-not/stick-to-bottom
A lightweight vanilla JavaScript library that automatically sticks to the bottom of a container and smoothly animates content while new items are added
https://github.com/probably-not/stick-to-bottom
Last synced: 5 months ago
JSON representation
A lightweight vanilla JavaScript library that automatically sticks to the bottom of a container and smoothly animates content while new items are added
- Host: GitHub
- URL: https://github.com/probably-not/stick-to-bottom
- Owner: probably-not
- License: mit
- Created: 2025-07-17T19:42:18.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-12-03T12:28:04.000Z (8 months ago)
- Last Synced: 2025-12-06T16:18:57.415Z (8 months ago)
- Language: JavaScript
- Size: 96.7 KB
- Stars: 0
- Watchers: 0
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
# stick-to-bottom
**DISCLAIMER: THIS LIBRARY WAS 100% VIBE CODED BY CLAUDE CODE SONNET.**
**[Vibe-coding methodology can be found here](./vibing/README.md)**
[](https://www.npmjs.com/package/@probably-not/stick-to-bottom)
[](https://www.npmjs.com/package/@probably-not/stick-to-bottom)
[](https://opensource.org/licenses/MIT)
A 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.
## ✨ Features
- **Framework Agnostic**: Pure vanilla JavaScript, works with any framework or no framework
- **Zero Dependencies**: No external dependencies, lightweight and fast
- **Smooth Animations**: Velocity-based spring animations with configurable parameters
- **Smart Scroll Detection**: Distinguishes between user scrolling and programmatic scrolling
- **Resize Handling**: Automatically handles content size changes using ResizeObserver
- **Mobile Friendly**: Works seamlessly on touch devices
- **TypeScript Support**: Full TypeScript definitions included
- **Escape Detection**: Users can scroll up to "escape" the sticky behavior
- **Selection Aware**: Pauses scrolling when user is selecting text
## 🚀 Installation
```bash
npm install @probably-not/stick-to-bottom
```
```bash
yarn add @probably-not/stick-to-bottom
```
```bash
pnpm add @probably-not/stick-to-bottom
```
## 📖 Usage
### Basic Usage
```javascript
import StickToBottom from '@probably-not/stick-to-bottom';
// Get your container and content elements
const container = document.querySelector('.chat-container');
const content = document.querySelector('.messages');
// Create the stick-to-bottom instance
const stickToBottom = new StickToBottom(container, content);
// Add new messages
const newMessage = document.createElement('div');
newMessage.textContent = 'Hello world!';
content.appendChild(newMessage);
// The container will automatically scroll to bottom with smooth animation
```
### With Options
```javascript
const stickToBottom = new StickToBottom(container, content, {
// Spring animation configuration
damping: 0.7, // How much to damp the animation (0-1)
stiffness: 0.05, // Animation stiffness
mass: 1.25, // Animation mass
// Behavior options
initial: 'smooth', // Scroll to bottom on initialization
resize: 'smooth', // How to handle resize events
// Custom target calculation
targetScrollTop: (target, { scrollElement, contentElement }) => {
return target - 50; // Leave 50px from bottom
}
});
```
### Event Handling
```javascript
// Listen for state changes
const unsubscribe = stickToBottom.on('bottomChange', (isAtBottom) => {
const scrollButton = document.querySelector('.scroll-to-bottom');
scrollButton.style.display = isAtBottom ? 'none' : 'block';
});
// Listen for escape events
stickToBottom.on('escapeChange', (hasEscaped) => {
console.log('User has escaped sticky behavior:', hasEscaped);
});
// Listen for all state changes
stickToBottom.on('stateChange', (state) => {
console.log('State changed:', state);
});
// Clean up
unsubscribe();
```
### Programmatic Scrolling
```javascript
// Scroll to bottom with default animation
stickToBottom.scrollToBottom();
// Scroll with custom animation
stickToBottom.scrollToBottom({
animation: 'instant', // or spring config object
duration: 500, // wait 500ms before allowing completion
ignoreEscapes: true // ignore user scroll during animation
});
// Scroll with promise handling
stickToBottom.scrollToBottom().then((success) => {
if (success) {
console.log('Scrolled to bottom successfully');
} else {
console.log('Scroll was cancelled');
}
});
```
## 📚 API Reference
### Constructor
```javascript
new StickToBottom(scrollElement, contentElement, options)
```
**Parameters:**
- `scrollElement` (HTMLElement): The scrollable container
- `contentElement` (HTMLElement): The content element to observe
- `options` (Object, optional): Configuration options
**Options:**
- `damping` (number, default: 0.7): Animation damping (0-1)
- `stiffness` (number, default: 0.05): Animation stiffness
- `mass` (number, default: 1.25): Animation mass
- `initial` (boolean|string|Object, default: true): Initial scroll behavior
- `resize` (string|Object, default: inherited): Resize scroll behavior
- `targetScrollTop` (function): Custom target scroll calculation
### Methods
#### `scrollToBottom(options)`
Scrolls to the bottom with optional configuration.
**Parameters:**
- `options.animation` (string|Object): Animation configuration ('instant' or spring config)
- `options.duration` (number|Promise): Duration to wait before completion
- `options.wait` (number|boolean): Wait time before starting
- `options.preserveScrollPosition` (boolean): Don't change isAtBottom state
- `options.ignoreEscapes` (boolean): Ignore user scroll during animation
**Returns:** Promise - Resolves to true if successful
#### `on(event, handler)`
Subscribe to events.
**Events:**
- `bottomChange`: Fired when isAtBottom state changes
- `escapeChange`: Fired when user escapes sticky behavior
- `nearBottomChange`: Fired when near bottom state changes
- `stateChange`: Fired on any state change
**Returns:** Function to unsubscribe
#### `off(event, handler)`
Unsubscribe from events.
#### `setScrollElement(element)`
Change the scroll element.
#### `setContentElement(element)`
Change the content element.
#### `destroy()`
Clean up all event listeners and resources.
### Properties
#### `state`
Current state object containing:
- `isAtBottom` (boolean): Whether currently at bottom
- `escapedFromLock` (boolean): Whether user has scrolled up
- `isNearBottom` (boolean): Whether near bottom (within offset)
- `scrollTop` (number): Current scroll position
- `targetScrollTop` (number): Target scroll position
#### `scrollTop` (getter/setter)
Get or set the current scroll position.
#### `isNearBottom` (getter)
Check if scroll position is near bottom.
## 🔧 Configuration
### Animation Configuration
```javascript
{
damping: 0.7, // How much to slow down the animation (0 = no damping, 1 = full damping)
stiffness: 0.05, // How quickly animation reaches target (higher = faster)
mass: 1.25 // Inertial mass (higher = slower, more momentum)
}
```
### Behavior Options
```javascript
{
initial: 'smooth', // Scroll on initialization: true, false, 'instant', 'smooth', or animation config
resize: 'smooth', // Scroll on resize: 'instant', 'smooth', or animation config
targetScrollTop: (target, elements) => {
// Custom target calculation
return target - 100; // Leave 100px from bottom
}
}
```
## 🌟 Advanced Usage
### Custom Animation Presets
```javascript
const animations = {
bounce: { damping: 0.5, stiffness: 0.1, mass: 0.8 },
gentle: { damping: 0.8, stiffness: 0.03, mass: 1.5 },
snappy: { damping: 0.6, stiffness: 0.08, mass: 1.0 }
};
const stickToBottom = new StickToBottom(container, content, {
...animations.gentle,
resize: animations.snappy
});
```
### Dynamic Element Management
```javascript
class DynamicChat {
constructor() {
this.stickToBottom = new StickToBottom(null, null);
this.setupDynamicElements();
}
setupDynamicElements() {
// Change containers dynamically
document.addEventListener('tab-change', (e) => {
const newContainer = document.querySelector(`#${e.detail.tabId} .chat`);
const newContent = newContainer.querySelector('.messages');
this.stickToBottom.setScrollElement(newContainer);
this.stickToBottom.setContentElement(newContent);
});
}
}
```
## 🤝 Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## 📝 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 🙏 Acknowledgments
- Inspired by the React `useStickToBottom` hook from Stackblitz Labs
- Built for modern web applications that need smooth scrolling behavior
- Designed with chat applications and live content in mind