screen.page.vue 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <script setup lang="ts">
  2. import { getApplicationMethod } from '@/request/api';
  3. import getBubbles from '@/tools/bubble';
  4. import { useElementSize } from '@vueuse/core';
  5. import { useRequest } from 'alova/client';
  6. import p5 from 'p5';
  7. import { Start } from '@/composables/start';
  8. const { data } = useRequest(getApplicationMethod, { initialData: { image: { el: '', copyright: '' } } });
  9. const title = computed(() => data.value.image.title || import.meta.env.SIX_APP_TITLE);
  10. const copyright = computed(() => data.value.copyright);
  11. const button = computed(() => data.value.image.el?.includes('btn'));
  12. const container = useTemplateRef<HTMLDivElement>('container');
  13. const { width, height } = useElementSize(container);
  14. interface Bubble {
  15. text: string;
  16. color: string;
  17. x?: number;
  18. y?: number;
  19. dx?: number;
  20. dy?: number;
  21. diameter?: number;
  22. size?: number;
  23. }
  24. watchEffect(() => {
  25. if ( width.value && height.value ) init({ width: width.value, height: height.value * 0.90, container: container.value! });
  26. });
  27. function init({ width, height, container }: { width: number; height: number; container: HTMLElement }) {
  28. const bubbles = [];
  29. new p5((sketch) => {
  30. let scanLineOffset;
  31. let scanLineOffStep = 5;
  32. const drawScan = (w = 24, x = 40, h = w, y = x, { color = '#34a76b', size = 5 } = {}) => {
  33. sketch.push();
  34. sketch.stroke(color);
  35. sketch.strokeWeight(size);
  36. // 左上角
  37. sketch.line(x, y, x + w, y);
  38. sketch.line(x, y, x, y + h);
  39. // 左下角
  40. sketch.line(x, height - y, x + w, height - y);
  41. sketch.line(x, height - y, x, height - y - h);
  42. // 右上角
  43. sketch.line(width - x, y, width - x - w, y);
  44. sketch.line(width - x, y, width - x, y + h);
  45. // 右下角
  46. sketch.line(width - x, height - y, width - x - w, height - y);
  47. sketch.line(width - x, height - y, width - x, height - y - h);
  48. // 线
  49. const yT = y * 2;
  50. const yB = height - yT;
  51. scanLineOffset ??= yT;
  52. if ( scanLineOffset < yT || scanLineOffset > yB ) scanLineOffStep = -scanLineOffStep;
  53. scanLineOffset += scanLineOffStep;
  54. sketch.line(x, scanLineOffset, width - x, scanLineOffset);
  55. sketch.pop();
  56. };
  57. const bubbles: Bubble[] = getBubbles();
  58. const drawBubble = (x = 40, y = x, diameter = 90) => {
  59. for ( const bubble of <Required<Bubble>[]> bubbles ) {
  60. bubble.diameter ??= diameter;
  61. const radius = Math.floor(bubble.diameter / 2);
  62. bubble.x ??= sketch.random(x + radius, width - x - radius);
  63. bubble.y ??= sketch.random(y + radius, height - y - radius);
  64. bubble.dx ??= sketch.random(-2, 2);
  65. bubble.dy ??= sketch.random(-2, 2);
  66. // 移动
  67. bubble.x += bubble.dx;
  68. bubble.y += bubble.dy;
  69. if ( bubble.x + radius >= width - y ) bubble.x = width - y;
  70. if ( bubble.y + radius >= height - y ) bubble.y = height - y;
  71. // 绘制
  72. const size = bubble.size;
  73. const color = sketch.color(bubble.color);
  74. sketch.push();
  75. sketch.fill('#fff');
  76. sketch.textSize(size);
  77. sketch.textAlign(sketch.CENTER);
  78. sketch.text(bubble.text, bubble.x, bubble.y - size / 4);
  79. sketch.fill(color);
  80. sketch.circle(bubble.x, bubble.y, bubble.diameter);
  81. sketch.pop();
  82. // 检测边界
  83. if ( bubble.x - radius <= x || bubble.x + radius >= width - x ) {
  84. bubble.dx *= -1;
  85. if ( bubble.x - radius <= x ) { bubble.x = x + radius; } else { bubble.x = width - x - radius; }
  86. }
  87. if ( bubble.y - radius <= y || bubble.y + radius >= height - y ) {
  88. bubble.dy *= -1;
  89. if ( bubble.y - radius <= y ) { bubble.y = y + radius; } else { bubble.y = height - y - radius; }
  90. }
  91. }
  92. const collide = (bubble: Required<Bubble>, other: Required<Bubble>) => {
  93. const ra = Math.floor(bubble.diameter / 2);
  94. const rb = Math.floor(other.diameter / 2);
  95. const radius = Math.max(ra, rb);
  96. let angle = sketch.atan2(other.y - bubble.y, other.x - bubble.x);
  97. let target = sketch.createVector(bubble.x, bubble.y);
  98. let a = p5.Vector.fromAngle(angle + sketch.PI, radius);
  99. let b = p5.Vector.fromAngle(angle, radius);
  100. bubble.x = target.x + a.x;
  101. bubble.y = target.y + a.y;
  102. other.x = target.x + b.x;
  103. other.y = target.y + b.y;
  104. bubble.dx *= -1;
  105. other.dx *= -1;
  106. };
  107. for ( let i = 0; i < bubbles.length; i++ ) {
  108. for ( let j = i + 1; j < bubbles.length; j++ ) {
  109. const bubble = bubbles[ i ] as Required<Bubble>;
  110. const other = bubbles[ j ] as Required<Bubble>;
  111. const d = sketch.dist(bubble.x, bubble.y, other.x, other.y);
  112. if ( d < bubble.diameter ) {
  113. collide(bubble, other);
  114. collide(other, bubble);
  115. }
  116. }
  117. }
  118. };
  119. sketch.setup = () => {
  120. sketch.createCanvas(width, height);
  121. sketch.noStroke();
  122. };
  123. sketch.draw = () => {
  124. sketch.clear();
  125. drawScan();
  126. drawBubble();
  127. };
  128. }, container);
  129. }
  130. </script>
  131. <template>
  132. <div class="wrapper">
  133. <div class="fixed size-full" ref="container"></div>
  134. <div class="fixed flex flex-col size-full">
  135. <div class="flex-none" style="height: 85vh;">
  136. <img class="mx-auto h-full object-scale-down" style="width: 32vw;" src="@/assets/images/title.png" :alt="title">
  137. </div>
  138. <div class="flex-auto flex flex-col">
  139. <div class="flex-auto flex justify-center items-center">
  140. <Start v-if="button" />
  141. </div>
  142. <div class="flex-none text-xl p-8 text-center" v-html="copyright"></div>
  143. </div>
  144. </div>
  145. </div>
  146. </template>
  147. <style scoped lang="scss">
  148. .wrapper {
  149. background: url("@/assets/images/screen.png") no-repeat center / 100%;
  150. }
  151. </style>