【PPTist】幻灯片放映

放映功能的代码都在 src/hooks/useScreening.ts ,我们看一下 从当前页开始 放映的功能。

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

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

typescript 复制代码
// 进入全屏
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

typescript 复制代码
setScreening(screening: boolean) {
  this.screening = screening
},

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

typescript 复制代码
<template>
  <Screen v-if="screening" />
  <Editor v-else-if="_isPC" />
  <Mobile v-else />
</template>

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

typescript 复制代码
<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
typescript 复制代码
// 处理鼠标(触摸)事件
// 准备开始绘制/擦除墨迹(落笔)
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
typescript 复制代码
// 开始绘制/擦除墨迹(移动)
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)
}
typescript 复制代码
// 路径操作
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 实现的

typescript 复制代码
// 绘制画笔墨迹方法
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
typescript 复制代码
// 结束绘制/擦除墨迹(停笔)
const handleMouseup = () => {
  if (!isMouseDown) return
  isMouseDown = false
  emit('end')
}

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

typescript 复制代码
// 每次绘制完成后将绘制完的图片更新到数据库
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 })
  })
}

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

typescript 复制代码
// 打开画笔工具或切换页面时,将数据库中存储的墨迹绘制到画布上
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

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

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

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

循环播放通过 loopPlay 控制。

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

typescript 复制代码
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 组件,页面就变成这样了

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

typescript 复制代码
const turnSlide = (index: number) => {
  props.turnSlideToIndex(index)
  emit('close')
}

src/views/Screen/hooks/useExecPlay.ts

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

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

typescript 复制代码
@close="slideThumbnailModelVisible = false"
2、演讲者视图
① 画笔

画笔跟上面的画笔工具是一个组件,由此可知组件化的重要性,功能复用多么方便!

② 激光笔

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

typescript 复制代码
<div 
  class="slide-list-wrap" 
  :class="{ 'laser-pen': laserPen }" 
  ref="slideListWrapRef"
>

是用来设置 cursor 属性的,显示成一个小圆点,是通过base64设置的,代码太长了我就不粘贴了

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

③ 计时器

计时器会显示这么一个小组件

src/views/Screen/CountdownTimer.vue

表示分和秒的两个小框框是由不可编辑的 input 框组成

typescript 复制代码
<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')"
>

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

typescript 复制代码
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 计算出来的。

typescript 复制代码
const time = ref(0)
const minute = computed(() => Math.floor(time.value / 60))
const second = computed(() => time.value % 60)
相关推荐
成长中的向日葵12 分钟前
element-ui dialog弹窗 设置点击空白处不关闭
前端·vue.js
ihengshuai1 小时前
Gitlab流水线配置
前端·docker·gitlab·devops
前端太佬1 小时前
笔记-Big.js的使用
前端·javascript
Blushyes1 小时前
【 如快 Tauri 2 实践 】结合 pinia 实现本地文件存储响应式
前端·javascript·typescript
梦想CAD控件1 小时前
(WEB CAD SDK)网页CAD绘制条形码、二维码的教程
前端·javascript·vue.js
纪伊路上盛名在1 小时前
在vscode中使用R-1
开发语言·ide·vscode·python·jupyter·r语言·编辑器
Aloha_Jimmy1 小时前
以 iOS 开发者的视角去看鸿蒙的struct
前端
秃头小宝贝(柚子君)1 小时前
对象数组按照指定rule对数据进行切割分层形成树形结构并支持搜索功能
开发语言·前端·javascript
xingqing87y2 小时前
滚动字幕视频怎么制作
前端
你当像火烈鸟2 小时前
支付系统中如何实现幂等
前端·vue.js