vue2中pdf文件虛擬滾動示例

  • 背景
    我無法使用"vue-virtual-scroller": "1.1.2"使pdf虛擬滾動,所以自己開始研究學習虛擬滾動原理,pdf文件的虛擬滾動是在另一篇文章 js原生虛擬滾動原理及示例的基礎上實現。
  • 思路
    通過pdfjs獲取每頁的數據并創建canvas元素及上下文,先存儲到列表中,將需要展示頁碼范圍的頁面信息從列表中取出,將相關的canvas元素append到dom節點,然后調用pdfjs的page渲染函數
  • 依賴版本
    "pdfjs-dist": "2.5.207",
    "vue": "2.6.11",
  • 示例
    parent.vue
<template>
  <div>
    <div class="pdf-container">
      <VirtualList
        :list="pages"
        :item-height="pageHeight"
        :wrapHeight="600"
        :renderDone="renderDone"
        @pageShowUpdate="pageShowUpdate"
      >
        <template #default>
          <div id="wrap-canvas" ref="wrap-canvas"></div>
        </template>
      </VirtualList>
    </div>
  </div>
</template>

<script>
import VirtualList from "./VirtualScrollPdfItem.vue";
import * as pdfjsLib from "pdfjs-dist";
import pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;
export default {
  components: {
    VirtualList,
  },
  data() {
    return {
      url: "http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf",
      // url: "http://www.pdf995.com/samples/pdf.pdf",
      totalPages: null,
      pdfDoc: null,
      pages: [],
      pageHeight: 0,
      canvasList: [],
      renderDone: false,
    };
  },
  mounted() {
    this.init();
  },
  methods: {
    async init() {
      var loadingTask = pdfjsLib.getDocument(this.url);
      console.log(111, loadingTask);
      this.pdfDoc = await loadingTask.promise;
      this.totalPages = this.pdfDoc.numPages;
      console.log("totalpage", this.totalPages);

      this.initPages();
      this.$nextTick(() => this.renderPages());
    },
    async renderPages() {
      for (let i = 1; i <= this.totalPages; i++) {
        const page = await this.pdfDoc.getPage(i);
        const viewport = page.getViewport({ scale: 1 });
        const canvas = document.createElement("canvas");
        const context = canvas.getContext("2d");

        // 適配Retina屏幕
        // const dpr = window.devicePixelRatio || 1;
        const dpr = 1;
        canvas.width = viewport.width * dpr;
        canvas.height = viewport.height * dpr;
        this.pageHeight = canvas.height;
        context.scale(dpr, dpr);
        const item = {
          page,
          canvasContext: context,
          viewport,
          canvas,
        };
        this.canvasList.push(item);
        this.renderDone = true;
      }
      console.log("renderdone");
    },
    pageShowUpdate(showPages) {
      console.log("render canvas", this.$refs);

      this.$refs["wrap-canvas"].innerHTML = "";
      showPages.split(",").forEach((p) => {
        this.renderCanvas(+p);
      });
    },
    renderCanvas(i) {
      const { page, canvasContext, viewport, canvas } = this.canvasList[i];
      this.$refs["wrap-canvas"].appendChild(canvas);
      console.log("wrap-canvas", this.$refs["wrap-canvas"]);
      this.$nextTick(() => {
        console.log("render page");
        page.render({
          canvasContext,
          viewport,
        }).promise;
      });
    },
    initPages() {
      this.pages = Array.from({ length: this.totalPages }, (_, i) => ({
        pageNumber: i + 1,
      }));
    },
  },
};
</script>

<style scoped>
.pdf-container {
  max-width: 700px;
  margin: 0 auto;
}
</style>

item.vue

<template>
  <!-- 顯示容器 -->
  <div id="scroll-container" ref="scroll-container" @scroll="onScroll">
    <!-- 使顯示容器出現滾動條 -->
    <div
      id="content"
      ref="content"
      :style="{ height: itemHeight * list.length + 'px' }"
    >
      <!-- 列表容器 -->
      <div id="wrap" ref="wrap">
        <slot name="default" />
      </div>
    </div>
  </div>
</template>
 
<script>
export default {
  name: "VirtualScrollPdfItem",
  props: {
    list: {
      type: Array,
      default: () => [],
    },
    itemHeight: {
      type: Number,
      default: 200,
    },
    wrapHeight: {
      type: Number,
      default: 600,
    },
    renderDone: {
      type: Boolean,
      default: false,
    },
  },

  data() {
    return {
      pageShow: "",
    };
  },
  watch: {
    pageShow(n) {
      console.log("watch pageShow", n);

      this.$emit("pageShowUpdate", n);
    },
    renderDone(n) {
      if (n) {
        this.onScroll();
      }
    },
  },
  methods: {
    onScroll() {
      const scrollTop = this.$refs["scroll-container"].scrollTop;
      // console.log("scrolltop", scrollTop);
      const start = scrollTop / this.itemHeight;
      // 開始展示的項目索引
      const startIndex = Math.floor(scrollTop / this.itemHeight);
      // 結束的項目索引
      let endIndex = Math.ceil(
        (scrollTop + this.$refs["scroll-container"].clientHeight) /
          this.itemHeight
      );
      if (endIndex > this.list.length) {
        endIndex = this.list.length;
      }
      // 是否剛好展示第一個完整項
      const isZero = scrollTop % this.itemHeight === 0;
      // 不是完整項則計算偏移量
      const offset = isZero
        ? 0
        : this.itemHeight -
          (Math.ceil(scrollTop / this.itemHeight) * this.itemHeight -
            scrollTop);
      this.renderItems(startIndex, endIndex, offset, scrollTop);
    },
    renderItems(startIndex, endIndex, offset, scrollTop) {
      console.log("start end offset", startIndex, endIndex, offset);
      this.pageShow = "";
      for (let i = startIndex; i < endIndex; i++) {
        console.log("pageshow for", this.pageShow);
        if (i === endIndex - 1) {
          this.pageShow = this.pageShow + i;
        } else {
          this.pageShow = this.pageShow + i + ",";
        }
      }
      console.log("pageshow for end", this.pageShow);

      // 計算最大滾動距離,超過后鼠標滾輪滾動后不再滾動
      const maxScrollTop = this.list.length * this.itemHeight - this.wrapHeight;
      console.log("maxscrolltop", maxScrollTop, scrollTop, offset);

      if (scrollTop >= maxScrollTop) {
        // 調整列表容器位置
        this.$refs.wrap.style.top = maxScrollTop - offset + "px";
        // 讓滾動條移動
        this.$refs["scroll-container"].scrollTop = maxScrollTop;
      } else {
        // 調整列表容器位置和滾動條位置
        this.$refs.wrap.style.top = scrollTop - offset + "px";
        this.$refs["scroll-container"].scrollTop = scrollTop;
      }
    },
  },
};
</script>
 
<style scoped>
#scroll-container {
  position: relative;
  overflow-y: scroll;
  height: 600px;
  border: 1px solid blue;
}
#content {
  width: 100%;
}
#wrap {
  position: absolute;
  top: 0;
  left: 0;
  height: 600px;
  width: 100%;
}
</style>

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容