Skip to content

PDF 在线预览

注意

Nginx 服务器需要处理 mjs !!!

Nginx 服务器需要处理 mjs !!!

Nginx 服务器需要处理 mjs !!!

百度 nginx mjs 有相关文献,如 https://segmentfault.com/a/1190000041954539

因为 体测报告的内容并不多,所以处理的很简单。

获取并且初始化 PDF Document 对象。

尽量使用 min 包。min 包为 1.4M,未压缩的有 2.2M。对于没有 CDN 的服务器这也是不小的压力。

ts
import * as pdfjsLib from 'pdfjs-dist'

// PDF Worker configuration
import workerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'

const loadPDF = async (url: string) => {
  const loadingTask = pdfjsLib.getDocument(url)
  pdfDocument = await loadingTask.promise

  if (state.init === false) {
    state.init = true
    emit('init')
  }

  console.log('todo 如果 pdf 页数太多,考虑虚拟列表' )

  state.totalPages = pdfDocument.numPages

  await nextTick()

  for (let i = 1; i <= state.totalPages; i++) {
    renderPage(i)
  }
}

渲染一页

ts
async function renderPage(pageNum: number) {
  if (!pdfDocument) return
  if (!pdfContainer.value) return

  console.log('pdfContainer', pdfContainer.value.children)

  if (!pdfContainer.value.children) return
  const canvas = pdfContainer.value.children[pageNum - 1] as HTMLCanvasElement
  if (!canvas) return
  const context = canvas.getContext('2d')
  if (!context) return

  const page = await pdfDocument.getPage(pageNum)

  const viewport = page.getViewport({ scale: 1 })

  canvas.height = viewport.height
  canvas.width = viewport.width

  const renderContext = {
    canvasContext: context,
    viewport: viewport
  }

  return page.render(renderContext).promise
}

完整代码

vue
<template>
  <div class="pdf-viewer">
    <div v-if="state.init" class="pdf-container" ref="pdfContainer">
      <canvas class="pdf-page" v-for="page in state.totalPages" :key="page"></canvas>
    </div>
    <div class="loading-container" v-else>
      <svg
        class="loading-icon"
        xmlns="http://www.w3.org/2000/svg"
        viewBox="0 0 24 24"
        fill="currentColor"
      >
        <path
          d="M12 2C12.5523 2 13 2.44772 13 3V6C13 6.55228 12.5523 7 12 7C11.4477 7 11 6.55228 11 6V3C11 2.44772 11.4477 2 12 2ZM12 17C12.5523 17 13 17.4477 13 18V21C13 21.5523 12.5523 22 12 22C11.4477 22 11 21.5523 11 21V18C11 17.4477 11.4477 17 12 17ZM22 12C22 12.5523 21.5523 13 21 13H18C17.4477 13 17 12.5523 17 12C17 11.4477 17.4477 11 18 11H21C21.5523 11 22 11.4477 22 12ZM7 12C7 12.5523 6.55228 13 6 13H3C2.44772 13 2 12.5523 2 12C2 11.4477 2.44772 11 3 11H6C6.55228 11 7 11.4477 7 12ZM19.0711 19.0711C18.6805 19.4616 18.0474 19.4616 17.6569 19.0711L15.5355 16.9497C15.145 16.5592 15.145 15.9261 15.5355 15.5355C15.9261 15.145 16.5592 15.145 16.9497 15.5355L19.0711 17.6569C19.4616 18.0474 19.4616 18.6805 19.0711 19.0711ZM8.46447 8.46447C8.07394 8.85499 7.44078 8.85499 7.05025 8.46447L4.92893 6.34315C4.53841 5.95262 4.53841 5.31946 4.92893 4.92893C5.31946 4.53841 5.95262 4.53841 6.34315 4.92893L8.46447 7.05025C8.85499 7.44078 8.85499 8.07394 8.46447 8.46447ZM4.92893 19.0711C4.53841 18.6805 4.53841 18.0474 4.92893 17.6569L7.05025 15.5355C7.44078 15.145 8.07394 15.145 8.46447 15.5355C8.85499 15.9261 8.85499 16.5592 8.46447 16.9497L6.34315 19.0711C5.95262 19.4616 5.31946 19.4616 4.92893 19.0711ZM15.5355 8.46447C15.145 8.07394 15.145 7.44078 15.5355 7.05025L17.6569 4.92893C18.0474 4.53841 18.6805 4.53841 19.0711 4.92893C19.4616 5.31946 19.4616 5.95262 19.0711 6.34315L16.9497 8.46447C16.5592 8.85499 15.9261 8.85499 15.5355 8.46447Z"
        ></path>
      </svg>

      <div>加载中...</div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { watch, useTemplateRef, reactive, nextTick } from 'vue'
import * as pdfjsLib from 'pdfjs-dist'

// PDF Worker configuration
import workerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'

pdfjsLib.GlobalWorkerOptions.workerSrc = workerUrl

const props = defineProps<{
  url: string
}>()
const emit = defineEmits(['init', 'faild'])

let pdfDocument: pdfjsLib.PDFDocumentProxy | null = null

const pdfContainer = useTemplateRef<HTMLDivElement>('pdfContainer')

const state = reactive({
  totalPages: 0,
  currentPage: 1,
  init: false
})

watch(
  () => props.url,
  (newUrl) => {
    if (newUrl) {
      loadPDF(newUrl).catch((e) => {
        emit('faild', e)
      })
    }
  }
)

const loadPDF = async (url: string) => {
  const loadingTask = pdfjsLib.getDocument(url)
  pdfDocument = await loadingTask.promise

  if (state.init === false) {
    state.init = true
    emit('init')
  }

  console.log('todo 如果 pdf 页数太多,考虑虚拟列表', pdfContainer.value)

  state.totalPages = pdfDocument.numPages

  await nextTick()

  for (let i = 1; i <= state.totalPages; i++) {
    renderPage(i)
  }
}

async function renderPage(pageNum: number) {
  if (!pdfDocument) return
  if (!pdfContainer.value) return

  console.log('pdfContainer', pdfContainer.value.children)

  if (!pdfContainer.value.children) return
  const canvas = pdfContainer.value.children[pageNum - 1] as HTMLCanvasElement
  if (!canvas) return
  const context = canvas.getContext('2d')
  if (!context) return

  const page = await pdfDocument.getPage(pageNum)

  const viewport = page.getViewport({ scale: 1 })

  canvas.height = viewport.height
  canvas.width = viewport.width

  const renderContext = {
    canvasContext: context,
    viewport: viewport
  }

  return page.render(renderContext).promise
}
</script>

<style scoped>
.pdf-viewer {
  width: 100%;
  height: 100%;
}

.pdf-container {
  flex: 1;
  overflow: auto;
  display: flex;
  justify-content: center;
  flex-direction: column;
  /* background-color: #525659; */
  gap: 12px;
}

.pdf-page {
  /* box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); */
  background-color: white;
}

.loading-container {
  margin-top: 120px;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  gap: 24px;
}

.loading-icon {
  width: 36px;
  height: 36px;
  animation: rotate 2s linear infinite;
}

@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
</style>