React、pdfjs-dist、pubsub-js实现简易自定义PDF页面

目录

0、简单效果展示

1、安装并使用 pdfjs-dist、pubsub-js,初始化工作

2、使用 pdfjs-dist 绘制给定PDF的首页或整体

3、侧边的略缩图列表

4、放大或缩小中心区域的 PDF

5、滚动到指定PDF页面的位置

6、点击左侧略缩图列表中的某略缩页,中间的展示区滚动到该页的位置

7、完整实现代码

 (7-1)displayPDF.js

(7-2)showPDF.jsx

 (7-3)showPDF.scss

(7-4)App.js

 参考:


0、简单效果展示

1、安装并使用 pdfjs-dist、pubsub-js,初始化工作

       安装pdfjs-dist:

npm i pdfjs-dist

        引入并配置工作者源文件:

import * as PDF from 'pdfjs-dist'

import workerSrc from 'pdfjs-dist/build/pdf.worker.entry'
PDF.GlobalWorkerOptions.workerSrc = workerSrc;

        安装 pubsub-js(消息订阅与发布):

npm i pubsub-js

        引入到项中:

import pubsub from 'pubsub-js'

        定义一个用于包裹绘制 pdf 的 canvas 列表的容器:

// 承装所有 canvas 的容器的类名
const allCanvasParentNodeClassName = 'canvas-parent-container';

2、使用 pdfjs-dist 绘制给定PDF的首页或整体

        封装函数 displayPDF 实现该功能。

        该函数接收六个参数:

        (1)给定 pdf 的地址;

        (2)承载要展示pdf区域的父元素(dom);

        (3)是否展示完整个pdf(false 就是只展示 pdf 的首页);

        (4)指定 canvas 的类名(PDF的每一页都会在一个canvas画布上绘制,为这一组画布确定一个共同的类名);

        (5)自定义pdf的缩放倍数;

        (6)pdf渲染完成后的一个回调函数。

/* 
    绘制整个pdf,一个canvas绘制一页
    传入 pdf 的地址、父容器标签元素、是否展示全部(false 即展示首页) 、指定canvas类名, 自定义缩放大小、自定义内联样式
*/
function displayPDF(pdfURL, parentNode, isAllPages, canvasClassName, customScale, callback){
    // 使用 PDF 加载指定的 .pdf 文档
    const loadingTask = PDF.getDocument(pdfURL);

    loadingTask.promise.then(async pdf=>{
        // pdf._pdfInfo.numPages 为 pdf 的总页数,isAllPage 为boolean值,表示是否展示全部
        const pageNumber = isAllPages ? pdf._pdfInfo.numPages : 1;

        /*         
            当前显示设备的物理像素分辨率与CSS像素分辨率之比,它告诉浏览器应使用多少屏幕实际像素来绘制单个CSS像素
            <canvas>可能在视网膜屏幕上显得太模糊。 使用window.devicePixelRatio确定应添加多少额外的像素密度以使图像更清晰。 
        */
        const outputScale = window.devicePixelRatio || 1;

        const allCanvasParentNode = document.createElement('div');
        allCanvasParentNode.className = allCanvasParentNodeClassName;
        allCanvasParentNode.style = `
            width: 100%;
            height: 100%;
            overflow: auto;
            padding: 0;
            margin: 0;
            box-sizing: border-box;
            display: flex;
            flex-direction: column;
            align-items: center;
        `
        // 将 canvas 列表的容器添加到给定的容器中
        parentNode.appendChild(allCanvasParentNode);

        // 根据 isAllPages 确定渲染多少页
        mainAreaAsyncRender(pdf, pageNumber, 1, customScale, canvasClassName, outputScale, allCanvasParentNode, callback);
    })
}

        其中 mainAreaAsyncRender 函数为解决 pdf 页面异步渲染的一个方案。即 使 PDF 的每一页在渲染完成后,都可以直接呈现到页面上,不必等待 PDF 的所有页面都渲染完成再一次过呈现到页面上(这种一般直接使用 for 循环来完成),避免用户过多的等待。

        该函数接收以下几个参数:

        (1)使用 pdfjs-dist 的 getDocument 加载后进行期约构造成功后返回的 pdf 对象;

        (2)当前 pdf 的总页数;

        (3)当前所绘制的是第几页;

        (4)自定义的缩放比例;

        (5)自定义的 canvas 类名;

        (6)当前显示设备的物理像素分辨率与CSS像素分辨率之比;

        (7)承装所有 canvas 的容器(dom元素);

        (8)所有页面渲染完成后执行的回调函数(这里设置回调函数有一个参数,为该pdf的总页数,用于展示pdf的总页数);

