The problem

Recently, the company needs to do a desktop demand, need to preview 'PDF, PPT', etc., and support 'online local' resourcesCopy the code

A project built based on viet-reactts-electron -starter

Attention to Electron:

The development environment starts with a local server, packaged with a file service

const port = process.env.PORT || 3000
const url = isDev ? `http://localhost:${port}` : join(__dirname, '.. /.. /renderer/index.html')

// and load the index.html of the app.
isDev ? window? .loadURL(url) :window? .loadFile(url)Copy the code

plan

Preview the PDF

iframe Disadvantages Do not support local resources

<iframe src=PDF "" https://public-static-edu.codemao.cn/dev/17/sdk_upload/1627992652000/ one month myth. />
Copy the code

react-pdf(pdfjs) Support online and local resources

Online resources

Create a static resource directory public to add worker and cmaps (resources from PDFJS)

Packaged resources

Problem:

  • WebWorkerResource introduction, or failure

Note that the path does not require the root directory of the static resource

pdfjs.GlobalWorkerOptions.workerSrc = 'pdf/pdf.worker.min.js'
Copy the code
  • cMapUrlResource configuration (no configuration, Chinese font)

Note that the path does not require the root directory of the static resource

<Document
  options={{
    cMapUrl: 'pdf/cmaps/'.cMapPacked: true}} / >Copy the code
  • svgMethod rendering will cause English to overlapcanvasWay render substitute
<Document
  renderMode="canvas"
/>
Copy the code
  • When renderingpdfIf the file size is too large, how to handle it
    • paging

    • Virtual list

    • Batch rendering (batch rendering used here)

      Add resources to the data source dynamically using the requestAnimationFrame function

    function addPageSource() {
      const allPageSourceLen = allPageSource.length
      constlength = allPage < allPageSourceLen + MIN_PAGE ? allPage - allPageSourceLen : MIN_PAGE allPageSource.push(... newArray(length).fill(' '))
      setAllPageSource([...allPageSource])
      if (allPageSource.length >= allPage) return
    
      requestAnimationFrame(() = > {
        addPageSource()
      })
    }
    Copy the code
    • useMemoThe function is used so that the rendering of each page is only affected by the total number of pages, not when other states changepageRerendering of
    const generatePage = useMemo(() = > {
      return allPageSource.map((page, index) = > (
        <Page
          key={index}
          pageNumber={index + 1}
          scale={1}
          loading=""
          onRenderError={()= > {
            console.log('onRenderError')
          }}
          onRenderSuccess={() => {
            console.log('onRenderSuccess')
          }}
        />
      ))
    }, [allPageSource])
    Copy the code
Local resources
Through communication between the renderer process and the main process, read files are passed to the renderer process, which is then constructed into a 'Blob' and renderedCopy the code

Rendering process

useEffect(() = > {
    window.Main.send('pdf'.' ')
    window.Main.on('pdf'.(fromMain: string) = > {
      console.log(typeof fromMain)
      setFile(new Blob([fromMain]))
    })
}, [])
  
<Pdf file={file} />
Copy the code

The main process


ipcMain.on('pdf'.(event: IpcMainEvent) = > {
 const file = readFileSync(join(__dirname, '/.. /.. PDF '/ the renderer/PDF/lalala courseware.))
 setTimeout(() = > event.sender.send('pdf', file), 500)})Copy the code

rendering

Preview the PPT

Microsoft’s Office Web version is freeDisadvantages Do not support local resources

  • https://view.officeapps.live.com/op/view.aspx?src=+ Resource address (requiredencodeURIComponentCode)
  • It must be an online resource
        <iframe src="https://view.officeapps.live.com/op/view.aspx?src=https%3A%2F%2Fdev-static-edu.codemao.cn%2Fdev%2F17%2Fsdk_upload%2F162 8485563000%2F%E6%BB%9A%E8%9B%8B.pptx%3Fe%3D1628486784%26token%3DZ7VJBYDfapvJhzJLXXsy-M4OqxaUPlrRP6pslxKr%3A8BvbZnX53Tlf4 kDV8EClklKbma0%3D" />
Copy the code

The complete code

The first draft needs some refinement

/** * PDF preview package ** Support file status * 1. 2. Local file blob */

import { FC, useState, useRef, memo, KeyboardEvent, ChangeEvent, useMemo, useEffect } from 'react'
import { Document, Page } from 'react-pdf'
import Style from './index.module.css'
import classnames from 'classnames'

const MULTIPLE_STEP = 0.15
const MIN_PAGE = 10
const MIN_WIDTH = 400

interface PdfType {
  file: string | Blob
}

enum ScaleType {
  NARROW,
  ENLARGE
}

