Vue实现图片预览,侧边栏懒加载,不用任何插件,简单好用

news2024/12/22 20:03:59

实现样式

在这里插入图片描述

需求

实现PDF上传预览,并且不能下载

第一次实现:用vue-pdf,将上传的文件用base64传给前端展示
问题:

  1. 水印第一次加载有后面又没有了。
  2. 当上传大的pdf文件后,前端获取和渲染又长又慢,甚至不能用

修改实现模式

  1. 前端上传PDF,后端将PDF转化成一页一页的图片
  2. 前端根据page去获取一页一页的PDF图片,类似于百度文库

实现思路

配合后端实现思路

  1. 获取全部页数,先把侧边栏的元素画出来占个位置
  2. 获取已经看到的页数,没有默认1
  3. 渲染上次看到的页数,同时侧边栏滚动到相同的index位置,通过监听元素是否进入视口去获取base64图片
  4. 已经获取回来的图片不再去请求

主要重点难点是侧边栏懒加载定位等比例展示图片

 <div class="pdf-viewer">
   <div class="pdf-main">
     <canvas id="pdf-view"></canvas>
   </div>
   <div class="pdf-list" :class="{ collapse: collapse }">
     <div
       class="pdf-item"
       :class="{ active: currentPage === index }"
       v-for="index in pageTotalNum"
       :key="index"
       @click="changePage(index)"
       :data-index="index"
     >
       <img :src="imgList[index - 1]" alt="" />
     </div>
   </div>
 </div>

<script>
let observer = null;
export default {
  name: "PDFView",
  data() {
    return {
      currentPage: 1, //当前页数
      pageTotalNum: 1, //总页数
      imgList: [], //base64图片列表
      updateTimer: null
    };
  },
  watch: {
    /**
     * @description 监听当前页变化 滚动列表到顶部
     */
    currentPage() {
      this.$nextTick(() => {
        const activeEl = document.querySelector(".pdf-list .active");
        if (activeEl) {
          document.querySelector(".pdf-list").scrollTo({
            top: activeEl.offsetTop - 20,
            behavior: "smooth",
          });
          // 解决进来会请求当前页数 前面所有图片
          setTimeout(() => {
            if (observer) {
              observer.disconnect();
            }
            this.isEnter();
          }, 500);
        }
        // 切换页面 将查看区域滚动到最上面
        const mainEl = document.querySelector(".pdf-main");
        mainEl.scrollTo({
          top: 0,
        });
      });
    },
  },
  mounted() {
    this.getPageTotal();
  },
  beforeDestroy() {
    if (observer) {
      observer.disconnect();
    }
  },
  methods: {
    /**
     * @description 获取pdf总页数
     */
    getPageTotal() {
      const params = {
        id: this.$route.query.id,
      };
      apiGetViewPdfPageTotal(params).then((response) => {
        this.pageTotalNum = response.data;
        this.updateStudy(true);
      });
    },
    /**
     * @description 切换当前页
     */
    changePage(index) {
      this.currentPage = index;
      this.updateStudy();
      if (this.imgList[index - 1]) {
        this.drawImage(this.imgList[index - 1]);
      } else {
        this.getPdf();
      }
    },
    /**
     * @description 上一页
     */
    prePage() {
      let page = this.currentPage;
      if (page !== 1) {
        page = page > 1 ? page - 1 : this.pageTotalNum;
        this.currentPage = page;
        this.updateStudy();
        if (this.imgList[page - 1]) {
          this.drawImage(this.imgList[page - 1]);
        } else {
          this.getPdf();
        }
      }
    },
    /**
     * @description 下一页
     */
    nextPage() {
      let page = this.currentPage;
      if (page !== this.pageTotalNum) {
        page = page < this.pageTotalNum ? page + 1 : 1;
        this.currentPage = page;
        this.updateStudy();
        if (this.imgList[page - 1]) {
          this.drawImage(this.imgList[page - 1]);
        } else {
          this.getPdf();
        }
      }
    },
    /**
     * @description 更新学习 flag=true第一次进入
     */
    updateStudy(flag = false) {
      const params = {
        courseId: this.$route.query.id,
        pageRate: this.currentPage,
        flag,
        totalPageRate: this.pageTotalNum,
      };
      apiUpdateStudy(params)
        .then((response) => {
          this.currentPage = response.data.pageRate;
          if (flag) {
            this.updateTimer = setInterval(() => {
              this.updateStudy();
            }, 1000 * 10);
          }
          if (flag) {
            this.getPdf();
            // 解决第一页进来不请求的问题,一页大概能展示4-5张
            if (this.currentPage < 5) {
              this.isEnter();
            }
          }
        })
    },
    /**
     * @description 查看资料
     */
    getPdf() {
      const params = {
        id: this.$route.query.id,
        page: this.currentPage,
      };
      apiGetPdf(params).then((response) => {
        let base64 = "data:image/png;base64," + response.data;
        this.drawImage(base64);
      });
    },
    /**
     * @description 将base64图片 画到canvas上
     */
    drawImage(base64) {
      const canvas = document.getElementById("pdf-view");
      const context = canvas.getContext("2d");
      const image = new Image();
      image.src = base64;
      image.onload = () => {
        const proportion = image.width / image.height;
        // 获取style设置width:100% 的canvas宽度
        const canvasWidth = canvas.offsetWidth;
        // 图片宽度与canvas宽度比例
        const canvasWidthProportion = image.width / canvasWidth;
        // canvas宽度设置为宽度
        canvas.width = image.width;
        // 根据图片比例和宽度比例计算出canvas高度
        canvas.height = (canvasWidth / proportion) * canvasWidthProportion;
        context.drawImage(image, 0, 0);
      };
    },
    /**
     * @description 监听元素进入视口
     */
    isEnter() {
      observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
          const target = entry.target;
          const index = target.dataset.index;
          if (entry.isIntersecting) {
            if (!this.imgList[index - 1]) {
              this.getImgList(index);
            }
          } else {
            // console.log("元素离开视口", index);
          }
        });
      });
      this.$nextTick(() => {
      //将所有侧边栏的元素进行监听
        const els = document.querySelectorAll(".pdf-item");
        Array.from(els).forEach((el) => {
          observer.observe(el);
        });
      });
    },
    /**
     * @description 滚动获取图片
     */
    getImgList(index) {
      const params = {
        id: this.$route.query.id,
        page: index,
      };
      apiGetPdf(params).then((response) => {
        let base64 = "data:image/png;base64," + response.data;
        this.imgList[index - 1] = base64;
        // 解决请求回来页面没更新的问题
        this.$forceUpdate();
      });
    },
  },
};
</script>