// 主要展示区 异步渲染解决方案,使之一上来就有页面
async function mainAreaAsyncRender(pdf, pageNumber, i, customScale, canvasClassName, outputScale, allCanvasParentNode, callback){
    // 获取 pdf 指定页
    let page = await pdf.getPage(i);
    // 设置 canvas 整体缩放的倍数
    let scale = customScale || 1.4;
    let viewport = page.getViewport({ scale });

    // 创建 canvas 共同的容器
    let canvas = document.createElement('canvas');
    canvas.className = canvasClassName || '';
    let context = canvas.getContext("2d");

    // 设置 canvas 和内容 大小
    canvas.width = Math.floor(viewport.width * outputScale);
    canvas.height = Math.floor(viewport.height * outputScale);
    canvas.style.width = Math.floor(viewport.width) + "px";
    canvas.style.height = Math.floor(viewport.height) + "px";
    canvas.style.margin = '10px 0';

    let transform = (outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null);

    // 将指定页渲染到 canvas
    let renderContext = {
        canvasContext: context,
        transform,
        viewport,
    };
    page.render(renderContext);

    // 将当前 canvas(当前页) 添加到父容器中
    allCanvasParentNode.appendChild(canvas);

    i++;
    if(i <= pageNumber){
        requestAnimationFrame(()=>
            mainAreaAsyncRender(pdf, pageNumber, i, customScale, canvasClassName, outputScale, allCanvasParentNode, callback));
    }else{
        // 传入 pdf 总页数
        if(callback) callback(pdf._pdfInfo.numPages);

        // 发布渲染完成的消息
        pubsub.publish('renderFinish');
    }
};

        渲染完成后, 发布消息 ‘renderFinish’ 告诉组件渲染已完成。

3、侧边的略缩图列表

/* 
    侧边导航略缩图
    参数:pdf地址、父容器元素、中心展示区域设置的canvas的类名,设置当前canvas的类名,自定义当前canvas列表的缩放大小,回调函数
*/
function displayPDFLeftNav(pdfURL, parentNode, centerAreaCanvasClassName, canvasClassName, customScale){
    // 使用 PDF 加载指定的 .pdf 文档
    const loadingTask = PDF.getDocument(pdfURL);

    loadingTask.promise.then(async pdf=>{
        // pdf._pdfInfo.numPages 为 pdf 的总页数,isAllPage 为boolean值,表示是否展示全部
        const pageNumber =  pdf._pdfInfo.numPages;

        /*         
            当前显示设备的物理像素分辨率与CSS像素分辨率之比,它告诉浏览器应使用多少屏幕实际像素来绘制单个CSS像素
            <canvas>可能在视网膜屏幕上显得太模糊。 使用window.devicePixelRatio确定应添加多少额外的像素密度以使图像更清晰。 
        */
        const outputScale = window.devicePixelRatio || 1;

        const allCanvasParentNode = document.createElement('div');
        allCanvasParentNode.className = allCanvasParentNodeClassName;
        allCanvasParentNode.style = `
            width: 100%;
            height: 100%;
            overflow: auto;
            padding: 0;
            margin: 0;
            box-sizing: border-box;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 10px 0;
        `
        // 将 canvas 列表的容器添加到给定的容器中
        parentNode.appendChild(allCanvasParentNode);

        // 根据 isAllPages 确定渲染多少页
        let i = 1;
        navAreaAsyncRender(pdf, pageNumber, i, customScale, canvasClassName, outputScale, allCanvasParentNode, centerAreaCanvasClassName);
    })
}

        其中 navAreaAsyncRender 函数也是一个异步的渲染方案。