export const Pdf: FC<PdfType> = memo(
  ({ file: propsFile }) = > {
    / / the total number of pages
    const [allPage, setAllPage] = useState<number>(0)
    const [allPageSource, setAllPageSource] = useState<string[]>([])
    // Enter the page number value
    const [currentInputVal, setCurrentInputVal] = useState<number | string>(1)
    // Roll the box
    let { current: pdfScrollWrapEl } = useRef<HTMLDivElement>(null)
    // PDF content box
    let { current: pdfMainEl } = useRef<HTMLDivElement>(null)
    // Current page number
    let pageNumber = useRef<number>(1)

    function onDocumentLoadSuccess({ numPages }: { numPages: number }) {
      setAllPage(numPages)
    }

    function addPageSource() {
      const allPageSourceLen = allPageSource.length
      constlength = allPage < allPageSourceLen + MIN_PAGE ? allPage - allPageSourceLen : MIN_PAGE allPageSource.push(... newArray(length).fill(' '))
      setAllPageSource([...allPageSource])
      if (allPageSource.length >= allPage) return

      requestAnimationFrame(() = > {
        addPageSource()
      })
    }

    useEffect(() = > {
      addPageSource()
    }, [allPage])

    const generateScrollPage = useMemo(() = > {
      return allPageSource.map((page, index) = > (
        <Page
          key={index}
          pageNumber={index + 1}
          scale={1}
          loading=""
          onRenderError={()= > {
            console.log('onRenderError')
          }}
          onRenderSuccess={() => {
            console.log('onRenderSuccess')
          }}
        />
      ))
    }, [allPageSource])

    / / back
    function handlePre() {
      const tempPageNumber = pageNumber.current - 1
      handleScroll(tempPageNumber)
    }

    function handleChange(event: ChangeEvent<InputEvent>) {
      const value = event.target.value
      if (value === ' ') return setCurrentInputVal(' ')
      setCurrentInputVal(Number(value) || 0)}function handleEnterEvent(e: KeyboardEvent<InputEvent>) {
      if (e.keyCode === 13) handleScroll(currentInputVal as number)
    }

    / / the next page
    function handleNext() {
      const tempPageNumber = pageNumber.current + 1
      handleScroll(tempPageNumber)
    }

    // Scroll to a page
    function handleScroll(page: number) {
      if (page > allPage) return
      if (page < 1) return
      pageNumber.current = page
      const clientHeight = document.querySelector('.react-pdf__Page')? .clientHeight ||0
      pdfScrollWrapEl && (pdfScrollWrapEl.scrollTop = (page - 1) * (clientHeight + 10))
      setCurrentInputVal(page)
    }

    / / zoom
    function handleScale(scaleType: ScaleType) {
      if(! pdfMainEl)return

      const width = pdfMainEl.clientWidth
      const scaleWidth = width * MULTIPLE_STEP
      let newWidth = 0
      if (ScaleType.NARROW === scaleType) {
        newWidth = width - scaleWidth
      } else {
        newWidth = width + scaleWidth
      }
      pdfMainEl.style.width = newWidth < MIN_WIDTH ? `${MIN_WIDTH}px` : `${newWidth}px`
    }

    function generatePagination() {
      return (
        <div className={Style.page_wrap}>
          <div className={Style.left}>
            <span className={classnames(Style['small-btn'].Style['prev'])} onClick={handlePre} />
            <input
              type="number"
              min="1"
              max={allPage}
              className={Style['page-num']}
              onChange={handleChange}
              onKeyDown={handleEnterEvent}
              value={currentInputVal}
            />
            /<span>{allPage} page</span>
            <span className={classnames(Style['small-btn'].Style['next'])} onClick={handleNext} />
          </div>
          <div className={classnames(Style.right)}>
            <div
              className={classnames(Style['tool-btn'].Style['narrow'])}
              onClick={()= > handleScale(ScaleType.NARROW)}
            />
            <div
              className={classnames(Style['tool-btn'].Style['enlarge'])}
              onClick={()= > handleScale(ScaleType.ENLARGE)}
            />
          </div>
        </div>)}return (
      <div className={Style.pdf_wrap}>
        <div className={Style.pdf_container}>
          <div
            className={Style.pdf_scroll_wrap}
            ref={(el: HTMLDivElement) = > {
              pdfScrollWrapEl = el
            }}
          >
            <Document// Chinese is not displayed because of the font filesvgMethod rendering will cause English to overlapcanvasMethod render instead of //cMapUrlNotice the path problemclassName={Style.pdf_main}
              inputRef={(el: HTMLDivElement) = >{ pdfMainEl = el }} options={{ cMapUrl: 'pdf/cmaps/', cMapPacked: True file = {propsFile}}} / / file = "https://public-static-edu.codemao.cn/dev/17/sdk_upload/1627992652000/ one month myth. PDF / /" The file = "https://public-static-edu.codemao.cn/dev/17/sdk_upload/1628149495000/sllwqy.pdf" / / file path or base64 onLoadSuccess={onDocumentLoadSuccess} onLoadError={console.error} error={<div>Resource error</div>} renderMode="canvas" loading=" trying to load... ExternalLinkTarget ="_blank" noData=" noData "> {/* generated page */} {generateScrollPage}</Document>
          </div>
        </div>{/* pagination */} {generatePagination()}</div>)},(newProps, oldProps) = > {
    return newProps.file === oldProps.file
  }
)
Copy the code

Post to recommend

  • Vue + TypeScript + Element-UI + Axios builds the front-end project infrastructure
  • Realize project download, automatic routing and project release scaffolding based on Node
  • Encapsulate a simple WebSocket library
  • Note: Vue often meet questions summary and analysis
  • Proxy is an alternative to Object. DefineProperty in Vue3.0
  • Vue 3.0 – First experience 1
  • Figure out a prototype, a prototype object, a prototype chain
  • Promise Principles = Build a Promise from 0 to 1

[Notes are not easy, if it is helpful to you, please like, thank you]