iT邦幫忙

0

JS卡片過渡3D球體轉場時卡片上的文字會消失疑問??

js
  • 分享至 

  • xImage
<!DOCTYPE html>
<body>
   <div id="container"></div>

   <!-- "開始" 按鈕 -->
   <button id="startButton">開始</button>

   <script src="js/bootstrap.bundle.min.js"></script>
   <script src="js/star.js"></script>
   <script src="js/three.min.js"></script>
   <script src="js/gsap.min.js"></script>

   <script>
      // 初始化場景、相機和渲染器
      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);
      const renderer = new THREE.WebGLRenderer({
         antialias: true,
         alpha: true
      });
      renderer.setSize(window.innerWidth, window.innerHeight);
      renderer.setClearColor(0x000000, 0); // 設置背景為透明
      document.getElementById('container').appendChild(renderer.domElement);

      // 創建圓角矩形形狀的函數
      function createRoundedRectShape(width, height, radius) {
         const shape = new THREE.Shape();
         shape.moveTo(-width / 2 + radius, -height / 2);
         shape.lineTo(width / 2 - radius, -height / 2);
         shape.quadraticCurveTo(width / 2, -height / 2, width / 2, -height / 2 + radius);
         shape.lineTo(width / 2, height / 2 - radius);
         shape.quadraticCurveTo(width / 2, height / 2, width / 2 - radius, height / 2);
         shape.lineTo(-width / 2 + radius, height / 2);
         shape.quadraticCurveTo(-width / 2, height / 2, -width / 2, height / 2 - radius);
         shape.lineTo(-width / 2, -height / 2 + radius);
         shape.quadraticCurveTo(-width / 2, -height / 2, -width / 2 + radius, -height / 2);
         return shape;
      }

      // 設置矩形區塊
      const group = new THREE.Group();
      scene.add(group);

      // 動態計算卡片大小和間距
      function calculateCardProperties() {
         const windowWidth = window.innerWidth;
         const windowHeight = window.innerHeight;

         const rectWidth = windowWidth / 25;
         const rectHeight = windowHeight / 12;
         const spacingX = rectWidth * 0.2;
         const spacingY = rectHeight * 0.2;

         return {
            rectWidth,
            rectHeight,
            spacingX,
            spacingY
         };
      }

      // 創建卡片並按窗口大小自適應
      function createCards() {
         while (group.children.length) {
            group.remove(group.children[0]);
         }

         const {
            rectWidth,
            rectHeight,
            spacingX,
            spacingY
         } = calculateCardProperties();
         const numRects = 119;
         const cols = 17;
         const rows = 7;

         for (let i = 0; i < numRects; i++) {
            const shape = createRoundedRectShape(rectWidth, rectHeight, 2);
            const geometry = new THREE.ShapeGeometry(shape);

            // 材質設置,包括白邊框、透明度和光暈效果
            const material = new THREE.MeshBasicMaterial({
               color: 0x00ffcc,
               transparent: true,
               opacity: 0.3,
               side: THREE.DoubleSide,
               depthTest: false, // 禁用深度測試,防止遮擋
               depthWrite: false, // 禁用深度寫入,讓透明物體不會遮擋
               blending: THREE.AdditiveBlending
            });

            const edgeMaterial = new THREE.LineBasicMaterial({
               color: 0xffffff,
               transparent: true,
               opacity: 0.7,
               depthTest: false, // 同樣禁用深度測試
               depthWrite: false
            });

            const rect = new THREE.Mesh(geometry, material);

            // 增加邊框
            const edges = new THREE.EdgesGeometry(geometry);
            const edgeMesh = new THREE.LineSegments(edges, edgeMaterial);
            rect.add(edgeMesh);

            const x = (i % cols) * (rectWidth + spacingX) - (cols * (rectWidth + spacingX) / 2);
            const y = Math.floor(i / cols) * (rectHeight + spacingY) - (rows * (rectHeight + spacingY) / 2);

            // 初始隨機位置設置
            rect.position.set(
               (Math.random() - 0.5) * window.innerWidth * 2,
               (Math.random() - 0.5) * window.innerHeight * 2,
               -1000
            );
            group.add(rect);

            // 使用 GSAP 讓卡片飛入最終位置
            gsap.to(rect.position, {
               x: x,
               y: y,
               z: 0,
               duration: 1.5 + Math.random(),
               ease: "power2.out",
               delay: Math.random() * 1
            });
         }

         camera.position.z = Math.max(window.innerWidth, window.innerHeight) / 2;
      }

      // 使用 Fibonacci Sphere 演算法來均勻分佈卡片
      function fibonacciSphere(samples, radius) {
         const points = [];
         const offset = 2 / samples;
         const increment = Math.PI * (3 - Math.sqrt(5)); // φ = Golden angle

         for (let i = 0; i < samples; i++) {
            const y = ((i * offset) - 1) + (offset / 2);
            const r = Math.sqrt(1 - Math.pow(y, 2));
            const phi = i * increment;

            const x = Math.cos(phi) * r;
            const z = Math.sin(phi) * r;

            points.push({
               x: x * radius,
               y: y * radius,
               z: z * radius
            });
         }

         return points;
      }

      // 球體半徑設定
      const sphereRadius = 350;
      const targetPositions = fibonacciSphere(119, sphereRadius);

      // 過渡動畫,並且讓卡片的朝向改變
      function transformToSphere() {
         group.children.forEach((rect, index) => {
            const {
               x,
               y,
               z
            } = targetPositions[index];

            // 設置渲染順序,確保卡片按順序渲染,避免遮擋
            rect.renderOrder = index;

            // 為每個卡片在過渡過程中引入一些隨機偏移
            const randomOffsetX = (Math.random() - 0.5) * 300;
            const randomOffsetY = (Math.random() - 0.5) * 300;
            const randomOffsetZ = (Math.random() - 0.5) * 300;

            gsap.to(rect.position, {
               x: x + randomOffsetX,
               y: y + randomOffsetY,
               z: z + randomOffsetZ,
               duration: 1.5,
               delay: Math.random() * 1,
               ease: "power2.out",
               onUpdate: () => {
                  rect.lookAt(new THREE.Vector3(0, 0, 0));

                  // 強制更新文字標籤的朝向和材質
                  rect.children.forEach(child => {
                     if (child instanceof THREE.Mesh) {
                        child.lookAt(new THREE.Vector3(0, 0, 0)); // 讓文字平面朝向球心
                        child.material.needsUpdate = true; // 強制更新材質,避免丟失
                     }
                  });
               },
               onComplete: () => {
                  gsap.to(rect.position, {
                     x: x,
                     y: y,
                     z: z,
                     duration: 1.5,
                     ease: "power2.inOut",
                     onUpdate: () => {
                        rect.lookAt(new THREE.Vector3(0, 0, 0));

                        // 持續保持文字平面朝向球心
                        rect.children.forEach(child => {
                           if (child instanceof THREE.Mesh) {
                              child.lookAt(new THREE.Vector3(0, 0, 0));
                              child.material.needsUpdate = true;
                           }
                        });
                     }
                  });
               }
            });
         });
      }

      // 透過 Ajax 獲取參與者資料
      function fetchParticipantData() {
         return $.ajax({
            url: 'includes/raffle_management.php',
            method: 'GET',
            data: {
               action: 'get_participant_data'
            },
            dataType: 'json'
         });
      }

      // 將參與者隨機分配到卡片上,工號與名字分行顯示
      function assignParticipantsToCards(participantData) {
         const shuffledData = participantData.sort(() => 0.5 - Math.random());
         group.children.forEach((rect, index) => {
            if (index < shuffledData.length) {
               const participant = shuffledData[index];
               const staffId = participant.user_staff;
               const userName = participant.user_name;

               // 確認卡片是否已有文字標籤,避免重複添加
               const existingTextPlane = rect.children.find(child => child instanceof THREE.Mesh);
               if (!existingTextPlane) {
                  const canvas = document.createElement('canvas');
                  canvas.width = 256;
                  canvas.height = 128;
                  const context = canvas.getContext('2d');
                  context.font = '20px Arial';
                  context.fillStyle = 'white';
                  context.textAlign = 'center';

                  // 繪製工號在上方
                  context.fillText(staffId, canvas.width / 2, canvas.height / 3);

                  // 繪製姓名在下方
                  context.fillText(userName, canvas.width / 2, (canvas.height / 3) * 2);

                  // 使用 canvas 創建紋理
                  const texture = new THREE.CanvasTexture(canvas);
                  const textMaterial = new THREE.MeshBasicMaterial({
                     map: texture,
                     transparent: true,
                     opacity: 1, // 設置為完全可見
                     depthTest: false, // 禁用深度測試,避免遮擋
                     depthWrite: false // 禁用深度寫入
                  });

                  // 創建平面顯示文字
                  const planeGeometry = new THREE.PlaneGeometry(150, 75);
                  const textMesh = new THREE.Mesh(planeGeometry, textMaterial);
                  textMesh.position.set(0, 0, 0.1); // 確保文字凸出顯示

                  textMesh.renderOrder = rect.renderOrder + 1; // 確保文字比卡片稍後渲染
                  rect.add(textMesh);
               }
            }
         });
      }

      // 點擊按鈕後觸發過渡動畫
      document.getElementById('startButton').addEventListener('click', function() {
         fetchParticipantData().then(participantData => {
            assignParticipantsToCards(participantData);
            transformToSphere(); // 啟動動畫
            this.style.display = 'none'; // 隱藏按鈕
         });
      });

      // 渲染循環
      function animate() {
         requestAnimationFrame(animate);
         renderer.render(scene, camera);
      }
      animate();

      // 自適應窗口大小變化
      window.addEventListener('resize', () => {
         renderer.setSize(window.innerWidth, window.innerHeight);
         camera.aspect = window.innerWidth / window.innerHeight;
         camera.updateProjectionMatrix();

         createCards();
      });

      // 初始化卡片並分配名單
      fetchParticipantData().then(participantData => {
         createCards();
         assignParticipantsToCards(participantData);
      });
   </script>
</body>

這個是一個宇宙星空穿梭的背景

有看到一個抽獎的畫面蠻不錯的,想自己來練習練習
這兩天在要做個卡片轉場3D球體
點擊按鈕執行過渡時某些卡片的文字會不見
拚成球體後,都是正面的卡片文字消失
如圖

https://ithelp.ithome.com.tw/upload/images/20240906/20124026VEOMBhG1qi.jpg

想請教各位前輩老師們
我是否遺漏了什麼環節??

圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友回答

立即登入回答