// 侧边略缩图列表 异步渲染解决方案
async function navAreaAsyncRender(pdf, pageNumber, i, customScale, canvasClassName,
     outputScale, allCanvasParentNode, centerAreaCanvasClassName){
    // 获取 pdf 指定页
    let page = await pdf.getPage(i);
    // 设置 canvas 整体缩放的倍数
    let scale = customScale || 0.35;
    let viewport = page.getViewport({ scale });

    // 创建 canvas 共同的容器
    let canvas = document.createElement('canvas');
    canvas.className = canvasClassName || 'canvas-left-nav-class-name';
    let context = canvas.getContext("2d");

    // 设置 canvas 内容 大小,添加悬浮事件
    canvas.id = `canvas-l-${i}`;
    canvas.width = Math.floor(viewport.width * outputScale);
    canvas.height = Math.floor(viewport.height * outputScale);
    canvas.style.width = Math.floor(viewport.width) + "px";
    canvas.style.height = Math.floor(viewport.height) + "px";
    canvas.style.border = '1px solid #888';
    canvas.style.margin = '20px 0 10px 0';
    canvas.addEventListener('mouseover',()=>{
        canvas.style.cursor = 'pointer';
        canvas.style.boxShadow = '2px 2px 1px 1px #888';
    });
    canvas.addEventListener('mouseleave',()=>{
        canvas.style.cursor = '';
        canvas.style.boxShadow = '';
    })
    canvas.addEventListener('click',(event)=>{
        clickNavToSee(canvas, centerAreaCanvasClassName);
    })

    let transform = (outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null);

    // 将指定页渲染到 canvas
    let renderContext = {
        canvasContext: context,
        transform,
        viewport,
    };
    page.render(renderContext);

    // 底部的页码标注
    let span = document.createElement('span');
    span.innerText = i;
    span.style = 'font-size: 0.85rem;color: #888;';

    // 将当前 canvas(当前页) 添加到父容器中
    allCanvasParentNode.appendChild(canvas);
    allCanvasParentNode.appendChild(span);

    i ++;
    if(i < pageNumber){
        requestAnimationFrame(()=>
            navAreaAsyncRender(pdf, pageNumber, i, customScale, canvasClassName, 
                outputScale, allCanvasParentNode, centerAreaCanvasClassName));
            
    }
}

4、放大或缩小中心区域的 PDF

        最好改变 customScale 进行异步重绘操作。

        如果直接使用 document.querySelecterAll 方法 + canvas 类名(这个canvas类名是自定义的,即函数displayPDF、resizePDF 中要传的参数 canvasClassName)的方式获取到所有的canvas,然后通过遍历的方式给他们分别设置属性 taransform scale 来实现放大或缩小的效果,会导致PDF变模糊而影响阅览体验。

/* 
    在已经绘制过pdf的基础上 放大 canvas 和 pdf (重绘)
    直接修改样式属性 transform scale 会导致 pdf 失真,需要重绘
    传入 pdf地址、canvas类名、自定义缩放大小
*/
function resizePDF(pdfURL, canvasClassName, customScale){
    // 使用 PDF 加载指定的 .pdf 文档
    const loadingTask = PDF.getDocument(pdfURL);

    loadingTask.promise.then(async pdf=>{
        const canvasList = document.querySelectorAll(`.${canvasClassName}`);

        /*         
            当前显示设备的物理像素分辨率与CSS像素分辨率之比,它告诉浏览器应使用多少屏幕实际像素来绘制单个CSS像素
            <canvas>可能在视网膜屏幕上显得太模糊。 使用window.devicePixelRatio确定应添加多少额外的像素密度以使图像更清晰。 
        */
        const outputScale = window.devicePixelRatio || 1;

        // 根据 isAllPages 确定渲染多少页(从 0 开始)
        resizeAsyncRender(pdf, canvasList, 0, customScale, outputScale);
    })
}

        其中 resizeAsyncRender 为重绘时的异步解决方法。

