react實現移動端PDF線上預覽外掛

風華振茂發表於2018-03-20

前端實現DPF線上預覽的外掛比較多,不過大多都不夠成熟,用起來可能覺得沒那麼好用;選了一個個人覺得相對好用,適合我們現有框架的mozilla/pdf.js


參考資料: mozilla/pdf.js

頭部引入mozilla/pdf.js的庫

這個庫目前沒有用npm安裝的,直接在頭部引入線上的或者下載一個就可以用了

<!DOCTYPE html>
<html lang="cn">
    <head>
        <meta charset="UTF-8">
        <meta content="yes" name="apple-mobile-web-app-capable">
        <meta content="yes" name="apple-touch-fullscreen">
        <meta content="telephone=no,email=no" name="format-detection">
        <title><%= htmlWebpackPlugin.options.title %></title>
        <link rel="stylesheet" href="./iconfont/iconfont.css">
        <script src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
        <script src="//mozilla.github.io/pdf.js/build/pdf.js"></script>
    </head>
    <body>
        <div id="appContainer"></div>
    </body>
</html>
複製程式碼

封裝一個PDF元件,直接複製貼上這段程式碼就可以

class PDF extends React.Component {
    constructor (props) {
        super(props)
        this.state = {
            pdf: null,
            scale: 1.2
        }
    }
    getChildContext () {
        return {
            pdf: this.state.pdf,
            scale: this.state.scale
        }
    }
    componentDidMount () {
        PDFJS.getDocument(this.props.src).then((pdf) => {
            console.log(pdf)
            this.setState({ pdf })
        })
    }
    render () {
    return (<div className='pdf-context'>{this.props.children}</div>) 
    }
}

PDF.propTypes = {
    src: React.PropTypes.string.isRequired
}

PDF.childContextTypes = {
    pdf: React.PropTypes.object,
    scale: React.PropTypes.number
}

class Page extends React.Component {
    constructor (props) {
        super(props)
        this.state = {
            status: 'N/A',
            page: null,
            width: 0,
            height: 0
        }
    }
    shouldComponentUpdate (nextProps, nextState, nextContext) {
        return this.context.pdf != nextContext.pdf || this.state.status !== nextState.status
    }
    componentDidUpdate (nextProps, nextState, nextContext) {
        this._update(nextContext.pdf) 
    }
    componentDidMount () {
        this._update(this.context.pdf) 
    }
    _update (pdf) {
        if (pdf) {
            this._loadPage(pdf)
        } else {
            this.setState({ status: 'loading' }) 
        }
    }
    _loadPage (pdf) {
        if (this.state.status === 'rendering' || this.state.page != null) return; 
        pdf.getPage(this.props.index).then(this._renderPage.bind(this))
        this.setState({ status: 'rendering' })
    } 
    _renderPage (page) {
        console.log(page)
        let { scale } = this.context 
        let viewport = page.getViewport(scale)
        let { width, height } = viewport
        let canvas = this.refs.canvas
        let context = canvas.getContext('2d')
        console.log(viewport.height, viewport.width)
        canvas.width = width
        canvas.height = height
        
        page.render({
            canvasContext: context,
            viewport
        })
      
        this.setState({ status: 'rendered', page, width, height })
    }
    render () {
        let { width, height, status } = this.state
        return (
            <div className={`pdf-page {status}`} style={{width, height}}>
                <canvas ref='canvas' />
            </div>
        )
    }
}

Page.propTypes = {
    index: React.PropTypes.number.isRequired
}
Page.contextTypes = PDF.childContextTypes

class Viewer extends React.Component {
    render () {
        let { pdf } = this.context
        let numPages = pdf ? pdf.pdfInfo.numPages : 0
        let fingerprint = pdf ? pdf.pdfInfo.fingerprint : 'none'
        let pages = Array.apply(null, { length: numPages })
            .map((v, i) => (<Page index={i + 1} key={`${fingerprint}-${i}`}/>))
        
        return (
            <div className='pdf-viewer'>
                {pages}
            </div>
        )
    }
}
Viewer.contextTypes = PDF.childContextTypes
複製程式碼