<style lang="scss" scoped>
.pdf-container {
  width: 100%;
  height: 100%;
  color: #999;
}
.pdf-viewer {
  width: 100%;
  height: calc(100vh - 50px - 30px - 60px - 6px);
  position: relative;
  display: flex;
}
.pdf-list {
  width: 240px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  padding: 20px;
  background: #000;
  box-sizing: border-box;
  // transition: all 0.3s ease-in-out;
  border-left: 1px solid #999;
  &::-webkit-scrollbar {
    width: 0px;
  }
  .pdf-item {
    height: 183px;
    min-height: 183px;
    display: inline-flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    overflow: hidden;
    &:hover {
      ::v-deep img {
        transition: all 0.5s ease-in-out;
        transform: scale(1.1);
      }
    }
    &.active {
      box-shadow: 0px 0px 0px 4px #e6a23c;
    }
    &:not(:last-child) {
      margin-bottom: 10px;
    }
    img {
      pointer-events: none;
      width: 100%;
      // height: 100%;
    }
  }
  &.collapse {
    width: 0;
    padding: 0;
  }
}
.pdf-main {
  flex: 1;
  // width: 100%;
  // height: 100%;
  overflow-y: auto;
  background: #000;
  position: relative;
  padding: 10px 0;
  &::-webkit-scrollbar {
    width: 0px;
  }
}
.handle-btn {
  background: #000;
  display: flex;
  font-size: 12px;
  position: relative;
  height: 60px;
  padding: 0 6px;
  border-bottom: 1px solid #999;
  .right {
    width: 240px;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    font-size: 32px;
  }
  .main {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 32px;
    margin-left: 250px;
    .pagination {
      display: flex;
      align-items: center;
      margin: 0 10px;
      .pagination-info {
        font-size: 14px;
        margin: 0 8px;
      }
    }
    .zoom {
      display: flex;
      align-items: center;
      margin: 0 10px;
      .scale {
        font-size: 14px;
        margin: 0 8px;
      }
    }
  }
  .tips {
    color: #e6a23c;
    font-size: 12px;
  }
  .start-test {
    display: flex;
    align-items: center;
  }
  .time {
    position: absolute;
    left: 6px;
    top: 50%;
    transform: translateY(-50%);
    > span {
      display: inline-block;
      margin-left: 10px;
    }
  }
}
i {
  cursor: pointer;
  &:hover {
    color: #fff;
  }
}
#pdf-view {
  width: 100%;
  // height: 100%;
  padding: 10px;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1409549.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows下网络编程(win32API+VS2022)