// 根据放大和缩小的比例重绘中间pdf展示区
async function resizeAsyncRender(pdf, canvasList, i, customScale, outputScale){
    let canvas = canvasList[i];
    // 获取 pdf 指定页
    let page = await pdf.getPage(i + 1);
    // 设置 canvas 整体缩放的倍数
    let scale = customScale || 1.4;
    let viewport = page.getViewport({ scale });

    let context = canvas.getContext("2d");

    // 设置 canvas 和内容 大小
    canvas.width = Math.floor(viewport.width * outputScale);
    canvas.height = Math.floor(viewport.height * outputScale);
    canvas.style.width = Math.floor(viewport.width) + "px";
    canvas.style.height = Math.floor(viewport.height) + "px";

    let transform = (outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null);

    // 将指定页渲染到 canvas
    let renderContext = {
        canvasContext: context,
        transform,
        viewport,
    };
    page.render(renderContext);
    i++;
    if(i < canvasList.length){
        requestAnimationFrame(()=>resizeAsyncRender(pdf, canvasList, i, customScale, outputScale));
    }else{
        // 发布重新渲染完成的消息
        pubsub.publish("resizeFinish");
    }
}

         当重新渲染完成后,将发布 “resizeFinish” 消息传递重新渲染已完成的信号。

5、滚动到指定PDF页面的位置

/* 
    滚动到指定的pdf页面位置(num 为 pdf 第几页 从 1 开始)
*/
function scrollIntoPage(num, canvasClassName){
    document.querySelectorAll(`.${canvasClassName}`)[num - 1].scrollIntoView({
        behavior:'smooth',
    });
}

6、点击左侧略缩图列表中的某略缩页,中间的展示区滚动到该页的位置

/* 
    点击左侧的略缩栏的某页,将该页滚动到展示区
*/
function clickNavToSee(canvasNode, centerAreaCanvasClassName){
    let index = canvasNode.id.split('-')[2].trim();
    document.querySelectorAll(`.${centerAreaCanvasClassName}`)[index - 1].scrollIntoView({
        behavior:'smooth'
    });
}

7、完整实现代码

        注意:本地的 pdf 要放在public文件夹下;

 (7-1)displayPDF.js

import * as PDF from 'pdfjs-dist'
import pubsub from 'pubsub-js'

import workerSrc from 'pdfjs-dist/build/pdf.worker.entry'
PDF.GlobalWorkerOptions.workerSrc = workerSrc;

// 承装所有 canvas 的容器的类名
const allCanvasParentNodeClassName = 'canvas-parent-container';

/* 
    绘制整个pdf,一个canvas绘制一页
    传入 pdf 的地址、父容器标签元素、是否展示全部(false 即展示首页) 、指定canvas类名, 自定义缩放大小、自定义内联样式
*/
function displayPDF(pdfURL, parentNode, isAllPages, canvasClassName, customScale, callback){
    // 使用 PDF 加载指定的 .pdf 文档
    const loadingTask = PDF.getDocument(pdfURL);

    loadingTask.promise.then(async pdf=>{
        // pdf._pdfInfo.numPages 为 pdf 的总页数,isAllPage 为boolean值,表示是否展示全部
        const pageNumber = isAllPages ? pdf._pdfInfo.numPages : 1;

        /*         
            当前显示设备的物理像素分辨率与CSS像素分辨率之比,它告诉浏览器应使用多少屏幕实际像素来绘制单个CSS像素
            <canvas>可能在视网膜屏幕上显得太模糊。 使用window.devicePixelRatio确定应添加多少额外的像素密度以使图像更清晰。 
        */
        const outputScale = window.devicePixelRatio || 1;

        const allCanvasParentNode = document.createElement('div');
        allCanvasParentNode.className = allCanvasParentNodeClassName;
        allCanvasParentNode.style = `
            width: 100%;
            height: 100%;
            overflow: auto;
            padding: 0;
            margin: 0;
            box-sizing: border-box;
            display: flex;
            flex-direction: column;
            align-items: center;
        `
        // 将 canvas 列表的容器添加到给定的容器中
        parentNode.appendChild(allCanvasParentNode);

        // 根据 isAllPages 确定渲染多少页
        mainAreaAsyncRender(pdf, pageNumber, 1, customScale, canvasClassName, outputScale, allCanvasParentNode, callback);
    })
}

