【PPTist】幻灯片放映

在这里插入图片描述
放映功能的代码都在 src/hooks/useScreening.ts,我们看一下 从当前页开始 放映的功能。

// 进入放映状态(从当前页开始)
const enterScreening = () => {
  enterFullscreen()
  screenStore.setScreening(true)
}

首先是 enterFullscreen(),进入全屏放映
src/utils/fullscreen.ts

// 进入全屏
export const enterFullscreen = () => {
  const docElm = document.documentElement
  if (docElm.requestFullscreen) docElm.requestFullscreen() 
  else if (docElm.mozRequestFullScreen) docElm.mozRequestFullScreen() 
  else if (docElm.webkitRequestFullScreen) docElm.webkitRequestFullScreen()
  else if (docElm.msRequestFullscreen) docElm.msRequestFullscreen()
}

进入全屏指的是整个编辑器 document.documentElement 进入全屏
在这里插入图片描述

就先找一下当前的浏览器能用的全屏的方法,然后调用。
然后执行 screenStore.setScreening(true)
src/store/screen.ts

setScreening(screening: boolean) {
  this.screening = screening
},

然后通过这个属性控制的App..vue中的组件的显示
src/App.vue

<template>
  <Screen v-if="screening" />
  <Editor v-else-if="_isPC" />
  <Mobile v-else />
</template>

进入放映模式
src/views/Screen/index.vue

<template>
  <div class="pptist-screen">
    <BaseView :changeViewMode="changeViewMode" v-if="viewMode === 'base'" />
    <PresenterView :changeViewMode="changeViewMode" v-else-if="viewMode === 'presenter'" />
  </div>
</template>

通过幻灯片放映的下拉框进入的是普通视图,就是 BaseView。然后右键有一个演讲者视图,就是 PresenterView
在这里插入图片描述

1、普通视图

src/views/Screen/BaseView.vue
右键点击工具栏的时候右下角会浮现工具栏
在这里插入图片描述
这里面的内容其实跟右键菜单项差不多

① 画笔工具

右键点击画笔工具时会出现画笔工具 src/views/Screen/WritingBoardTool.vue
在这里插入图片描述
使用画笔工具的时候,sizePopoverType 这个属性用来表示当前使用的是哪个工具。
绘制过程的组件 src/components/WritingBoard.vue
绘制过程分为三个阶段,鼠标落下、鼠标移动、鼠标抬起

  • mousedown
// 处理鼠标(触摸)事件
// 准备开始绘制/擦除墨迹(落笔)
const handleMousedown = (e: MouseEvent | TouchEvent) => {
  // 获取鼠标在canvas中的相对位置
  const [mouseX, mouseY] = getMouseOffsetPosition(e)
  // 计算鼠标在canvas中的绝对位置
  const x = mouseX / widthScale.value
  const y = mouseY / heightScale.value

  // 设置鼠标状态
  isMouseDown = true
  lastPos = { x, y }
  lastTime = new Date().getTime()

  // 设置鼠标状态
  if (!(e instanceof MouseEvent)) {
    mouse.value = { x: mouseX, y: mouseY }
    mouseInCanvas.value = true
  }
}
  • mousemove
// 开始绘制/擦除墨迹(移动)
const handleMousemove = (e: MouseEvent | TouchEvent) => {
  // 获取鼠标在canvas中的相对位置
  const [mouseX, mouseY] = getMouseOffsetPosition(e)
  // 计算鼠标在canvas中的绝对位置
  const x = mouseX / widthScale.value
  const y = mouseY / heightScale.value

  // 设置鼠标位置
  mouse.value = { x: mouseX, y: mouseY }

  // 开始绘制/擦除墨迹
  if (isMouseDown) handleMove(x, y)
}
// 路径操作
const handleMove = (x: number, y: number) => {
  const time = new Date().getTime()

  if (props.model === 'pen') {
    const s = getDistance(x, y)
    const t = time - lastTime
    const lineWidth = getLineWidth(s, t)

    draw(x, y, lineWidth)
    lastLineWidth = lineWidth
  }
  else if (props.model === 'mark') draw(x, y, props.markSize)
  else erase(x, y)

  lastPos = { x, y }
  lastTime = new Date().getTime()
}

其中画画、橡皮擦是通过 canvas 实现的

