admin管理员组

文章数量:1649937

前段时间在react项目中接到了一个需求: 能够预览PDF 和 图片
接下来总结并记录一下:
用到的插件是react-pdf
我所实现的功能如下:可以鼠标点击翻页,可以鼠标上下滑动翻页,可以放大、缩小,可以跳转到指定页面。

  • 首先封装pdf.js 的组件:
    其中自己封装了四个icon的组件:nextPage、 lastPage 、zoomOut 、zoomIn (后面会说明封装组件的具体步骤,慢慢期待吧~)
    但也可以用antd中的icon组件来实现
/* eslint-disable jsx-a11y/no-static-element-interactions */
import { useEffect, useState } from "react"
import { Document, Page, pdfjs } from "react-pdf"
import { toast } from "react-toastify"
import "./preview.scss"
import { Card, Input } from "reactstrap"
import Nextpage from "../components/Icons/Nextpage"
import Lastpage from "../components/Icons/Lastpage"
import Zoomin from "../components/Icons/Zoomin"
import Zoomout from "../components/Icons/Zoomout"

pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cat/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`

function MyApp({ file = " " }) {
  const [numPages, setNumPages] = useState(1)
  const [pageNumber, setPageNumber] = useState(1)
  const [scale, setScale] = useState(1)
  const fileType = file.split(";")[0].split("/")[1]
  const [pageNumberFocus, setPageNumberFocus] = useState(false)
  const [pageNumberInput, setPageNumberInput] = useState(1)
  const [fullscreen, setFullscreen] = useState(false)

  function onDocumentLoadSuccess({ numPages }) {
    setNumPages(numPages)
    setPageNumber(pageNumber)
  }
  useEffect(
    () => {
      window.addEventListener("scroll", handleScroll, true)
      return () => {
        window.removeEventListener("scroll", handleScroll, true)
      }
    },
    [pageNumber, numPages], // eslint-disable-line react-hooks/exhaustive-deps
    handleScroll
  )

  function handleScroll(e) {
    let height = e.srcElement.documentElement.scrollTop + e.srcElement.documentElement.clientHeight
    height = Math.ceil(height)
    let scrollTop = e.srcElement.documentElement.scrollTop
    let scrollHeight = e.srcElement.documentElement.scrollHeight

    if (pageNumber !== 1 && scrollTop === 0) {
      const page = pageNumber - 1
      setPageNumber(page)
      setPageNumberInput(page)
      window.scrollTo(0, 10)
    }
    if (pageNumber !== numPages && height >= scrollHeight - 10) {
      const page = pageNumber + 1
      setPageNumber(page)
      setPageNumberInput(page)
      window.scrollTo(0, 10)
    }
  }

  // 上一页 (lastPage)
  function lastPage() {
    if (pageNumber === 1) {
      toast.info("This is the first page", 2)
      return
    }
    const page = pageNumber - 1
    setPageNumber(page)
    setPageNumberInput(page)
  }

  //下一页(nextPage)
  function nextPage() {
    if (pageNumber === numPages) {
      toast.info("This is the last page", 2)
      return
    }
    const page = pageNumber + 1
    setPageNumber(page)
    setPageNumberInput(page)
  }

  // 跳转页面
  function onPageNumberFocus() {
    setPageNumberFocus(true)
  }
  function onPageNumberBlur() {
    setPageNumberFocus(false)
    setPageNumberInput(pageNumber)
  }
  function onPageNumberChange(e) {
    let value = e.target.value
    value = value <= 0 ? 1 : value
    value = value >= numPages ? numPages : value
    setPageNumberInput(value)
  }
  function handleKeyDown(e) {
    if (e.keyCode === 13) {
      setPageNumber(Number(e.target.value))
    }
  }

  //缩小(ZoomOut)
  function pageZoomOut() {
    if (scale <= 0.5) {
      toast.info("Zoomed to minimum", 2)
      return
    }
    let newScale = scale - 0.1
    setScale(newScale)
  }

  //放大(ZoomIn)
  function pageZoomIn() {
    if (scale >= 5) {
      return
    }
    let newScale = scale + 0.1
    setScale(newScale)
  }

  return (
    <div className='preview-pdf-wrap'>
      <div className='content-wrap'>
        {file && (
          <Card className='cardItem' bordered={false}>
            {fileType === "jpg" || fileType === "png" || fileType === "jpeg" ? (
              <div className='img-wrap'>
                <img src={file} style={{ width: "100vw" }} alt='Red dot' />
              </div>
            ) : (
              <div className='pageContainer'>
                <Document file={file} onLoadSuccess={onDocumentLoadSuccess}>
                  <Page pageNumber={pageNumber} scale={scale} width={window.innerWidth} />
                </Document>
              </div>
            )}
          </Card>
        )}

        <p>
          Page {pageNumber} of {numPages}
        </p>
      </div>
      {fileType === "pdf" && (
        <div className='footer-wrap'>
          <div className='pageTool'>
            <div className='btn-wrap' title='last page'>
              <div className='btn-icon' onClick={lastPage}>
                <Lastpage type='icon-xiayiyehouyiye' />
              </div>
            </div>
            <Input
              value={pageNumberFocus ? pageNumberInput : pageNumber}
              onFocus={onPageNumberFocus}
              onBlur={onPageNumberBlur}
              onChange={onPageNumberChange}
              onKeyDown={handleKeyDown}
            />
            / {numPages}
            <div className='btn-wrap' title='next page'>
              <div className='btn-icon' onClick={nextPage}>
                <Nextpage type='icon-xiayiyehouyiye' />
              </div>
            </div>
            <div className='btn-wrap' title='zoomIn'>
              <div className='btn-icon' onClick={pageZoomIn}>
                <Zoomout type='plus' />
              </div>
            </div>
            <div className='btn-wrap' title='zoomOut'>
              <div className='btn-icon' onClick={pageZoomOut}>
                <Zoomin type='icon-suoxiao1' style={{ textAlign: "center" }} />
              </div>
            </div>
          </div>
        </div>
      )}
    </div>
  )
}

export default MyApp

  • 下面是样式文件:preview.scss
.preview-pdf-wrap {
  display: flex;
  flex-direction: column;
  justify-content: center;
  flex-wrap: nowrap;
  margin: 0 auto;
  padding: 20px 0;

  .content-wrap {
    height: calc(100vh - 94px);

    .cardItem {
      .img-wrap {
        padding-top: 112px;

        img {
          position: relative;
          display: flex;
          justify-content: center;
          align-items: center;
          transform: scale(0.3, 0.3);
        }
      }
    }

    .react-pdf__Document {
      .react-pdf__Page {
        width: fit-content;
        margin: 0 auto;
      }
    }
  }

  .footer-wrap {
    color: #fff;
    width: 100%;
    height: 64em;
    display: flex;
    justify-content: center;
    flex-direction: column;

    .pageTool {
      position: fixed;
      margin: 0 auto;
      bottom: 10%;
      left: 50%;
      transform: translateX(-50%);
      height: 74px;
      align-items: center;
      height: 45px;
      background: #333;
      opacity: 0.9;
      border-radius: 16px;
      display: flex;
      flex-wrap: nowrap;
      justify-content: space-between;
      width: 30%;

      input {
        display: inline-block;
        width: 30px;
        text-align: center;
        margin-right: 10px;
        height: 15px;
      }

      input::-webkit-outer-spin-button,
      input::-webkit-inner-spin-button {
        -webkit-appearance: none;
      }

      input[type="number"] {
        -moz-appearance: textfield;
      }
    }

    .btn-wrap {
      flex: 1;
      display: flex;
      justify-content: center;
      align-items: center;

      .btn-icon {
        cursor: pointer;
      }
    }

    button {
      font-size: 12px;
      width: 70px;
      height: 35px;
      border-radius: 7px;
    }

    .Nextpage {
      text-align: center;
    }
  }
}

这里说一下最初打开PDF时自适应问题, 官方对scale做了解释的, 具体如下:

所以我在这把scale的默认值为1 ,然后写了宽度,如下:

  const [scale, setScale] = useState(1)
 <Page pageNumber={pageNumber} scale={scale} width={window.innerWidth} />
  
  • 最后来说明怎么去使用这些封装好的组件:
    preview.js 文件中使用, 这个文件我直接创建在同一个目录的~ 这个可以根据自己的具体情况而定哟~
import Pdf from "./pdf"
import { data } from "./data"
export default function Home() {
    return (
        <>
            <Pdf file={data} />
        </>
    )
}
  • 接下来是data文件,因为我没用的是base64 ,所以单独创建了data.js 文件, 在这因为数据太长就不放了,这个数据你可以自己放上去哈,如果不是base64的要求,那就不用data.js 文件,直接放文件地址就可以实现的哦。
export const data ="  //  在这填写对应pdf文件的base64 数据,在此要注意pdf文件格式(一般是一下格式:data:application/pdf;base64 )"

我也给大家推荐一个把文件转换为base64 数据的网站:文件Base64在线编码和解码工具
能够坚持看到这儿就很棒了!! 给自己一个大大的赞吧~
欣赏一波樱花嘻嘻嘻

到这儿就大功告成了~ 不是很清晰的欢迎私信或者留言哦

如果还想看 上面说的icon 组件的封装的话那我们继续哈
下面来说一下icon组件的封装,组件的封装方式都一样,所以在这只说明一个,目录的话,如果你在项目里做的话要在组件库中创建哦,如果自己单独实现的话也尽量自己创建组件库再封装,具体看自己的习惯哦~ 不多唠叨了, 直接上代码吧~
nextPage.js文件的内容如下: 在封装这个组件的时候顺便可以看看svg的应用~

const Nextpage = () => {
  return (
    <div style={{ textAlign: "center" }} aria-hidden="true" role="presentation">
      <svg
        style={{ width: "24px", height: "23px" }}
        aria-hidden="true"
        focusable="false"
        data-prefix="fal"
        data-icon="arrows-h"
        className="icon-xiayiyehouyiye"
        viewBox="0 0 1024 1024"
      >
        <path
          d="M639.7 490.3l-1.8-1.8-21.8-21.8L430.4 281c-12.4-12.5-32.8-12.5-45.2-0.1-12.4 12.5-12.4 32.9 0 45.3L570.9 512 385.1 697.7c-12.4 12.5-12.4 32.9 0 45.3 12.5 12.4 32.9 12.4 45.3 0l185.7-185.7 21.5-21.5 2.2-2.2c5.2-5.7 8.3-13.2 8.3-21.5 0.1-8.4-3.1-16.1-8.4-21.8z"
          fill="#fff"
        />
      </svg>
    </div>
  )
}

export default Nextpage

index 文件:

import Nextpage from "./Nextpage"
export default Nextpage

封装好后直接引入就可以了, 是不是so easy 吖 哈哈哈
图片的预览暂时不在这讲了,可能后期会更新博客说明一下的哦,没更新的话可以私信或留言哦~
一起努力吖!加油~

本文标签: ReactPDF