/* 
    侧边导航略缩图
    参数:pdf地址、父容器元素、中心展示区域设置的canvas的类名,设置当前canvas的类名,自定义当前canvas列表的缩放大小,回调函数
*/
function displayPDFLeftNav(pdfURL, parentNode, centerAreaCanvasClassName, canvasClassName, customScale){
    // 使用 PDF 加载指定的 .pdf 文档
    const loadingTask = PDF.getDocument(pdfURL);

    loadingTask.promise.then(async pdf=>{
        // pdf._pdfInfo.numPages 为 pdf 的总页数,isAllPage 为boolean值,表示是否展示全部
        const pageNumber =  pdf._pdfInfo.numPages;

        /*         
            当前显示设备的物理像素分辨率与CSS像素分辨率之比,它告诉浏览器应使用多少屏幕实际像素来绘制单个CSS像素
            <canvas>可能在视网膜屏幕上显得太模糊。 使用window.devicePixelRatio确定应添加多少额外的像素密度以使图像更清晰。 
        */
        const outputScale = window.devicePixelRatio || 1;

        const allCanvasParentNode = document.createElement('div');
        allCanvasParentNode.className = allCanvasParentNodeClassName;
        allCanvasParentNode.style = `
            width: 100%;
            height: 100%;
            overflow: auto;
            padding: 0;
            margin: 0;
            box-sizing: border-box;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 10px 0;
        `
        // 将 canvas 列表的容器添加到给定的容器中
        parentNode.appendChild(allCanvasParentNode);

        // 根据 isAllPages 确定渲染多少页
        let i = 1;
        navAreaAsyncRender(pdf, pageNumber, i, customScale, canvasClassName, outputScale, allCanvasParentNode, centerAreaCanvasClassName);
    })
}

/* 
    在已经绘制过pdf的基础上 放大 canvas 和 pdf (重绘)
    直接修改样式属性 transform scale 会导致 pdf 失真,需要重绘
    传入 pdf地址、canvas类名、自定义缩放大小
*/
function resizePDF(pdfURL, canvasClassName, customScale){
    // 使用 PDF 加载指定的 .pdf 文档
    const loadingTask = PDF.getDocument(pdfURL);

    loadingTask.promise.then(async pdf=>{
        const canvasList = document.querySelectorAll(`.${canvasClassName}`);

        /*         
            当前显示设备的物理像素分辨率与CSS像素分辨率之比,它告诉浏览器应使用多少屏幕实际像素来绘制单个CSS像素
            <canvas>可能在视网膜屏幕上显得太模糊。 使用window.devicePixelRatio确定应添加多少额外的像素密度以使图像更清晰。 
        */
        const outputScale = window.devicePixelRatio || 1;

        // 根据 isAllPages 确定渲染多少页(从 0 开始)
        resizeAsyncRender(pdf, canvasList, 0, customScale, outputScale);
    })
}

/* 
    滚动到指定的pdf页面位置(num 为 pdf 第几页 从 1 开始)
*/
function scrollIntoPage(num, canvasClassName){
    document.querySelectorAll(`.${canvasClassName}`)[num - 1].scrollIntoView({
        behavior:'smooth',
    });
}

/* 
    点击左侧的略缩栏的某页,将该页滚动到展示区
*/
function clickNavToSee(canvasNode, centerAreaCanvasClassName){
    let index = canvasNode.id.split('-')[2].trim();
    document.querySelectorAll(`.${centerAreaCanvasClassName}`)[index - 1].scrollIntoView({
        behavior:'smooth'
    });
}