// 绘制画笔墨迹方法
const draw = (posX: number, posY: number, lineWidth: number) => {
  if (!ctx) return

  const lastPosX = lastPos.x
  const lastPosY = lastPos.y

  ctx.lineWidth = lineWidth
  ctx.strokeStyle = props.color
  ctx.beginPath()
  ctx.moveTo(lastPosX, lastPosY)
  ctx.lineTo(posX, posY)
  ctx.stroke()
  ctx.closePath()
}
  • mouseup
// 结束绘制/擦除墨迹(停笔)
const handleMouseup = () => {
  if (!isMouseDown) return
  isMouseDown = false
  emit('end')
}

结束绘制会被父组件监听
src/views/Screen/WritingBoardTool.vue

// 每次绘制完成后将绘制完的图片更新到数据库
const hanldeWritingEnd = () => {
  const dataURL = writingBoardRef.value!.getImageDataURL()
  if (!dataURL) return

  db.writingBoardImgs.where('id').equals(currentSlide.value.id).toArray().then(ret => {
    const currentImg = ret[0]
    if (currentImg) db.writingBoardImgs.update(currentImg, { dataURL })
    else db.writingBoardImgs.add({ id: currentSlide.value.id, dataURL })
  })
}

将绘制的内容存储到数据库中,切换幻灯片的时候,会查看当前幻灯片有没有对应的绘制笔迹

// 打开画笔工具或切换页面时,将数据库中存储的墨迹绘制到画布上
watch(currentSlide, () => {
  db.writingBoardImgs.where('id').equals(currentSlide.value.id).toArray().then(ret => {
    const currentImg = ret[0]
    writingBoardRef.value!.setImageDataURL(currentImg?.dataURL || '')
  })
}, { immediate: true })
② 自动放映

自动放映的方法在这里:src/views/Screen/hooks/useExecPlay.ts

// 自动播放
const autoPlayInterval = ref(2500)
const autoPlay = () => {
  closeAutoPlay()
  message.success('开始自动放映')
  autoPlayTimer.value = setInterval(execNext, autoPlayInterval.value)
}

execNext() 方法咱们以前见过,就是下一张的方法。可以看到自动放映就是每隔固定时间执行下一张的方法

③ 循环放映
// 循环放映
const loopPlay = ref(false)
const setLoopPlay = (loop: boolean) => {
  loopPlay.value = loop
}

循环播放通过 loopPlay 控制。
在执行 execNext() 方法的时候,如果执行到最后一张幻灯片,就会需要判断是否是循环放映模式,如果是,就将幻灯片从头开始,

const execNext = () => {
  if (formatedAnimations.value.length && animationIndex.value < formatedAnimations.value.length) {
    runAnimation()
  }
  else if (slideIndex.value < slides.value.length - 1) {
    slidesStore.updateSlideIndex(slideIndex.value + 1)
    animationIndex.value = 0
    inAnimation.value = false
  }
  else {
    // 如果循环播放,则切换到第一页
    if (loopPlay.value) turnSlideToIndex(0)
    else {
      throttleMassage('已经是最后一页了')
      closeAutoPlay()
    }
    inAnimation.value = false
  }
}
④ 查看所有幻灯片

点击“查看所有幻灯片”时,会显示 src/views/Screen/SlideThumbnails.vue 组件,页面就变成这样了
在这里插入图片描述

不过这页面可以看出来挺简单的,点击某一张幻灯片的时候,会进入目标幻灯片的放映模式

const turnSlide = (index: number) => {
  props.turnSlideToIndex(index)
  emit('close')
}

src/views/Screen/hooks/useExecPlay.ts

// 切换幻灯片到指定的页面
const turnSlideToIndex = (index: number) => {
  slidesStore.updateSlideIndex(index)
  animationIndex.value = 0
}

触发 close 方法,就是隐藏这个 SlideThumbnails 组件

@close="slideThumbnailModelVisible = false"
2、演讲者视图

在这里插入图片描述

① 画笔

画笔跟上面的画笔工具是一个组件,由此可知组件化的重要性,功能复用多么方便!
在这里插入图片描述

② 激光笔

设置成激光笔模式的时候,内容区域会增加一个类名

<div 
  class="slide-list-wrap" 
  :class="{ 'laser-pen': laserPen }" 
  ref="slideListWrapRef"
>

是用来设置 cursor 属性的,显示成一个小圆点,是通过base64设置的,代码太长了我就不粘贴了
在这里插入图片描述

然后激光笔好像没有别的作用了,就是让鼠标更明显一些。

③ 计时器

计时器会显示这么一个小组件
在这里插入图片描述
src/views/Screen/CountdownTimer.vue
表示分和秒的两个小框框是由不可编辑的 input 框组成

