2
用pdfjs加载远程资源后,如果一次性将所有页面渲染出来,导致有时候滑动有些卡顿,所以想实现懒加载渲染,只有当前可视区域的页面和将要看见的页面才会去渲染

pdfjs的基本使用 >>>

懒加载原理

把每个页面都当做一个子组件,让他们拥有自己的状态(页面的大小,位置等),通过pdfjs获得,不用去渲染 canvas,就可以拿到页面的大小和当前是第几页,通过这些信息计算出页面位置
这里把缩放比乘 ratio,原因参看《canvas模糊问题》

// pageProxy 就是指 传入 pdf 链接由 pdfjs 产生的 pdf对象
// 方便控制所以定位用的 absolute
const { docId, pageIndex, numPages } = pageProxy
const ratio = window.devicePixelRatio
const { width, height } = pageProxy.getViewport({ scale: scale * ratio })
const top = (height + ENUM.marginTop) * pageIndex
const left = (document.documentElement.clientWidth - width) / 2

因为react的渲染机制,所有的子组件被挂载到DOM树上后 componentDidMount 立即调用,所以在这个生命周期中就可以开始判断,当面页面是否在可视区域内

componentDidMount () {
    const { scrollTop } = this.props
    const { clientWidth, clientHeight } = document.documentElement
    // offsetTop 当前页面所在文档的相对高度
    // height 页面高度
    // scrollTop 可视区域顶部距离文档顶部的相对高度
    const { top: offsetTop, height, ratio } = this.state
    if (offsetTop > scrollTop - height && offsetTop < scrollTop + clientHeight + height) {
        // TODO:渲染页面
    }
}

为了知道页面组件中的页面是否已经被渲染,通过一个状态来记录是否渲染出页面,避免重复渲染

componentDidMount () {
    const { scrollTop } = this.props
    const { clientWidth, clientHeight } = document.documentElement
    const { top: offsetTop, height, ratio } = this.state
    if (offsetTop > scrollTop - height && offsetTop < scrollTop + clientHeight + height) {
        const canvas = this.canvasRef.current
        const context = canvas.getContext('2d')
        const { pageProxy, scale } = this.props
        const viewport = pageProxy.getViewport({ scale: scale * ratio })
        canvas.width = viewport.width
        canvas.height = viewport.height
        const renderContext = {
            // transform: [CSS_UNITS, 0, 0, CSS_UNITS, 0, 0],
            canvasContext: context,
            viewport: viewport,
            enableWebGL: true,
        }
        pageProxy.render(renderContext)
        this.prePageCanvas = context
        // 渲染 canvas 操作异步操作,处于 render 之后,因为组件状态没有改变,所以不会去触发更新 DOM
        // 用 this.forceUpdate() 强制触发更新
        this.forceUpdate()
    }
}

因为页面组件本身的状态都是固定的,要想触发组件更新,就要通过外部条件来触发。
要实现页面的滚动加载,唯一的变量就是文档的 scrollTop ,监听 scrollTop 的变化,触发页面组件的更新

// 监听滚动
onScroll = () => {
    console.info('onScroll')
    this.setState({
        scrollTop: this.pageRenderRef.current.scrollTop
    })
}

因为已经不是第一次加载了,因为页面组件中的 props.scrollTop 发生改变触发的更新,shouldComponentUpdate 触发执行。
在这个阶段再去做判断,当前页面是否在可视区域内

shouldComponentUpdate (nextProps, nextState, nextContext) {
    const { scrollTop } = this.props
    const { clientWidth, clientHeight } = document.documentElement
    const { top: offsetTop, height } = this.state
    if (offsetTop < scrollTop - height || offsetTop > scrollTop + clientHeight + height) {
        return false
    }
    return true
}

即使页面在可视区域内,也要在 componentDidUpdate 生命周期中判断该组件是否已经渲染出 canvas,避免重复渲染出 canvas
因为在页面上可能放其他组件,其他组件的更新也有可能触发渲染,为了不影响其他组件的触发,所以没有在 shouldComponentUpdate 生命周中一起判断

componentDidUpdate (prevProps, prevState, snapshot) {
    if (!this.prePageCanvas) {
        this.prePageCanvas = null
        const ratio = window.devicePixelRatio
        const canvas = this.canvasRef.current
        const context = canvas.getContext('2d')
        const { pageProxy, scale } = this.props
        const viewport = pageProxy.getViewport({ scale: scale * ratio })
        canvas.width = viewport.width
        canvas.height = viewport.height
        const renderContext = {
            // transform: [CSS_UNITS, 0, 0, CSS_UNITS, 0, 0],
            canvasContext: context,
            viewport: viewport,
            enableWebGL: true,
        }
        pageProxy.render(renderContext)
        this.prePageCanvas = context
    }
}

测试

display.gif

源代码

gitHub


Ezio
35 声望2 粉丝