/* 
    获取 pdf 的页面总数,就是获取 canvas 的总数
*/
// function getPDFPageSum(canvasClassName){
//     return document.querySelectorAll(`.${canvasClassName}`).length;
// }

// 主要展示区 异步渲染解决方案,使之一上来就有页面
async function mainAreaAsyncRender(pdf, pageNumber, i, customScale, canvasClassName, outputScale, allCanvasParentNode, callback){
    // 获取 pdf 指定页
    let page = await pdf.getPage(i);
    // 设置 canvas 整体缩放的倍数
    let scale = customScale || 1.4;
    let viewport = page.getViewport({ scale });

    // 创建 canvas 共同的容器
    let canvas = document.createElement('canvas');
    canvas.className = canvasClassName || '';
    let context = canvas.getContext("2d");

    // 设置 canvas 和内容 大小
    canvas.width = Math.floor(viewport.width * outputScale);
    canvas.height = Math.floor(viewport.height * outputScale);
    canvas.style.width = Math.floor(viewport.width) + "px";
    canvas.style.height = Math.floor(viewport.height) + "px";
    canvas.style.margin = '10px 0';

    let transform = (outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null);

    // 将指定页渲染到 canvas
    let renderContext = {
        canvasContext: context,
        transform,
        viewport,
    };
    page.render(renderContext);

    // 将当前 canvas(当前页) 添加到父容器中
    allCanvasParentNode.appendChild(canvas);

    i++;
    if(i <= pageNumber){
        requestAnimationFrame(()=>
            mainAreaAsyncRender(pdf, pageNumber, i, customScale, canvasClassName, outputScale, allCanvasParentNode, callback));
    }else{
        // 传入 pdf 总页数
        if(callback) callback(pdf._pdfInfo.numPages);

        // 发布渲染完成的消息
        pubsub.publish('renderFinish');
    }
};

// 侧边略缩图列表 异步渲染解决方案
async function navAreaAsyncRender(pdf, pageNumber, i, customScale, canvasClassName,
     outputScale, allCanvasParentNode, centerAreaCanvasClassName){
    // 获取 pdf 指定页
    let page = await pdf.getPage(i);
    // 设置 canvas 整体缩放的倍数
    let scale = customScale || 0.35;
    let viewport = page.getViewport({ scale });

    // 创建 canvas 共同的容器
    let canvas = document.createElement('canvas');
    canvas.className = canvasClassName || 'canvas-left-nav-class-name';
    let context = canvas.getContext("2d");

    // 设置 canvas 内容 大小,添加悬浮事件
    canvas.id = `canvas-l-${i}`;
    canvas.width = Math.floor(viewport.width * outputScale);
    canvas.height = Math.floor(viewport.height * outputScale);
    canvas.style.width = Math.floor(viewport.width) + "px";
    canvas.style.height = Math.floor(viewport.height) + "px";
    canvas.style.border = '1px solid #888';
    canvas.style.margin = '20px 0 10px 0';
    canvas.addEventListener('mouseover',()=>{
        canvas.style.cursor = 'pointer';
        canvas.style.boxShadow = '2px 2px 1px 1px #888';
    });
    canvas.addEventListener('mouseleave',()=>{
        canvas.style.cursor = '';
        canvas.style.boxShadow = '';
    })
    canvas.addEventListener('click',(event)=>{
        clickNavToSee(canvas, centerAreaCanvasClassName);
    })

    let transform = (outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null);

    // 将指定页渲染到 canvas
    let renderContext = {
        canvasContext: context,
        transform,
        viewport,
    };
    page.render(renderContext);

    // 底部的页码标注
    let span = document.createElement('span');
    span.innerText = i;
    span.style = 'font-size: 0.85rem;color: #888;';

    // 将当前 canvas(当前页) 添加到父容器中
    allCanvasParentNode.appendChild(canvas);
    allCanvasParentNode.appendChild(span);

    i ++;
    if(i < pageNumber){
        requestAnimationFrame(()=>
            navAreaAsyncRender(pdf, pageNumber, i, customScale, canvasClassName, 
                outputScale, allCanvasParentNode, centerAreaCanvasClassName));
    }
}

