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:
WebWorker
Resource 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
cMapUrl
Resource 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
svg
Method rendering will cause English to overlapcanvas
Way render substitute
<Document
renderMode="canvas"
/>
Copy the code
- When rendering
pdf
If 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
useMemo
The function is used so that the rendering of each page is only affected by the total number of pages, not when other states changepage
Rerendering 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 (requiredencodeURIComponent
Code)- 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]