react-stay-scrolled
v9.0.0
Published
React component to keep content always scrolled down
Downloads
15,178
Readme
react-stay-scrolled
Keep your component, such as message boxes, scrolled down
Live demo
You can see the simplest demo here: Live demo
Install
$ npm install --save react-stay-scrolled
Examples
Run examples:
$ npm ci
$ cd examples
$ npm ci
$ npm start
Usage
import { useRef, useLayoutEffect } from 'react';
import PropTypes from 'prop-types';
import useStayScrolled from 'react-stay-scrolled';
const Messages = ({ messages }) => {
const listRef = useRef();
const { stayScrolled/*, scrollBottom*/ } = useStayScrolled(listRef);
// Typically you will want to use stayScrolled or scrollBottom inside
// useLayoutEffect, because it measures and changes DOM attributes (scrollTop) directly
useLayoutEffect(() => {
stayScrolled();
}, [messages.length])
return (
<ul ref={listRef}>
{messages.map((message, i) => <Message key={i} text={message} />)}
</ul>
);
};
Messages.propTypes = {
messages = PropTypes.array,
}
Another use case is notifying users when there is a new message down the window that they haven't read:
// messages.jsx
import { useState, useRef, useCallback, useLayoutEffect } from 'react';
import PropTypes from 'prop-types';
import useStayScrolled from 'react-stay-scrolled';
import Message from './message.jsx';
const Messages = ({ messages }) => {
const [notifyNewMessage, setNotifyNewMessage] = useState(false);
const ref = useRef();
const { stayScrolled, isScrolled } = useStayScrolled(ref);
// The element just scrolled down - remove new messages notification, if any
const onScroll = useCallback(() => {
if (isScrolled()) setNotifyNewMessage(false);
}, []);
useLayoutEffect(() => {
// Tell the user to scroll down to see the newest messages if the element wasn't scrolled down
setNotifyNewMessage(!stayScrolled());
}, [messages.length])
return (
<div ref={ref} onScroll={onScroll}>
{messages.map((message, i) => <Message key={i} text={message} />)}
{notifyNewMessage && <div>Scroll down to new message</div>}
</div>
);
};
You can use react-spring to animate the scroll:
import { useRef, useCallback, useLayoutEffect } from 'react';
import useStayScrolled from 'react-stay-scrolled';
import { useSpring, animated } from '@react-spring/web';
const SpringStayScrolled = ({
provideControllers,
onScroll,
getRunScroll,
}) => {
const ref = useRef(null);
const [{ scrollTop }, animateScroll] = useSpring(() => ({ scrollTop: 0 }), []);
const runScroll = useCallback(offset => animateScroll.start({
scrollTop: offset,
from: { scrollTop: ref.current ? ref.current.scrollTop : 0 },
}), [animateScroll])
const { scrollBottom } = useStayScrolled(ref, { runScroll });
useLayoutEffect(() => { scrollBottom(); }, []);
return (
<animated.div ref={ref} scrollTop={scrollTop}>
{/* ... */}
</animated.div>
);
};
Arguments
ref
Type: a React ref
, required
A ref
to the DOM element whose scroll position you want to control
options
Type: object
, default:
{
initialScroll: null,
inaccuracy: 0,
runScroll: defaultRunScroll,
}
options.initialScroll
Type: number
, default: null
If provided, the scrolling element will mount scrolled with the provided value. If Infinity
is provided, it will mount scrolled to the bottom.
options.inaccuracy
Type: number
, default: 0
Defines an error margin, in pixels, under which stayScrolled
will still scroll to the bottom
options.runScroll
Type: function: (offset) => undefined
, default: (offset) => { ref.current.scrollTop = offset; }
where ref
is the first value
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 = (domRef) => (offset) => {
dynamics.animate(domRef.current, {
scrollTop: offset,
}, {
type: dynamics[easing],
duration,
});
};
const jqueryRunScroll = (domRef) => (offset) => {
jQuery(domRef.current).animate({ scrollTop: offset }, duration, easing);
};
const velocityRunScroll = (domRef) => (offset) => {
Velocity(
domRef.current.firstChild,
'scroll',
{
container: domRef.current,
easing,
duration,
offset,
}
);
};
Return value
Type: object
, shape: { stayScrolled, scrollBottom, scroll, isScrolled }
Four functions used for controlling scroll behavior.
stayScrolled
Type: function: () => bool
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. Returns true if it performed a scrolled down, false otherwise.
scroll
Type: function: (position: Integer) => void
Scrolls down to the desired position. If given Infinity
, it scrolls to the bottom
scrollDown
Type: function: () => void
Scrolls down the wrapper element, regardless of current position. Equivalent to () => scroll(Infinity)
.
isScrolled
Type: function: () => bool
Returns true if the dom element is scrolled all the way down (within the inaccuracy provided).
License
See the LICENSE file for license rights and limitations (MIT).