Maxime Deranty
Maxime Deranty

Reputation: 11

How to Implement Zoom and Scroll with Canvas in a PDF Viewer with Centered Zoom on Mobile Devices?

I’m building a PDF viewer on a webpage using PDF.js, and I’m facing an issue with implementing both scrolling and zooming features. The user should be able to:

Scroll through the PDF pages (already working). Zoom in/out with pinch gestures (mobile) and double tap (mobile). The zoom should happen centered around the position where the user taps or pinches on the canvas.

Here is my code:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable= no">
    <title>Pdf reader</title>
</head>
    <body class="mobile">
    <style type="text/css">
        body{margin: 0; padding: 0; outline: none !important; font-family: sans-serif; -webkit-user-select: none !important; -ms-user-select: none !important; user-select: none !important; background: #181212;}
        #pdf-container { display: flex; flex-direction: column; gap: 20px; margin-top: 40px;}
        #pdf-container canvas { display: block; margin: 0 auto;}
        div.container_tool_bar{position: fixed; top: 0; left: 0px; z-index: 100; padding: 10px; border: none; width: calc(100svw - 20px); display: flex; justify-content: space-between; background: #181212;}
        div.container_tool_bar button#next{position: relative; border: 1px solid #9F7540; padding: 6px 10px; transition: all 0.3s ease; font-weight: 600; color: #9F7540; font-size: 14px; text-transform: uppercase; font-family: "Montserrat", Arial, sans-serif; background: transparent; cursor: pointer;}
        div.container_tool_bar button#prev{position: relative; border: 1px solid #9F7540; padding: 6px 10px; transition: all 0.3s ease; font-weight: 600; color: #9F7540; font-size: 14px; text-transform: uppercase; font-family: "Montserrat", Arial, sans-serif;  background: transparent; cursor: pointer;}
        div.container_tool_bar div.container_count{position: relative; padding: 6px 10px; transition: all 0.3s ease; font-weight: 600; color: #9F7540 !important; font-size: 14px; text-transform: uppercase; font-family: "Montserrat", Arial, sans-serif;  }
        div.container_tool_bar div.container_count a{color: #9F7540 !important;}

        body.mobile #pdf-container canvas{width: 100%;}
    </style>

    <script src="//mozilla.github.io/pdf.js/build/pdf.mjs" type="module"></script>

    <script type="module">
      var url ="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf";

      // Loaded via <script> tag, create shortcut to access PDF.js exports.
      var { pdfjsLib } = globalThis;

      // The workerSrc property shall be specified.
      pdfjsLib.GlobalWorkerOptions.workerSrc = '//mozilla.github.io/pdf.js/build/pdf.worker.mjs';

      var pdfDoc = null;
      var pages = []; // To store the references to each canvas for navigation
      var pageNum = 1; // Initial page number

      /**
       * Get page info from document, resize canvas accordingly, and render page.
       * @param num Page number.
       */
      function renderPage(num) {
        pdfDoc.getPage(num).then(function(page) {
            let viewport;

            // Si mobile, calculer l'échelle pour que la largeur corresponde à l'écran
            if (document.body.classList.contains('mobile')) {
                const screenWidth = window.innerWidth; // Largeur de l'écran
                viewport = page.getViewport({ scale: 2 }); // Double échelle sur desktop
            } else {
                viewport = page.getViewport({ scale: 2 }); // Double échelle sur desktop
            }

            // Créer un élément canvas pour chaque page et l'ajouter au conteneur
            var canvas = document.createElement("canvas");
            var ctx = canvas.getContext("2d");
            canvas.height = viewport.height;
            canvas.width = viewport.width;
            document.getElementById("pdf-container").appendChild(canvas);

            // Rendre la page PDF dans le contexte du canvas
            var renderContext = {
                canvasContext: ctx,
                viewport: viewport,
            };
            var renderTask = page.render(renderContext);

            // Stocker la référence du canvas pour une utilisation ultérieure
            pages.push(canvas);

            // Attendre la fin du rendu
            renderTask.promise.then(function () {
                console.log("Page rendue : " + num);
            });
        });
    }


      /**
       * Asynchronously downloads PDF.
       */
      pdfjsLib.getDocument(url).promise.then(function(pdfDoc_) {
        pdfDoc = pdfDoc_;
        // document.getElementById('page_count').textContent = pdfDoc.numPages;

        // Render all pages
        for (var i = 1; i <= pdfDoc.numPages; i++) {
          renderPage(i);
        }
      });

      /**
       * Function to scroll to a specific page
       */
      function scrollToPage(pageNum) {
        var canvas = pages[pageNum - 1]; // Get the canvas for the selected page
        if (canvas) {
          // Scroll the page to the top of the selected canvas
          window.scrollTo({
            top: canvas.offsetTop, // Adjust if you want space above the page
            behavior: 'smooth' // Smooth scroll
          });
        }
      }

      /**
       * Function to determine the current visible page based on scroll position
       */
      function getCurrentPage() {
        var currentPage = 1;
        for (var i = 0; i < pages.length; i++) {
            console.log("Element", pages[i], "is in viewport:", isElementInViewport(pages[i]));
            if (isElementInViewport(pages[i])) {
                currentPage = i + 1;

                break;
            }
        }
        return currentPage;
      }

      /**
         * Check if an element is currently in the viewport
         */
        function isElementInViewport(el) {
          var rect = el.getBoundingClientRect();
          
          // Vérifier si l'élément est visible même partiellement dans la fenêtre
          return rect.top < window.innerHeight && rect.bottom >= 0 && rect.left < window.innerWidth && rect.right >= 0;
        }


      // Add event listeners to navigation buttons
      document.getElementById('prev').addEventListener('click', function() {
            // Get the current visible page number
            var currentPage = getCurrentPage();
            console.log(currentPage);
            pageNum = currentPage - 1;
            scrollToPage(pageNum);
      });

      document.getElementById('next').addEventListener('click', function() {
            // Get the current visible page number
            var currentPage = getCurrentPage();
            console.log(currentPage);
            pageNum = currentPage + 1;
            scrollToPage(pageNum);
      });

    </script>
    <div class="container_tool_bar">
        <button id="prev">Précédente</button>
        <div class="container_count">
            <a href="https://www.google.com/">Retour au site</a>
            <!-- <span>Total Pages: <span id="page_count"></span></span> -->
        </div>
        <button id="next">Suivante</button>
    </div>
    <div id="pdf-container"></div>

</body>
</html>

I’ve used the getBoundingClientRect method to track the canvas position and tried adjusting the scale when pinch gestures or double tap events occur, but I haven't been able to achieve the correct zooming behavior.

I also explored event listeners for touch and mouse events, but my implementation doesn't seem to properly scale the canvas from the pointer’s location.

Upvotes: 0

Views: 39

Answers (0)

Related Questions