In this example, we will display a progress bar indicating where the current scroll position is and how long to reach the end of document.
The progress bar will be displayed under the toolbar created by the
Default Layout plugin.
To do that, we need to handle the `scroll`
event of the element that contains all pages.
Create a plugin
The plugin will serve two things exactly:
- Access to the container of all pages
- Provide a component named
`ReadingIndicator`
to display the reading progress bar
A plugin that extends from `Plugin`
is able to retrieve the container of all pages via the `install`
function:
import * as React from 'react';
import { createStore, Plugin, PluginFunctions } from '@react-pdf-viewer/core';
interface StoreProps {
getPagesContainer?(): HTMLElement;
}
interface ReadingIndicatorPlugin extends Plugin {
ReadingIndicator: () => React.ReactElement;
}
const readingIndicatorPlugin = (): ReadingIndicatorPlugin => {
const store = React.useMemo(() => createStore<StoreProps>({}), []);
const ReadingIndicatorDecorator = () => <ReadingIndicator store={store} />;
return {
install: (pluginFunctions: PluginFunctions) => {
store.update('getPagesContainer', pluginFunctions.getPagesContainer);
},
ReadingIndicator: ReadingIndicatorDecorator,
};
};
export default readingIndicatorPlugin;
Usually, the `store`
is a central place to track the internal states of a plugin. Different components of plugin can communicate to each other and the main `Viewer`
component via `store`
.
As you see in the sample code above, the `ReadingIndicator`
component accepts the `store`
instance. From there, we can access the container of all pages, and handle its `scroll`
event:
const ReadingIndicator = ({ store }) => {
const handleScroll = (e: Event) => {
};
const handlePagesContainer = () => {
const getPagesContainer = store.get('getPagesContainer');
if (!getPagesContainer) {
return;
}
const pagesEle = getPagesContainer();
pagesEle.addEventListener('scroll', handleScroll);
};
React.useLayoutEffect(() => {
store.subscribe('getPagesContainer', handlePagesContainer);
return () => store.unsubscribe('getPagesContainer', handlePagesContainer);
}, []);
};
It is NOT recomended to handle the scroll event without debouncing the handler. An expensive handler can slow down the application.
It is up to you to implement it yourself or choose from popular libraries such as
lodash's debounce
or
Underscore's debounce.
It is quite easy to calculate the current scroll position and how many percentages the user has been scrolling in total of the height of container:
const ReadingIndicator = ({ store }) => {
const [percentages, setPercentages] = React.useState(0);
const handleScroll = (e: Event) => {
const target = e.target;
if (target instanceof HTMLDivElement) {
const p = Math.floor((100 * target.scrollTop) / (target.scrollHeight - target.clientHeight));
setPercentages(Math.min(100, p));
}
};
};
The internal state `percentages`
indicates how far the user has been scrolling:
const ReadingIndicator = ({ store }) => {
return (
<div
style={{
height: '4px',
}}
>
<div
style={{
backgroundColor: 'rgb(53, 126, 221)',
height: '100%',
width: `${percentages}%`,
}}
/>
</div>
);
};
Register the plugin
Like any built-in
plugins, we can create a new instance of a given plugin and register it with the main
`Viewer`
component:
const readingIndicatorPluginInstance = readingIndicatorPlugin();
<Viewer
fileUrl={...}
plugins={[
readingIndicatorPluginInstance,
...
]}
/>
Display the reading progress bar
The reading progress bar can be taken from the instance of plugin created in the previous section:
const { ReadingIndicator } = readingIndicatorPluginInstance;
The following code demonstrates how we can customize the toolbar:
import { defaultLayoutPlugin, ToolbarProps } from '@react-pdf-viewer/default-layout';
const renderToolbar = React.useCallback(
(Toolbar: (props: ToolbarProps) => React.ReactElement) => (
<>
<Toolbar />
<div
style={{
bottom: '-0.25rem',
position: 'absolute',
left: 0,
width: '100%',
}}
>
<ReadingIndicator />
</div>
</>
),
[]
);
const defaultLayoutPluginInstance = defaultLayoutPlugin({
renderToolbar,
});
Last but not least, in order to make the reading progress bar displayed right below the toolbar container, we use a negative value for the `bottom`
property as you see above.
Scroll in the pages container to see the reading progress bar (The sample code)
See also