一、开发环境 我这里介绍下我用的环境安装过程。 所有版本的VS都可以的。 我当前环境是在Windows下&#xff0c;IDE用的是地表最强IDE VS2022。 下载地址&#xff1a;https://visualstudio.microsoft.com/zh-hans/downloads/ 因为我这里只需要用到C和C语言编程&#xff0c;那…

SRC实战 | 小白SRC找到的第一个SQL注入

本文由掌控安全学院 - zbs投稿 一、漏洞说明 xxxxx公司后台存在SQL注入&#xff0c;后端数据库为Mysql 【显错位2&#xff0c;4&#xff0c;6】 漏洞已提交平台&#xff0c;后台的开发商提供给了很多公司&#xff0c;搜一下资产就有很多公司都没有修复该漏洞。 二、漏洞挖掘…

Java Web(四)--JavaScript

介绍 JavaScript 教程 JavaScript 能改变 HTML 内容&#xff0c;能改变 HTML 属性&#xff0c;能改变 HTML 样式 (CSS)&#xff0c;能完成页面的数据验证&#xff1b; JS 需要运行浏览器来解析执行JavaScript 代码&#xff1b; JS 是 Netscape 网景公司的产品&#xf…

Git搭建

文件格式 <VirtuaHost * 80> nginx </virtualHost> pache xml server {} nginx conf格式 [xx] 配置内容 代码开发中版本控制,项目代码编译构建,项目版本迭代全流程 命令300条 Hospital Information System 开发语言分类: 编译型: C nginx ma…

SSL证书 DV、OV、EV等级的证书适用群体

DV&#xff08;Domain Validation&#xff0c;域名验证&#xff09;证书 特点&#xff1a;DV证书是最基础的SSL/TLS证书类型&#xff0c;仅验证申请证书的实体是否对该域名有控制权。验证过程相对简单快速&#xff0c;通常只需要验证域名的所有权即可。 适用人群&#xff1a;…

蓝桥杯备战——2.矩阵键盘

1.分析原理图 由上图可以看到若J5跳线帽接地&#xff0c;就S4~S7就可以当做四路独立按键&#xff0c;若接到P44&#xff0c;则就是4*4的矩阵键盘。 2.独立按键处理 相对传统的按键延时消抖方案&#xff0c;这里我采用更高效&#xff0c;更经典&#xff0c;更偏向产品级应用的…

2016年认证杯SPSSPRO杯数学建模B题(第一阶段)低分辨率下看世界全过程文档及程序

2016年认证杯SPSSPRO杯数学建模 B题 低分辨率下看世界 原题再现&#xff1a; 数码摄像技术被广泛使用于多种场合中。有时由于客观条件的限制&#xff0c;拍摄设备只能在较低的分辨率下成像。为简单起见&#xff0c;我们只考虑单色成像。假设成像的分辨率为 32 64&#xff0c…

一文读懂mysql的锁

提起mysql的锁&#xff0c;你是否会似懂非懂&#xff0c;最常听人提起的就是乐观锁&#xff0c;悲观锁、排他锁、共享锁 悲观锁是用 select c form T for update然后等待提交实现的&#xff0c;但是你知道吗&#xff0c;其实排他锁和悲观锁其实是一回事&#xff01;&#xff0…

vue常用指令(v-if)

一、v-if 指令 作用: 根据表达值的真假,切换元素的显示和隐藏( 操纵dom 元素 ) 二、代码演示 1、v-if 设置条件&#xff0c;添加按键修改状态 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta na…

12. banner 定制