// 根据放大和缩小的比例重绘中间pdf展示区
async function resizeAsyncRender(pdf, canvasList, i, customScale, outputScale){
    let canvas = canvasList[i];
    // 获取 pdf 指定页
    let page = await pdf.getPage(i + 1);
    // 设置 canvas 整体缩放的倍数
    let scale = customScale || 1.4;
    let viewport = page.getViewport({ scale });

    let context = canvas.getContext("2d");

    // 设置 canvas 和内容 大小
    canvas.width = Math.floor(viewport.width * outputScale);
    canvas.height = Math.floor(viewport.height * outputScale);
    canvas.style.width = Math.floor(viewport.width) + "px";
    canvas.style.height = Math.floor(viewport.height) + "px";

    let transform = (outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null);

    // 将指定页渲染到 canvas
    let renderContext = {
        canvasContext: context,
        transform,
        viewport,
    };
    page.render(renderContext);
    i++;
    if(i < canvasList.length){
        requestAnimationFrame(()=>resizeAsyncRender(pdf, canvasList, i, customScale, outputScale));
    }else{
        // 发布重新渲染完成的消息
        pubsub.publish("resizeFinish");
    }
}

export {
    displayPDF,
    displayPDFLeftNav,
    resizePDF,
    scrollIntoPage,
    // getPDFPageSum
}

(7-2)showPDF.jsx

import React from 'react'
import {displayPDF,resizePDF,scrollIntoPage,displayPDFLeftNav} from './displayPDF';
import pubsub from 'pubsub-js';

import './ShowPDF.scss'

export default class ShowPDF extends React.Component{
    leftNav = React.createRef();
    controlArea = React.createRef();
    state = {
        // 注意 pdf 要放在 public 文件夹下
        PDFURL:'/中国计算机学会推荐国际学术会议和期刊目录-2019.pdf',
        leftNavCanvasClassName:'left-canvas',   // 左侧略缩图列表各canvas的类名(共同
        centerCanvasClassName:'can',            // 主要展示区各canvas的类名(共同
        controlDisable:false,                   // 是否允许操作
        pdfScaleSize:1.4,                       // pdf 展示的缩放比例
        totalPage:0,                            // pdf 的页数
        targetPage:1,                           // 用户输入,要跳转到多少页
    }
    render(){
        return(
            <div className='container'>
                <div ref={this.controlArea} className='control-area'>
                    <button disabled={this.state.controlDisable} onClick={this.changeSketch}>展开或收起略缩图</button>
                    <button disabled={this.state.controlDisable} onClick={this.larger}>放大</button>
                    <button disabled={this.state.controlDisable} onClick={this.smaller}>缩小</button>
                    <div className='inp-area'>
                        <input disabled={this.state.controlDisable} type="number" placeholder='to' onChange={this.toPageChange} min={1} max={this.state.totalPage}/>
                        /
                        <input type="text" disabled value={this.state.totalPage}/>
                        <button disabled={this.state.controlDisable} onClick={this.toThisPage}>到指定页</button>
                    </div>
                    <button disabled={this.state.controlDisable} onClick={this.toTop}>返回顶部</button>
                </div>
                <div className='display-area'>
                    {/* 左侧的略缩图 */}
                    <div ref={this.leftNav} className='left-nav'></div>
                    {/* 中间的展示区 */}
                    <div className='center-area'></div>
                </div>
            </div>
        )
    }
    componentDidMount(){
        let {PDFURL, leftNavCanvasClassName, pdfScaleSize, centerCanvasClassName} = this.state;

        // 左侧的略缩图列表
        displayPDFLeftNav(PDFURL, document.querySelector('.left-nav'), centerCanvasClassName, 
            leftNavCanvasClassName, 0.35);

        // 中间的主要展示区域 
        displayPDF(PDFURL, document.querySelector('.center-area'), true,
            centerCanvasClassName, pdfScaleSize, (pageSum)=>{
                this.setState({totalPage:pageSum});
            });

        // 当中间展示区域初次渲染完成,顶部的控制部件显现
        pubsub.subscribe('renderFinish',()=>{
            this.controlArea.current.classList.add('control-area-show');
            pubsub.unsubscribe('renderFinish');
        })
        // 监听中间展示区域canvas的放大缩小,完成操作时,设置控制部件可操作
        pubsub.subscribe('resizeFinish',()=>{
            this.setState({controlDisable: false});
        })
    }
    componentWillUnmount(){
        pubsub.unsubscribe('resizeFinish');
    }
    // 返回顶部
    toTop = ()=>{
        scrollIntoPage(1, this.state.centerCanvasClassName);
    }