<input 
  type="text"
  :value="fillDigit(minute, 2)"
  :maxlength="3" :disabled="inputEditable"
  @mousedown.stop 
  @blur="$event => changeTime($event, 'minute')"
  @keydown.stop
  @keydown.enter.stop="$event => changeTime($event, 'minute')"
>

开始计时的时候,会设置计时器

const start = () => {
  clearTimer()

  if (isCountdown.value) {
    // 倒计时
    timer.value = setInterval(() => {
      time.value = time.value - 1

      // 倒计时结束 重置计时器
      if (time.value <= 0) reset()
    }, 1000)
  }
  else {
    // 计时
    timer.value = setInterval(() => {
      time.value = time.value + 1

      // 计时超过36000秒 暂停计时器
      if (time.value > 36000) pause()
    }, 1000)
  }

  inTiming.value = true
}

至于分钟和秒数的计算,都是根据 time 计算出来的。

const time = ref(0)
const minute = computed(() => Math.floor(time.value / 60))
const second = computed(() => time.value % 60)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 16 章——变量、流程控制和游标

一、变量 在MySQL数据库的存储过程和存储函数中&#xff0c;可以使用变量来存储查询或计算的中间结果数据&#xff0c;或者输出最终的结果数据 在MySQL数据库中&#xff0c;变量分为系统变量和用户自定义变量 &#xff08;1&#xff09;系统变量 1.1.1系统变量分类 变量由…

T-SQL编程

目录 1、T-SQL的元素 1.1 标识符 1. 常规标识符 2. 分隔标识符 1.2 变量 1. 全局变量 2. 局部变量 1.3 运算符 1. 算数运算符 2. 赋值运算符 3. 位运算符 4. 比较运算符 5. 逻辑运算符 6. 字符串连接运算符 7. 一元运算符 8. 运算符的优先级和结合性 1.4 批处…

2024 China Collegiate Programming Contest (CCPC) Zhengzhou Onsite 基础题题解

L. Z-order Curve 思路&#xff1a;这题目说了&#xff0c;上面那一行&#xff0c;只有在偶数位才有可能存在1&#xff0c;那么一定存在这样的数&#xff0c;0 ,1,100, 10000,那么反之&#xff0c;我们的数列是行的二倍&#xff0c;因此会出现10,1000,100000这样的数&#xff0…

Unity2D初级背包设计后篇 拓展举例与不足分析

Unity2D初级背包设计中篇 MVC分层撰写(万字详解)-CSDN博客、 如果你已经搞懂了中篇&#xff0c;那么对这个背包的拓展将极为简单&#xff0c;我就在这里举个例子吧 目录 1.添加物品描述信息 2.拓展思路与不足分析 1.没有删除只有丢弃功能&#xff0c;所以可以添加垃圾桶 2.格…

vue(七) vue进阶

目录 第一课&#xff1a;Vue方法、计算机属性及侦听器 一、数组变化侦测 方法1&#xff1a;变更方法 方法2&#xff1a;替换一个数组 例子&#xff1a;小Demo:合并两个数组 二、计算属性 1.基础&#xff08;不推荐&#xff09; 2.使用计算属性来完成案例 3.使用函数的方…

Spring Boot 2 学习指南与资料分享

Spring Boot 2 学习资料 Spring Boot 2 学习资料 Spring Boot 2 学习资料 在当今竞争激烈的 Java 后端开发领域&#xff0c;Spring Boot 2 凭借其卓越的特性&#xff0c;为开发者们开辟了一条高效、便捷的开发之路。如果你渴望深入学习 Spring Boot 2&#xff0c;以下这份精心…

YangQG 面试题汇总

一、交叉链表 问题&#xff1a; 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 解题思想&#xff1a; 双指针 备注&#xff1a;不是快慢指针&#xff0c;如果两个长度相…

fastapi 使用

参考&#xff1a; https://fastapi.tiangolo.com/zh/tutorial/first-steps/https://fastapi.tiangolo.com/zh/tutorial/first-steps/ FastAPI 用于基于标准 Python 类型提示使用 Python 构建 API&#xff0c;使用 ASGI 的标准来构建 Python Web 框架和服务器。所有简单理解&a…

2024年度漏洞态势分析报告,需要访问自取即可!(PDF版本)

2024年度漏洞态势分析报告&#xff0c;需要访问自取即可!(PDF版本),大家有什么好的也可以发一下看看

泛目录和泛站有什么差别

