VOOZH about

URL: https://dzone.com/articles/scrolling-with-konvajs-and-react

⇱ Scrolling With Konva.js and React


Related

  1. DZone
  2. Coding
  3. Languages
  4. Scrolling With Konva.js and React

Scrolling With Konva.js and React

This article explores a solution for displaying large HTML Canvases efficiently, focusing on limiting the visible area and scrolling through content.

By Apr. 30, 24 · Tutorial
Likes
Comment
Save
2.5K Views

Join the DZone community and get the full member experience.

Join For Free

Let's assume we have a situation where we have a large HTML Canvas, say it is 2,000 x 10,000 pixels in dimensions, with a considerable number of objects depicted on it. Displaying the entire Сanvas at once would lead to performance issues, especially on mobile devices.

In this article, I would like to explore one of the efficient solutions to this problem. The core idea is to limit the visible area to the screen's dimensions and only display a portion of the content. The remaining content can be viewed by scrolling through the document.

Step 1: Preparing the Document

Let's start with preparing the document, and as an example, we will define a list of pages that we want to display on the HTML Canvas:

JavaScript
import imageURL from "./lorem-ipsum.png";

// ...

const pages = [
 { pageId: 1, width: 2016, height: 2264, imageURL, offsetY: 0 },
 { pageId: 2, width: 2016, height: 2264, imageURL, offsetY: 2264 },
 { pageId: 3, width: 2016, height: 2264, imageURL, offsetY: 4528 }
];


For each page, width, height, and an image URL are specified, which need to be displayed on the HTML Canvas. As you may notice, the total size of the content is quite large and equals 2016 x 6792 = 2016 x 2264 * 3. In this example, the document contains only 3 pages, and in the case of more pages (dozens or even hundreds), the content size increases proportionally.

Step 2: Display the Pages

The next step is to display the pages, and for that, we will add a Page component responsible for individually rendering each page:

JavaScript
import { Image } from "react-konva";
import useImage from "use-image";

export default function Page({ width, height, imageURL, offsetY }) {
 const [image] = useImage(imageURL);

 if (!image) {
 return null;
 }

 return (
 <Image
 x={0}
 y={0}
 image={image}
 width={width}
 height={height}
 offsetY={offsetY}
 />
 );
}


The useImage hook is utilized within the component for image loading. It loads an image based on the provided URL and creates a DOM element with the same src value. The loaded image is then passed to the Image component from the “react-konva” library, thereby displaying the page image on the HTML Canvas.

Step 3: Implement a Component

Next, we'll implement a component responsible for displaying the entire list of pages:

JavaScript
import { Layer, Stage } from "react-konva";

import Page from "./Page.js";

// ...

export default function App() {
 return (
 <Stage width={window.innerWidth} height={window.innerHeight}>
 <Layer>
 {pages.map((page) => (
 <Page
 key={page.pageId}
 width={page.width}
 height={page.height}
 imageURL={page.imageURL}
 offsetY={-page.offsetY}
 />
 ))}
 </Layer>
 </Stage>
 );
}


To display each page, we used the previously implemented Page component. Then, we passed the list of pages as children to the Layer and Stage components from the “react-konva” library.

The Layer component serves as a graphical container used for displaying and managing a set of shapes and elements. Each Layer contains a set of Konva.js objects, such as shapes, images, text, etc., which can be added, removed, and modified. Layer components are used to set the hierarchy of objects and control the rendering order of elements.

The Stage component is the top-level component that contains all Layer components and handles events on the HTML Canvas. The Stage is the main component on which graphical objects are displayed.

Step 4: Implement Ability to Scroll Through Pages

And last but not least, let's implement the ability to scroll through the pages. The library's documentation describes several options for navigation. We will implement one of these options, the idea of which is to display only a portion of the content and scroll the remaining content using an external container. When the user scrolls the container, a CSS style transform: translate(scrollTop, scrollLeft) is applied to the container to keep it in place, while simultaneously changing the position of the Stage to scroll the content. This way, we can navigate through the document using scrolling.

Let's implement this approach. For this, we will add a state to store the scroll position and an event handler to keep track of its updated position when scrolling the document:

JavaScript
const [scroll, setScroll] = useState({ left: 0, top: 0 });

...

const handleScroll = useCallback((event) => {
 const { scrollLeft, scrollTop } = event.currentTarget;

 setScroll({ left: scrollLeft, top: scrollTop });
}, []);


We will need the value of the scroll position to set coordinates in the Stage component and as a value for the transform CSS property, as we described above.

JavaScript
const stageStyles = useMemo(() => {
 return { transform: `translate(${scroll.left}px, ${scroll.top}px)` };
}, [scroll]);


We also need to calculate the dimensions of the content, specifically, its width and height. The content's height will be the sum of the heights of all pages, and the content's width will be the width of the widest page.

JavaScript
const contentStyles = useMemo(() => {
 const { width, height } = pages.reduce(
 (acc, page) => ({
 width: Math.max(acc.width, page.width),
 height: acc.height + page.height
 }),
 {
 width: 0,
 height: 0
 }
 );
 return { width, height };
}, []);


Now that we have all the necessary calculations and handlers, let's bring them all together into a single component. The final implementation of the application will be as follows:

JavaScript
import { Layer, Stage } from "react-konva";

import Page from "./Page.js";

// ...

export default function App() {
 const [scroll, setScroll] = useState({ left: 0, top: 0 });

 const handleScroll = useCallback((event) => {
 const { scrollLeft, scrollTop } = event.currentTarget;

 setScroll({ left: scrollLeft, top: scrollTop });
 }, []);

 const stageStyles = useMemo(() => {
 return { transform: `translate(${scroll.left}px, ${scroll.top}px)` };
 }, [scroll]);

 const contentStyles = useMemo(() => {
 const { width, height } = pages.reduce(
 (acc, page) => ({
 width: Math.max(acc.width, page.width),
 height: acc.height + page.height
 }),
 {
 width: 0,
 height: 0
 }
 );
 return { width, height };
 }, []);

 return (
 <div className="Scroll" onScroll={handleScroll}>
 <div className="Content" style={contentStyles}>
 <Stage
 x={-scroll.left}
 y={-scroll.top}
 width={window.innerWidth}
 height={window.innerHeight}
 style={stageStyles}
 >
 <Layer>
 {pages.map((page) => (
 <Page
 key={page.pageId}
 width={page.width}
 height={page.height}
 imageURL={page.imageURL}
 offsetY={-page.offsetY}
 />
 ))}
 </Layer>
 </Stage>
 </div>
 </div>
 );
}

You can view a functional application and explore it in more detail on the CodeSandbox playground.

Conclusion

In this article, we've discussed the implementation of an application that enables viewing documents with a large number of pages. In the next article, we will continue exploring the “react-konva” library and further enhance the functionality of our viewer.

CSS Document HTML Library React (JavaScript library)

Opinions expressed by DZone contributors are their own.

Related

  • React, Angular, and Vue.js: What’s the Technical Difference?
  • Accessibility Basics for Building Telehealth Platforms With React Code Examples
  • How to Merge HTML Documents in Java
  • Creating Scrolling Text With HTML, CSS, and JavaScript

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

Let's be friends: