如何检测 PDF 文件的是否已加载完成

通过 Fetch API 和 Blob 对象实现检测 PDF 是否已加载完成。
杭州
如何检测 PDF 文件的是否已加载完成

在网页中,如何判断 iframe 中的 PDF 是否已经加载完成呢?

网页中常规展示 PDF 的方式

        <object data='filename.pdf' type='application/pdf'>
  <p>您的浏览器不支持嵌入式 PDF</p>
</object>
      

上述写法虽然简洁,但存在一个问题 —— 你无法通过代码判断 PDF 是否加载完成。

尤其是对于较大的 PDF 文件(例如 10MB),加载过程可能需要几秒钟时间,用户体验不佳。

改进方案:使用 Fetch 请求 PDF 后再加载

为了更好地控制加载流程,我们可以使用 fetch 请求 PDF 文件,再通过 Blob 创建一个临时 URL 来展示 PDF。整体流程如下:

  1. 通过 fetch 请求 PDF 文件;
  2. 将请求到的文件转换为 Blob 对象;
  3. 使用 createObjectURL 创建临时本地 URL;
  4. 使用该 URL 来展示 PDF。

这样你可以在加载过程中设置 loading 状态,甚至配合进度条或提示信息,显著提升用户体验。

👉 示例代码 standbox:usePDFUrl

代码实现

        import { useCallback, useEffect, useRef, useState } from 'react'

interface PdfRenderProps {
  url: string // PDF 的远程地址
  params: Record<string, any> // 可选的 PDF 参数,如页码等
}

// 将参数序列化为 URL 查询字符串
const serialization = (obj: Record<string, any>) => {
  const params = new URLSearchParams()
  for (let key in obj) {
    params.append(key, obj[key])
  }
  return params.toString()
}

/**
 * 自定义 Hook:根据远程 URL 异步加载 PDF 文件,并返回本地 URL
 */
export const usePdfUrl = (props: PdfRenderProps) => {
  const { url, params } = props

  const [pdfLocalUrl, setPdfLocalUrl] = useState<string | null>(null)
  const [loading, setLoading] = useState(false)
  const abortController = useRef<AbortController | null>(null)

  // 拼接参数(如页码、缩放比例等)
  const wrapperUrl = pdfLocalUrl ? `${pdfLocalUrl}#${serialization(params)}` : null

  const fetchPdf = useCallback(async (requestUrl: string) => {
    if (abortController.current) {
      abortController.current.abort()
    }

    setLoading(true)

    abortController.current = new AbortController()
    const signal = abortController.current.signal

    try {
      const response = await fetch(requestUrl, { signal })
      abortController.current = null

      if (!response.ok) {
        throw new Error(`HTTP 请求错误,状态码:${response.status}`)
      }

      const blob = await response.blob()
      const pdfBlob = new Blob([blob], { type: 'application/pdf' })

      const localUrl = URL.createObjectURL(pdfBlob)
      setPdfLocalUrl(localUrl)
    } catch (error) {
      console.error('加载 PDF 失败:', error)
    }

    setLoading(false)
  }, [])

  useEffect(() => {
    if (url) {
      fetchPdf(url)
    }

    return () => {
      abortController.current?.abort()
      abortController.current = null
    }
  }, [fetchPdf, url])

  return {
    pdfLocalUrl: wrapperUrl,
    loading,
  }
}
      

总结

通过使用 fetch + Blob + URL.createObjectURL 的方式,我们可以更好地掌控 PDF 的加载过程:

  • 可以监听加载状态(如 loading);
  • 支持设置 URL 参数(如指定页码);
  • 可扩展性更强,适用于需要精细控制的业务场景。

这种方法相比直接嵌入 <object> 更具灵活性。