啥是 SEO 泛目录&#xff1f; 咱先来说说 SEO 泛目录是啥。想象一下&#xff0c;你有一个巨大的图书馆&#xff0c;里面的书架上摆满了各种各样的书&#xff0c;每一本书都代表着一个网页。而 SEO 泛目录呢&#xff0c;就像是一个超级图书管理员&#xff0c;它的任务就是把这些…

k8s基础(6)—Kubernetes-存储

Kubernetes-存储概述 k8s的持久券简介 Kubernetes的持久卷&#xff08;PersistentVolume, PV&#xff09;和持久卷声明&#xff08;PersistentVolumeClaim, PVC&#xff09;为用户在Kubernetes中使用卷提供了抽象。PV是集群中的一块存储&#xff0c;PVC是对这部分存储的请求。…

深度学习-卷积神经网络反向传播梯度公式推导

这篇文章非常棒&#xff0c;单样本单通道的反向传播梯度公式推导我都理解了。为了防止找不到原网页&#xff0c;所以特复制于此 参考&#xff1a; https://zhuanlan.zhihu.com/p/640697443

论文笔记(四十七)Diffusion policy: Visuomotor policy learning via action diffusion(下)

Diffusion policy: Visuomotor policy learning via action diffusion&#xff08;下&#xff09; 文章概括5. 评估5.1 模拟环境和数据集5.2 评估方法论5.3 关键发现5.4 消融研究 6 真实世界评估6.1 真实世界Push-T任务6.2 杯子翻转任务6.3 酱汁倒入和涂抹任务 7. 实际双臂任务…

C#学习笔记 --- 简单应用

1.operator 运算符重载&#xff1a;使自定义类可以当做操作数一样进行使用。规则自己定。 2.partial 分部类&#xff1a; 同名方法写在不同位置&#xff0c;可以当成一个类使用。 3.索引器&#xff1a;使自定义类可以像数组一样通过索引值 访问到对应的数据。 4.params 数…

汽车基础软件AutoSAR自学攻略(四)-AutoSAR CP分层架构(3) (万字长文-配21张彩图)

汽车基础软件AutoSAR自学攻略(四)-AutoSAR CP分层架构(3) (万字长文-配21张彩图) 前面的两篇博文简述了AutoSAR CP分层架构的概念&#xff0c;下面我们来具体到每一层的具体内容进行讲解&#xff0c;每一层的每一个功能块力求用一个总览图&#xff0c;外加一个例子的图给大家进…

【2024年华为OD机试】 (CD卷,100分)- 最大N个数与最小N个数的和(Java JS PythonC/C++)

一、问题描述 题目描述 给定一个数组&#xff0c;编写一个函数来计算它的最大N个数与最小N个数的和。你需要对数组进行去重。 说明&#xff1a; 数组中数字范围 [0, 1000]最大N个数与最小N个数不能有重叠&#xff0c;如有重叠&#xff0c;输入非法返回 -1输入非法返回 -1 …

WINFORM - DevExpress -> DevExpress总结[安装、案例]

安装devexpress软件 路径尽量不换&#xff0c;后面破解不容易出问题 vs工具箱添加控件例如: ①使用控制台进入DevExpress安装目录: cd C:\Program Files (x86)\DevExpress 20.1\Components\Tools ②添加DevExpress控件&#xff1a; ToolboxCreator.exe/ini:toolboxcreator…

cursor+deepseek构建自己的AI编程助手

文章目录 准备工作在Cursor中添加deepseek 准备工作 下载安装Cursor &#xff08;默认安装在C盘&#xff09; 注册deepseek获取API key 在Cursor中添加deepseek 1、打开cursor&#xff0c;选择设置 选择Model&#xff0c;添加deepseek-chat 注意这里去掉其他的勾选项&…

《零基础Go语言算法实战》【题目 2-7】defer 关键字特性

《零基础Go语言算法实战》 【题目 2-7】defer 关键字特性 下面代码的输出是什么&#xff1f;请说明原因。 package main import ( "fmt" ) func main() { deferFunc() func deferFunc() { defer func() { fmt.Println("value1") }() defer func() {…

如何规模化实现完全自动驾驶?Mobileye提出解题“新”思路

在CES 2025上&#xff0c;Mobileye展示了端到端自动驾驶系统Mobileye Drive™&#xff0c;通过高度集成的传感器、算法和计算平台&#xff0c;可以实现自动驾驶功能的全覆盖。 Mobileye创始人兼首席执行官Amnon Shashua教授 期间&#xff0c;Mobileye创始人兼首席执行官Amnon …