1. 简介 Spring Boot 启动时默认会显示以下 LOGO&#xff1a; . ____ _ __ _ _ /\ / ’ __ _ () __ __ _ \ \ \ ( ( )__ | _ | | | ’ / | \ \ \ \/ )| |)| | | | | || (| | ) ) ) ) ’ || .__|| ||| |_, | / / / / |||///// :: Spring Boot :: (v2.1.1.RELEASE) 实际上&am…

沃通服务器密码机(WTHSM)

概述 沃通服务器密码机&#xff08;WTHSM&#xff09;由沃通CA自主设计开发&#xff0c;严格遵照国密局颁布技术规范&#xff0c;获得国密局颁发《商用密码产品认证证书》&#xff0c;是一款多安全功能、高稳定性、可扩展和快速部署的软硬件集成化安全设备&#xff0c;为应用提…

[RK-Linux] 移植Linux-5.10到RK3399(十)| 配置AP6256模组使能WIFI、BT功能

手上 ROC-RK3399-PC Pro 使用蓝牙 WIFI 模组是 AP6256。 一、AP6256 模组介绍 AP6256是正基科技(AMPAK)推出的一款低成本、低功耗的双模模块,它集成了Wi-Fi和蓝牙功能。这款模块支持SDIO接口,具有以下特点: 1、型号:AP6256 2、接口:SDIO(Secure Digital Input/Outp…

MySQL--数据类型(4)

MySQL 中定义数据字段的类型对你数据库的优化是非常重要的。 MySQL 支持多种类型&#xff0c;大致可以分为三类&#xff1a;数值、日期/时间和字符串(字符)类型。 数值类型 MySQL 支持所有标准 SQL 数值数据类型。 这些类型包括严格数值数据类型(INTEGER、SMALLINT、DECIMAL …

SUBMIT指定用户名错误

1、SUBMIT说明 在ABAP中&#xff0c;SUBMIT关键字用于运行另一个ABAP程序。通过SUBMIT关键字&#xff0c;可以在当前程序内部调用其他程序&#xff0c;而无需关闭当前程序。 SUBMIT语句的一般语法如下&#xff1a; "--------------------斌将军-------------------- SUB…

有关软件测试的,任何时间都可以,软件测试主要服务项目:测试用例 报告 计划

有关软件测试的&#xff0c;任何时间都可以&#xff0c;软件测试主要服务项目&#xff1a; 1. 测试用例 2. 测试报告 3. 测试计划 4. 白盒测试 5. 黑盒测试 6. 接口测试 7.自动…

护眼大路灯什么牌子好?公认好用大路灯推荐

一段时间孩子经常搓眼睛&#xff0c;还说眼睛痛&#xff0c;带她去看了眼科医生&#xff0c;医生说应该是用眼习惯不好导致&#xff0c;测了远视储备&#xff0c;虽然正常&#xff0c;但是也是正常范围的临界点了&#xff0c;医生建议多注意孩子的用眼健康&#xff0c;可以减慢…

大文件分块上传进度控制处理

说这个之前&#xff0c;需要对上一篇文章进行一个回顾文件上传之大文件分块上传-CSDN博客 如果我们要对文件进行一个进度的控制&#xff0c;我们可以在upload接口上进行一些操作。 服务器端&#xff1a; 在上一篇&#xff0c;我们已经获取到totalChunks&#xff1a;也就是分…

wayland(xdg_wm_base) + egl + opengles 最简实例

文章目录 前言一、ubuntu 下相关环境准备1. 获取 xdg_wm_base 依赖的相关文件2. 查看 ubuntu 上安装的opengles 版本3. 查看 weston 所支持的 窗口shell 接口种类二、xdg_wm_base 介绍三、egl_wayland_demo1.egl_wayland_demo2_0.c2.egl_wayland_demo3_0.c3. xdg-shell-protoco…

springboot项目快速引入knife4j

引入依赖 <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>3.0.3</version> </dependency>knife4j配置文件 basePackage改为自己存放接口的包名 /*** Kn…

Zookeeper3.5.7源码分析

文章目录 一、Zookeeper算法一致性1、Paxos 算法1.1 概述1.2 算法流程1.3 算法缺陷 2、ZAB 协议2.1 概述2.2 Zab 协议内容 3、CAP理论 二、源码详解1、辅助源码1.1 持久化源码(了解)1.2 序列化源码 2、ZK 服务端初始化源码解析2.1 启用脚本分析2.2 ZK 服务端启动入口2.3 解析参…