    // 展开或收起略缩图
    changeSketch = () =>{
        this.leftNav.current.classList.toggle('left-nav-change');
    }

    // 用户输入,将要跳转到指定页
    toPageChange = (event)=>{
        let val = event.target.value;
        val = parseInt(val);
        this.setState({targetPage:val});
    }

    // 点击按钮 根据用户输入页数 跳转到指定页面
    toThisPage = ()=>{
        scrollIntoPage(this.state.targetPage, this.state.centerCanvasClassName);
    }

    // 整体放大pdf
    larger = ()=>{                  
        let size = this.state.pdfScaleSize;
        if(size >= 3){              // 阈值
            return;
        }
        this.setState({controlDisable: true});
        size += 0.2;
        this.setState({pdfScaleSize:size});
        resizePDF(this.state.PDFURL, this.state.centerCanvasClassName, size);
    }

    // 整体缩小pdf
    smaller = ()=>{                 
        let size = this.state.pdfScaleSize;
        if(size <= 0.6){            // 阈值
            return;
        }
        this.setState({controlDisable: true});
        size -= 0.2;
        this.setState({pdfScaleSize:size});
        resizePDF(this.state.PDFURL, this.state.centerCanvasClassName, size);
    }
}

 (7-3)showPDF.scss

.container{
    width: 100vw;
    height: 100vh;
    min-width: 800px;
    border: 1px solid black;
    box-sizing: border-box;
    overflow: hidden;
    display: flex;
    flex-direction: column;

    .control-area{
        flex-grow: 2;
        box-sizing: border-box;
        width: 100%;
        height: 0px;
        background-color: rgb(255, 255, 255);
        border-bottom: 2px solid black;
        display: flex;
        justify-content: center;
        align-items: center;
        transition: height 300ms ease, opacity 300ms ease;
        opacity: 0;
        // overflow: hidden;

        .inp-area{
            margin: 0 70px;

            input{
                width: 50px;
                text-align: center;
                outline: none;
                margin: 0 10px;
            }
        }
        button{
            margin: 0 8px;
        }
    }
    .control-area-show{
        height: 45px;
        padding: 8px;
        opacity: 1;
    }

    .display-area{
        flex-grow: 1;
        flex-shrink: 1;
        display: flex;
        overflow: hidden;
    
        .left-nav{
            width: 20%;
            height: 100%;
            transition: width 300ms ease;
            background-color: #222222f6;
        }
        .left-nav-change{
            width: 0;
            overflow: hidden;
        }
        .center-area{
            flex-grow: 1;
            display: flex;
            justify-content: center;
            align-items: center;
            transition: width 300ms ease;
            background-color: #222;
        }
    }
}

(7-4)App.js

import React from 'react';

// 引入展示 pdf 的组件
import ShowPDF from './components/ShowPDF/ShowPDF'

export default class App extends React.Component{
    render(){
        const style = {
            width:'100vw',
            padding:'0',
            margin:'0',
            display:'flex',
            justifyContent:'center',
            alignItem:'center'
        }
        return(
            <div style={style}>
                <ShowPDF/>
            </div>
        )
    }
}

 参考:

[1]、pdf.js/helloworld.html at master · mozilla/pdf.js · GitHub

[2]、Examples

猜你喜欢

转载自blog.csdn.net/hao_13/article/details/130435171