react-stay-scrolled
Keep your component, such as message boxes, scrolled down.
Install
$ npm install --save react-stay-scrolled
Usage
react-stay-scrolled
injects methods stayScrolled
and scrollBottom
to its children through the scrolled
higher order component:
// messages.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import StayScrolled from 'react-stay-scrolled';
import Message from './message.jsx';
const Messages = ({ messages }) => (
<StayScrolled component="div">
{
messages.map(
(message, i) => <Message key={i} text={message} />
)
}
</StayScrolled>
);
Messages.propTypes = {
messages = PropTypes.array
}
// message.jsx
import React, { Component, propTypes } from 'react';
import { scrolled } from 'react-stay-scrolled';
class Message extends Component {
static propTypes = {
stayScrolled: PropTypes.func,
scrollBottom: PropTypes.func,
}
componentDidMount() {
const { stayScrolled, scrollBottom } = this.props;
// Make the parent StayScrolled component scroll down if it was already scrolled
stayScrolled();
// Make the parent StayScrolled component scroll down, even if not completely scrolled down
// scrollBottom();
}
render() {
return (<div>{this.props.text}</div>);
}
}
export default scrolled(Message);
The methods can also be called from the parent element:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import StayScrolled from 'react-stay-scrolled';
class Messages extends Component {
componentDidUpdate(prevProps) {
if(prevProps.messages.length < this.props.messages.length)
this.stayScrolled(); // Or: this.scrollBottom
}
storeScrolledControllers = ({ stayScrolled, scrollBottom }) => {
this.stayScrolled = stayScrolled;
this.scrollBottom = scrollBottom;
}
render() {
const { messages } = this.props;
return (
<StayScrolled provideControllers={this.storeScrolledControllers}>
{
messages.map(
(message, i) => <Message key={i} text={message} />
)
}
</StayScrolled>
);
}
}
Another use case is notifying users when there is a new message down the window that they haven't read:
// messages.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import StayScrolled from 'react-stay-scrolled';
import Message from './message.jsx';
class Messages extends Component {
state = {
notifyNewMessage: false
}
onStayScrolled = (isScrolled) => {
// Tell the user to scroll down to see the newest messages if the element wasn't scrolled down
this.setState({ notifyNewMessage: !isScrolled });
}
onScrolled = () => {
// The element just scrolled down - remove new messages notification, if any
this.setState({ notifyNewMessage: false });
}
render() {
const { messages } = this.props;
const { notifyNewMessage } = this.state;
return (
<div>
<StayScrolled
component="div"
onStayScrolled={this.onStayScrolled}
onScrolled={this.onScrolled}
>
{
messages.map(
(message, i) => <Message key={i} text={message} />
)
}
</StayScrolled>
{ notifyNewMessage && <div>Scroll down to new message</div> }
</div>
);
}
}
Props
component
Type: a React component
, default: "div"
Passed to React.createElement
, used to wrap the children
debug
Type: function(msg)
, default () => {}
Used to log debug messages in StayScrolled, usually (msg) => { console.log(msg); }
stayInaccuracy
Type: number
, default: 0
Defines an error margin, in pixels, under which stayScrolled
will still scroll to the bottom
provideControllers
Type: function({ stayScrolled, scrollBottom })
, default: null
Used for getting scroll controllers to the parent elements, see the controller API below
onStayScrolled
Type: function(scrolled)
Fires after executing stayScrolled
, notifies back whether or not the component is scrolled down. Useful to know if you need to notify the user about new messages
scrolled
Type: boolean
True if the call to stayScrolled
performed a scroll to bottom, false otherwise
onScrolled
Type: function()
Fires when the element scrolls down, useful to remove the new message notification
runScroll
Type: function(dom, offset)
, default: (dom, offset) => { dom.scrollTop = offset; }
Used for animating dom scrolling. You can use dynamic.js, Velocity, jQuery, or your favorite animation library. Here are examples of possible, tested runScroll
values:
const easing = 'linear';
const duration = 100;
const dynamicsRunScroll = (dom, offset) => {
dynamics.animate(dom, {
scrollTop: offset,
}, {
type: dynamics[easing],
duration,
});
};
const jqueryRunScroll = (dom, offset) => {
jQuery(dom).animate({ scrollTop: offset }, duration, easing);
};
const velocityRunScroll = (dom, offset) => {
Velocity(
dom.firstChild,
'scroll',
{
container: dom,
easing,
duration,
offset,
}
);
};
Controllers
Two methods used for controlling scroll behavior.
Can be accessed by children by injecting into props with scrolled
higher order component, or via context.
Can be accessed by parents by passing provideControllers
prop to StayScrolled
.
stayScrolled
Type: function(notify = true)
Scrolls down the element if it was already scrolled down - useful for when a user is reading previous messages, and you don't want to interrupt
notify
Type: boolean
optional, default true
.
If true
, it fires an onStayScrolled
event after execution, notifying whether or not the component stayed scrolled
scrollDown
Type: function()
Scrolls down the wrapper element, regardless of current position
Higher order component
scrolled
Injects the above controllers, stayScrolled
and scrollBottom
to the props of a child element of StayScrolled
TODO
- Try to automate scrolling on some of the use-cases
- Improve examples