用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
}
}