Sfoglia il codice sorgente

添加放映幻灯片的画笔工具

pipipi-pikachu 5 anni fa
parent
commit
1b440ea27b

+ 1 - 1
src/components/ColorPicker/Alpha.vue

@@ -85,7 +85,7 @@ export default defineComponent({
 })
 </script>
 
-<style>
+<style lang="scss" scoped>
 .alpha {
   position: absolute;
   top: 0;

+ 1 - 1
src/components/ColorPicker/Checkboard.vue

@@ -60,7 +60,7 @@ export default defineComponent({
 })
 </script>
 
-<style>
+<style lang="scss" scoped>
 .checkerboard {
   position: absolute;
   top: 0;

+ 1 - 1
src/components/ColorPicker/EditableInput.vue

@@ -41,7 +41,7 @@ export default defineComponent({
 })
 </script>
 
-<style>
+<style lang="scss" scoped>
 .editable-input {
   width: 100%;
   position: relative;

+ 1 - 1
src/components/ColorPicker/Hue.vue

@@ -101,7 +101,7 @@ export default defineComponent({
 })
 </script>
 
-<style>
+<style lang="scss" scoped>
 .hue {
   position: absolute;
   top: 0;

+ 2 - 2
src/components/ColorPicker/Saturation.vue

@@ -97,16 +97,16 @@ export default defineComponent({
 })
 </script>
 
-<style>
+<style lang="scss" scoped>
 .saturation,
 .saturation-white,
 .saturation-black {
-  cursor: pointer;
   position: absolute;
   top: 0;
   left: 0;
   right: 0;
   bottom: 0;
+  cursor: pointer;
 }
 .saturation-white {
   background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));

+ 198 - 0
src/components/WritingBoard.vue

@@ -0,0 +1,198 @@
+<template>
+  <div class="writing-board" ref="writingBoardRef">
+    <canvas class="canvas" ref="canvasRef"
+      @mousedown="$event => handleMousedown($event)"
+      @mousemove="$event => handleMousemove($event)"
+      @mouseup="handleMouseup"
+      @mouseout="handleMouseup"
+    ></canvas>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, onMounted, PropType, ref } from 'vue'
+
+const penSize = 6
+const rubberSize = 80
+
+export default defineComponent({
+  name: 'writing-board',
+  props: {
+    color: {
+      type: String,
+      default: '#ffcc00',
+    },
+    model: {
+      type: String as PropType<'pen' | 'eraser'>,
+      default: 'pen',
+    },
+  },
+  setup(props) {
+    let ctx: CanvasRenderingContext2D | null = null
+    const writingBoardRef = ref<HTMLElement | null>(null)
+    const canvasRef = ref<HTMLCanvasElement | null>(null)
+    let lastPos = {
+      x: 0,
+      y: 0,
+    }
+    let isMouseDown = false
+    let lastTime = 0
+    let lastLineWidth = -1
+
+    const initCanvas = () => {
+      if(!canvasRef.value || !writingBoardRef.value) return
+
+      ctx = canvasRef.value.getContext('2d')
+      if(!ctx) return
+
+      canvasRef.value.width = writingBoardRef.value.clientWidth
+      canvasRef.value.height = writingBoardRef.value.clientHeight
+
+      canvasRef.value.style.width = writingBoardRef.value.clientWidth + 'px'
+      canvasRef.value.style.height = writingBoardRef.value.clientHeight + 'px'
+
+      ctx.lineCap = 'round'
+      ctx.lineJoin = 'round'
+    }
+
+    const getDistance = (posX: number, posY: number) => {
+      const lastPosX = lastPos.x
+      const lastPosY = lastPos.y
+      return Math.sqrt((posX - lastPosX) * (posX - lastPosX) + (posY - lastPosY) * (posY - lastPosY))
+    }
+
+    const getLineWidth = (s: number, t: number) => {
+      const maxV = 10
+      const minV = 0.1
+      const maxWidth = penSize
+      const minWidth = 3
+      const v = s / t
+      let lineWidth
+
+      if(v <= minV) lineWidth = maxWidth
+      else if(v >= maxV) lineWidth = minWidth
+      else lineWidth = maxWidth - v / maxV * maxWidth
+
+      if(lastLineWidth === -1) return lineWidth
+      return lineWidth * 1 / 3 + lastLineWidth * 2 / 3
+    }
+
+    // 画笔绘制方法
+    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()
+    }
+
+    // 橡皮擦除方法
+    const erase = (posX: number, posY: number) => {
+      if(!ctx || !canvasRef.value) return
+      const lastPosX = lastPos.x
+      const lastPosY = lastPos.y
+
+      const radius = rubberSize / 2
+
+      const sinRadius = radius * Math.sin(Math.atan((posY - lastPosY) / (posX - lastPosX)))
+      const cosRadius = radius * Math.cos(Math.atan((posY - lastPosY) / (posX - lastPosX)))
+      const rectPoint1: [number, number] = [lastPosX + sinRadius, lastPosY - cosRadius]
+      const rectPoint2: [number, number] = [lastPosX - sinRadius, lastPosY + cosRadius]
+      const rectPoint3: [number, number] = [posX + sinRadius, posY - cosRadius]
+      const rectPoint4: [number, number] = [posX - sinRadius, posY + cosRadius]
+
+      ctx.save()
+      ctx.beginPath()
+      ctx.arc(posX, posY, radius, 0, Math.PI * 2)
+      ctx.clip()
+      ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
+      ctx.restore()
+
+      ctx.save()
+      ctx.beginPath()
+      ctx.moveTo(...rectPoint1)
+      ctx.lineTo(...rectPoint3)
+      ctx.lineTo(...rectPoint4)
+      ctx.lineTo(...rectPoint2)
+      ctx.closePath()
+      ctx.clip()
+      ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
+      ctx.restore()
+    }
+
+    const startDraw = (posX: number, posY: number) => {
+      lastPos = { x: posX, y: posY }
+      lastTime = new Date().getTime()
+    }
+
+    const startMove = (posX: number, posY: number) => {
+      const time = new Date().getTime()
+
+      // 画笔模式(这里通过绘制速度调节画笔的粗细)
+      if(props.model === 'pen') {
+        const s = getDistance(posX, posY)
+        const t = time - lastTime
+        const lineWidth = getLineWidth(s, t)
+  
+        draw(posX, posY, lineWidth)
+        lastLineWidth = lineWidth
+      }
+      // 橡皮模式
+      else erase(posX, posY)
+
+      lastPos = { x: posX, y: posY }
+      lastTime = new Date().getTime()
+    }
+
+    const handleMousedown = (e: MouseEvent) => {
+      isMouseDown = true
+      startDraw(e.offsetX, e.offsetY)
+    }
+
+    const handleMousemove = (e: MouseEvent) => {
+      if(!isMouseDown) return
+      startMove(e.offsetX, e.offsetY)
+    }
+
+    const handleMouseup = () => {
+      if(!isMouseDown) return
+      isMouseDown = false
+    }
+
+    // 清空画布
+    const clearCanvas = () => {
+      if(!ctx || !canvasRef.value) return
+      ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
+    }
+
+    onMounted(initCanvas)
+
+    return {
+      writingBoardRef,
+      canvasRef,
+      handleMousedown,
+      handleMousemove,
+      handleMouseup,
+      clearCanvas,
+    }
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.writing-board {
+  position: fixed;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 9;
+}
+</style>

+ 8 - 8
src/views/Editor/CanvasTool/index.vue

@@ -18,7 +18,7 @@
           <IconFont type="icon-image" class="handler-item" />
         </Tooltip>
       </FileInput>
-      <Popover trigger="click" v-model:visible="isOpenShapePool">
+      <Popover trigger="click" v-model:visible="shapePoolVisible">
         <template #content>
           <ShapePool @select="shape => drawShape(shape)" />
         </template>
@@ -26,7 +26,7 @@
           <IconFont type="icon-star" class="handler-item" />
         </Tooltip>
       </Popover>
-      <Popover trigger="click" v-model:visible="isOpenLinePool">
+      <Popover trigger="click" v-model:visible="linePoolVisible">
         <template #content>
           <LinePool @select="line => drawLine(line)" />
         </template>
@@ -89,8 +89,8 @@ export default defineComponent({
       getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
     }
 
-    const isOpenShapePool = ref(false)
-    const isOpenLinePool = ref(false)
+    const shapePoolVisible = ref(false)
+    const linePoolVisible = ref(false)
     const drawText = () => {
       store.commit(MutationTypes.SET_CREATING_ELEMENT, {
         type: 'text',
@@ -102,14 +102,14 @@ export default defineComponent({
         type: 'shape',
         data: shape,
       })
-      isOpenShapePool.value = false
+      shapePoolVisible.value = false
     }
     const drawLine = (line: LinePoolItem) => {
       store.commit(MutationTypes.SET_CREATING_ELEMENT, {
         type: 'line',
         data: line,
       })
-      isOpenLinePool.value = false
+      linePoolVisible.value = false
     }
 
     return {
@@ -120,8 +120,8 @@ export default defineComponent({
       redo,
       undo,
       insertImageElement,
-      isOpenShapePool,
-      isOpenLinePool,
+      shapePoolVisible,
+      linePoolVisible,
       drawText,
       drawShape,
       drawLine,

+ 75 - 1
src/views/Screen/index.vue

@@ -54,11 +54,33 @@
       </div>
     </Modal>
 
+    <WritingBoard ref="writingBoardRef" :color="writingBoardColor" :model="writingBoardModel" v-if="writingBoardVisible" />
+
     <div class="tools">
       <IconFont class="tool-btn" type="icon-left-circle" @click="execPrev()" />
       <IconFont class="tool-btn" type="icon-right-circle" @click="execNext()" />
       <IconFont class="tool-btn" type="icon-appstore" @click="slideListModelVisible = true" />
-      <IconFont class="tool-btn" type="icon-edit" />
+      <Popover trigger="click" v-model:visible="writingBoardConfigsVisible">
+        <template #content>
+          <div class="writing-board-configs">
+            <div class="btn" @click="writingBoardModel = 'pen'; writingBoardConfigsVisible = false">画笔</div>
+            <div class="btn" @click="writingBoardModel = 'eraser'; writingBoardConfigsVisible = false">橡皮擦</div>
+            <div class="btn" @click="writingBoardRef.clearCanvas(); writingBoardConfigsVisible = false">擦除所有墨迹</div>
+            <div class="btn" @click="writingBoardVisible = false; writingBoardConfigsVisible = false">关闭画笔</div>
+            <div class="colors">
+              <div 
+                class="color" 
+                :class="{ 'active': color === writingBoardColor }"
+                v-for="color in writingBoardColors" 
+                :key="color"
+                :style="{ backgroundColor: color }"
+                @click="writingBoardColor = color; writingBoardConfigsVisible = false"
+              ></div>
+            </div>
+          </div>
+        </template>
+        <IconFont class="tool-btn" type="icon-edit" @click="writingBoardVisible = true" />
+      </Popover>
     </div>
   </div>
 </template>
@@ -76,12 +98,16 @@ import { ContextmenuItem } from '@/components/Contextmenu/types'
 
 import ScreenSlide from './ScreenSlide.vue'
 import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
+import WritingBoard from '@/components/WritingBoard.vue'
+
+const writingBoardColors = ['#000000', '#ffffff', '#1e497b', '#4e81bb', '#e2534d', '#9aba60', '#8165a0', '#47acc5', '#f9974c']
 
 export default defineComponent({
   name: 'screen',
   components: {
     ScreenSlide,
     ThumbnailSlide,
+    WritingBoard,
   },
   setup() {
     const store = useStore<State>()
@@ -95,6 +121,12 @@ export default defineComponent({
 
     const slideListModelVisible = ref(false)
 
+    const writingBoardRef = ref()
+    const writingBoardVisible = ref(false)
+    const writingBoardConfigsVisible = ref(false)
+    const writingBoardColor = ref('#e2534d')
+    const writingBoardModel = ref('pen')
+
     const setSlideContentSize = () => {
       const winWidth = document.body.clientWidth
       const winHeight = document.body.clientHeight
@@ -229,7 +261,13 @@ export default defineComponent({
       execPrev,
       execNext,
       slideListModelVisible,
+      writingBoardVisible,
+      writingBoardConfigsVisible,
       turnSlideToIndex,
+      writingBoardRef,
+      writingBoardColors,
+      writingBoardColor,
+      writingBoardModel,
     }
   },
 })
@@ -351,4 +389,40 @@ export default defineComponent({
     }
   }
 }
+
+.writing-board-configs {
+  font-size: 12px;
+
+  .btn {
+    padding: 3px 10px;
+    margin: 0 -10px;
+    margin-bottom: 3px;
+    cursor: pointer;
+
+    &:hover {
+      background-color: #ccc;
+    }
+  }
+  .colors {
+    display: flex;
+    margin-top: 8px;
+  }
+  .color {
+    width: 15px;
+    height: 15px;
+    outline: 1px solid #ccc;
+    cursor: pointer;
+
+    &:hover {
+      transform: scale(1.1);
+    }
+    &.active {
+      outline: 2px solid $themeColor;
+    }
+
+    & + .color {
+      margin-left: 5px;
+    }
+  }
+}
 </style>