最後一步就是呼叫這個PDF元件就可以了,這裡需要改一下頭部的meta[name="viewport"]的屬性為可自由放大縮小,退出PDF頁時需要恢復meta標籤為不可手動縮放的

class previewPDF extends React.Component {
    constructor (props) {
        super (props);
    }

    // 初始化PDF元件時改變meta為可手動縮放
    componentDidMount(){
        document.querySelector('meta[name="viewport"]').setAttribute("content", "width=device-width,user-scalable=yes,initial-scale=0.5,maximum-scale=1.2,minimum-scale=0.1");
    }

    // 元件解除安裝時恢復meta
    componentWillUnmount(){
        document.querySelector('meta[name="viewport"]').setAttribute("content", "width=device-width,user-scalable=no,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5");
        window.location.reload();
    }

    render() {
        let PDF_URL = getQueryString('pdf', this.props.location.search);
        return (
            <PDF src={PDF_URL}>
                <Viewer />
            </PDF>
        );
    }
}
複製程式碼

-------------------更新內容------------------

React.PropTypes的問題

由於React v15.5起,React.PropTypes已移至另一個包中。 React.PropTypes請改用prop-types庫。 prop-types的使用如下:

import PropTypes from 'prop-types';

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

Greeting.propTypes = {
  name: PropTypes.string
};
複製程式碼

更新後的PDF元件

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export class PDF extends Component {
    constructor (props) {
        super(props)
        this.state = {
            pdf: null,
            scale: 1.2
        }
    }
    getChildContext () {
        return {
            pdf: this.state.pdf,
            scale: this.state.scale
        }
    }
    componentDidMount () {
        PDFJS.getDocument(this.props.src).then((pdf) => {
            console.log(pdf)
            this.setState({ pdf })
        })
    }
    render () {
        return (<div className='pdf-context'>{this.props.children}</div>) 
    }
}

// PDF.propTypes = {
//     src: PropTypes.string.isRequired
// }

PDF.childContextTypes = {
    pdf: PropTypes.object,
    scale: PropTypes.number
}

export class Page extends Component {
    constructor (props) {
        super(props)
        this.state = {
            status: 'N/A',
            page: null,
            width: 0,
            height: 0
        }
    }
    // shouldComponentUpdate (nextProps, nextState, nextContext) {
    //     return this.context.pdf != nextContext.pdf || this.state.status !== nextState.status
    // }
    // componentDidUpdate (nextProps, nextState, nextContext) {
    //     debugger
    //     this._update(nextContext.pdf) 
    // }
    componentDidMount () {
        this._update(this.context.pdf) 
    }
    _update (pdf) {
        if (pdf) {
            this._loadPage(pdf)
        } else {
            this.setState({ status: 'loading' }) 
        }
    }
    _loadPage (pdf) {
        if (this.state.status === 'rendering' || this.state.page != null) return; 
        pdf.getPage(this.props.index).then(this._renderPage.bind(this))
        this.setState({ status: 'rendering' })
    } 
    _renderPage (page) {
        let { scale } = this.context 
        let viewport = page.getViewport(scale)
        let { width, height } = viewport
        let canvas = this.refs.canvas
        let context = canvas.getContext('2d')

        canvas.width = width
        canvas.height = height
        
        page.render({
            canvasContext: context,
            viewport
        })
      
        this.setState({ status: 'rendered', page, width, height })
    }
    render () {
        let { width, height, status } = this.state
        return (
            <div className={`pdf-page {status}`} style={{width, height}}>
                <canvas ref='canvas' />
            </div>
        )
    }
}

// Page.propTypes = {
//     index: PropTypes.number.isRequired
// }
Page.contextTypes = PDF.childContextTypes

export class Viewer extends Component {
    render () {
        let { pdf } = this.context
        let numPages = pdf ? pdf.pdfInfo.numPages : 0
        let fingerprint = pdf ? pdf.pdfInfo.fingerprint : 'none'
        let pages = Array.apply(null, { length: numPages })
            .map((v, i) => (<Page index={i + 1} key={`${fingerprint}-${i}`}/>))
        
        return (
            <div className='pdf-viewer'>
                {pages}
            </div>
        )
    }
}
Viewer.contextTypes = PDF.childContextTypes
複製程式碼

相關文章