index.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. <template>
  2. <div class="box">
  3. <div id="menu">
  4. <span class="submenu">
  5. <span>{{ status }}</span>&nbsp; SOCKET
  6. </span>
  7. <span class="submenu">
  8. <span>
  9. <span
  10. v-for="(item, index) in colors"
  11. :key="index"
  12. @click="restColor(index)"
  13. >{{ item }}</span>
  14. </span>
  15. <span>
  16. <a
  17. href="https://beian.miit.gov.cn/"
  18. target="_blank"
  19. rel="noopener noreferrer"
  20. >蜀ICP备2021004869号-1</a>
  21. </span>
  22. </span>
  23. </div>
  24. <div id="cursors" />
  25. <canvas
  26. id="canvas"
  27. :style="resetStyle"
  28. />
  29. </div>
  30. </template>
  31. <script lang="ts" setup>
  32. import { Painter } from './js/Painter.js'
  33. import { Recorder } from './js/Recorder.js'
  34. import {
  35. onMounted, ref, nextTick, onUnmounted
  36. } from 'vue'
  37. import { io } from 'socket.io-client'
  38. const painters: any = {}
  39. let recorder: any = null
  40. let ptime = 0
  41. const status = ref('🟡')
  42. const colors = [ '⚫️', '🔵', '🟢', '🟣', '🔴', '🟤', '🟠', '🟡', '⚪️' ]
  43. let canvas: HTMLCanvasElement | null = null
  44. let context: CanvasRenderingContext2D | null = null
  45. const resetStyle = ref('')
  46. // canvas fn
  47. const onPointerMove = (event: PointerEvent) => {
  48. if (event.isPrimary) {
  49. // Throttle to 20Hz
  50. const throttle = 1000 / 20
  51. const time = event.timeStamp
  52. if (time < (ptime + throttle)) return
  53. ptime = time
  54. //
  55. const x = Math.floor(event.pageX * window.devicePixelRatio)
  56. const y = Math.floor(event.pageY * window.devicePixelRatio)
  57. recorder.move(x, y)
  58. }
  59. }
  60. const onPointerDown = (e: PointerEvent) => {
  61. if (e.isPrimary && e.button === 0 && canvas) {
  62. const x = Math.floor(e.pageX * window.devicePixelRatio)
  63. const y = Math.floor(e.pageY * window.devicePixelRatio)
  64. recorder.down(x, y)
  65. canvas.addEventListener('pointermove', onPointerMove)
  66. }
  67. }
  68. const onPointerUp = (event: PointerEvent) => {
  69. if (event.isPrimary && event.button === 0) {
  70. recorder.up()
  71. canvas?.removeEventListener('pointermove', onPointerMove)
  72. }
  73. }
  74. const restColor = (i: number) => {
  75. recorder.color(i)
  76. const w = canvas?.width || 0
  77. const h = canvas?.height || 0
  78. const r = window.devicePixelRatio
  79. resetStyle.value = `width:${w / r}px;height:${h / r}px; cursor: url(/pen/${i}.cur) 0 17, crosshair;`
  80. }
  81. // socket init
  82. const socket = io(import.meta.env.VITE_SOCKET_URL, { autoConnect: false, auth: { roomID: 'drawCanvas', name: 'test' }, transports: [ 'websocket' ] })
  83. socket.on('connect', () => {
  84. recorder = new Recorder(context, socket)
  85. canvas?.addEventListener('pointerdown', onPointerDown)
  86. canvas?.addEventListener('pointerup', onPointerUp)
  87. status.value = colors[2]
  88. })
  89. socket.on('msg', (data: any) => {
  90. const dataview = new DataView(data)
  91. const id = dataview.getUint8(0)
  92. if (painters[id] === undefined) {
  93. const container = document.getElementById('cursors')
  94. painters[id] = new Painter(context, container)
  95. }
  96. painters[id].execute(dataview)
  97. })
  98. socket.on('connect_error', () => {
  99. status.value = colors[4]
  100. })
  101. // init canvas
  102. const intCanvas = () => {
  103. canvas = document.getElementById('canvas') as HTMLCanvasElement
  104. canvas.width = window.innerWidth * window.devicePixelRatio
  105. canvas.height = window.innerHeight * window.devicePixelRatio
  106. canvas.style.width = `${window.innerWidth}px`
  107. canvas.style.height = `${window.innerHeight}px`
  108. context = canvas.getContext('2d') as CanvasRenderingContext2D
  109. context.lineWidth = 0.7 * window.devicePixelRatio
  110. context.fillStyle = 'rgb(238, 238, 238)'
  111. context.fillRect(0, 0, canvas.width, canvas.height)
  112. }
  113. onMounted(() => {
  114. document.title = '多人在线画板'
  115. intCanvas()
  116. nextTick(() => {
  117. socket.connect()
  118. })
  119. window.addEventListener('resize', intCanvas)
  120. })
  121. onUnmounted(() => {
  122. if (!socket) return
  123. socket.disconnect()
  124. window.removeEventListener('resize', intCanvas)
  125. context = null
  126. canvas = null
  127. recorder = null
  128. })
  129. </script>
  130. <style lang="scss" scoped>
  131. .box {
  132. background: #eeeeee;
  133. font-size: 12px;
  134. font-family: sans-serif;
  135. margin: 0;
  136. overflow: hidden;
  137. #canvas {
  138. display: block;
  139. cursor: url('/pen/0.cur') 0 17, crosshair;
  140. touch-action: none;
  141. }
  142. #cursors {
  143. position: absolute;
  144. width: 100%;
  145. height: 100%;
  146. overflow: hidden;
  147. pointer-events: none;
  148. }
  149. #menu {
  150. position: absolute;
  151. bottom: 10px;
  152. width: 100%;
  153. text-align: center;
  154. user-select: none;
  155. pointer-events: none;
  156. }
  157. span.submenu {
  158. display: inline-block;
  159. background-color: #ffffff;
  160. color: #999999;
  161. padding: 6px 12px;
  162. border-radius: 25px;
  163. user-select: none;
  164. pointer-events: auto;
  165. &:last-child {
  166. margin-left: 5px;
  167. }
  168. &>span {
  169. span {
  170. margin: 0 3px;
  171. cursor: grabbing;
  172. }
  173. }
  174. }
  175. }
  176. </style>