主頁 > 企業開發 > 記錄--ThreeJs手搓一個羅盤特效

記錄--ThreeJs手搓一個羅盤特效

2023-05-11 08:24:49 企業開發

這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

先上效果

ThreeJs手搓一個羅盤特效

前言

最近在學Three.js.,對著檔案看了一周多,正好趕上碼上掘金的活動,就順便寫了一個小demo,手搓一個羅盤特效,

太極

先來看一下太極的實作方式,這里我們使用CircleGeometry,將其分解開來可以看出是由圓形和半圓形組成 ,

CircleGeometry

CircleGeometry官網案例
radius 半徑
segments 分段(三角面)的數量
thetaStart 第一個分段的起始角度
thetaLength 圓形扇區的中心角

這里不需要用到segments,但是需要顏色,所以定義一個函式傳入半徑、顏色、起始角度、中心角,

const createCircle = (r, color, thetaStart, thetaLength) => {
    const material = new THREE.MeshBasicMaterial({
    color: color,
    side: THREE.DoubleSide
    });
    const geometry = new THREE.CircleGeometry(r, 64, thetaStart, thetaLength);
    const circle = new THREE.Mesh(geometry, material);
    return circle;
  };

我們只需要通過傳參生產不同大小的圓或半圓,再進行位移就可以實作其效果,

參考代碼/73-96行 還有一些需要注意的地方寫在注釋里了,

羅盤

接下來看羅盤的實作,羅盤由一個個圓環組成,一個圓環又由內圈、外圈、分隔線、文字、八卦構成,

 

內外圈

內外圈我們使用兩個RingGeometry

RingGeometry官網案例
innerRadius 內部半徑
outerRadius 外部半徑
thetaSegments 圓環的分段數
phiSegments 圓環的分段數
thetaStart 起始角度
thetaLength 圓心角

通過circle控制內外圓圈的尺寸,circleWidth控制圓圈的線寬

  const circleWidth = [0.1, 0.1]
  const circle = [0, 1];
  circle.forEach((i, j) => {
    const RingGeo = new THREE.RingGeometry(
      innerRing + i,
      innerRing + i + circleWidth[j],
      64,
      1
    );
    const Ring = new THREE.Mesh(RingGeo, material);
    RingGroup.add(Ring);
  });

分隔線

分隔線使用的是PlaneGeometry

PlaneGeometry官網案例
width 寬度
height 高度
widthSegments 寬度分段數
heightSegments 高度分段數

關于分隔線,它的長度就是內外圈的差值,所以這里使用外圈的數值,確定與圓心的距離就要使用內圈的數值加上自身長度除2,除此之外,還需要計算分隔線與圓心的夾角,

  for (let i = 0; i < lineNum; i++) {
    const r = innerRing + circle[1] / 2;
    const rad = ((2 * Math.PI) / lineNum) * i;
    const x = Math.cos(rad) * r;
    const y = Math.sin(rad) * r;
    const planeGeo = new THREE.PlaneGeometry(lineWidth, circle[1]);
    const line = new THREE.Mesh(planeGeo, material);

    line.position.set(x, y, 0);
    line.rotation.set(0, 0, rad + Math.PI / 2);
    RingGroup.add(line);
  }

文字

文字使用的是TextGeometry,定位與分隔線一致,只需要交錯開來,

for (let i = 0; i < lineNum; i++) {
      const r = innerRing + circle[1] / 2;
      const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
      const x = Math.cos(rad) * r;
      const y = Math.sin(rad) * r;
      var txtGeo = new THREE.TextGeometry(text[i % text.length], {
        font: font,
        size: size,
        height: 0.001,
        curveSegments: 12,
      });
      txtGeo.translate(offsetX, offsetY, 0);
      var txt = new THREE.Mesh(txtGeo, material);
      txt.position.set(x, y, 0);
      txt.rotation.set(0, 0, rad + -Math.PI / 2);
      RingGroup.add(txtMesh);

不過TextGeometry的使用有一個得注意得前提,我們需要引入字體檔案,

const fontLoader = new THREE.FontLoader();
const fontUrl =
  "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/fonts.json";
let font;
const loadFont = new Promise((resolve, reject) => {
  fontLoader.load(
    fontUrl,
    function (loadedFont) {
      font = loadedFont;
      resolve();
    },
    undefined,
    function (err) {
      reject(err);
    }
  );
});

八卦

圓環中除了文字之外,還能展示八卦,通過傳遞baguaData給createBagua生成每一個符號,

const baguaData = https://www.cnblogs.com/smileZAZ/archive/2023/05/10/[
      [1, 1, 1],
      [0, 0, 0],
      [0, 0, 1],
      [0, 1, 0],
      [0, 1, 1],
      [1, 0, 0],
      [1, 0, 1],
      [1, 1, 0],
    ];
    for (let i = 0; i < lineNum; i++) {
      const r = innerRing + circle[1] / 2;
      const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
      const x = Math.cos(rad) * r;
      const y = Math.sin(rad) * r;
      RingGroup.add(
        createBagua(baguaData[i % 8], x, y, 0 , rad + Math.PI / 2, text[0]),
      );
    }

createBagua參考代碼/114-146行 ,和分隔線是一樣的,使用了PlaneGeometry只是做了一些位置的設定,

視頻貼圖

在羅盤外,還有一圈視頻,這里是用到了VideoTexture,實作也很簡單,唯一得注意的是視頻的跨域問題,需要配置video.crossOrigin = "anonymous"

  const videoSrc = https://www.cnblogs.com/smileZAZ/archive/2023/05/10/["https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/yAC65vN6.mp4",
    "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/6Z5VZdZM.mp4",
  ];
  video.src = https://www.cnblogs.com/smileZAZ/archive/2023/05/10/videoSrc[Math.floor(Math.random() * 2)];
  video.crossOrigin ="anonymous";
  const texture = new THREE.VideoTexture(video);
  ...
  
      const material = new THREE.MeshBasicMaterial({
      color: 0xffffff,
      side: THREE.DoubleSide,
      map: texture,
    });

影片

影片總共分為三個部分,一塊是旋轉影片,一塊是分解影片和入場影片,我們使用gsap實作,

旋轉影片

  gsap.to(videoGroup.rotation, {
    duration: 30,
    y: -Math.PI * 2,
    repeat: -1,
    ease: "none",
  });

分解影片

      .to(RingGroup.position, {
        duration: 1,
        ease: "ease.inOut",
        y: Math.random() * 10 - 5,
        delay: 5,
      })
      .to(RingGroup.position, {
        duration: 1,
        ease: "ease.inOut",
        delay: 5,
        y: 0,
      })
  }

入場影片

    item.scale.set(1.2, 1.2, 1.2);
    gsap.to(item.scale, {
      duration: 0.8,
      x: 1,
      y: 1,
      repeat: 0,
      ease: "easeInOut",
    });

旋轉影片與分解影片可以寫在生成函式內,也可以寫在添加scene時,但是入場影片只能寫到scene后,因為在生成時,影片就添加上了,當我們點擊開始的時候才會將其加入場景中,而這時影片可能已經執行了,

 

總代碼

html

<!-- 
靈感來源:一人之下里的八奇技————風后奇門,但是劇中和漫畫中施展的羅盤有限,所以就參考了羅盤特效隨便排布,
從抖音選取了兩段剪輯隨機播放,

實作方式:Three.js





 -->




<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>

<body>
    <canvas ></canvas>
    <div >
        <div>大道五十,天衍四九,人遁其一</div>
        <img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e80a3fa048e84f02bb5ef5b6b04af87f~tplv-k3u1fbpfcp-no-mark:240:240:240:160.awebp?">
        <div >推衍中...</div>
    </div>
</body>

</html>

style

*{
  margin: 0;
  padding: 0;
}
body {
  background-color: #3d3f42;
}

.box{
  width: 350px;
  height: 250px;
  background-color: #000;
  position:absolute;
  top: calc(50% - 75px);
  left: calc(50% - 150px);
  border-radius: 10px;
  font-size: 16px;
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
  align-items: center;
}
.btn {
  width: 120px;
  height: 35px;
  line-height: 35px;
  color: #fff;
  border: 2px solid #fff;
  border-radius: 10px;
  font-size: 20px;
  transition: 0.5s;
  text-align: center;
  cursor:default;
  opacity: 0.5;
}
img{
  width: 200px;
  height: 150px;
}

js

import * as THREE from "[email protected]";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { gsap } from "[email protected]";
// Canvas
const canvas = document.querySelector("canvas.webgl");
const box = document.querySelector(".box");
const btn = document.querySelector(".btn");
const video = document.createElement("video");

// Scene
const scene = new THREE.Scene();
 
//----------------------

const fontLoader = new THREE.FontLoader();
const fontUrl =
  "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/fonts.json";
let font;
const loadFont = new Promise((resolve, reject) => {
  fontLoader.load(
    fontUrl,
    function (loadedFont) {
      font = loadedFont;
      resolve();
    },
    undefined,
    function (err) {
      reject(err);
    }
  );
});
const text = {
  五行: ["金", "木", "水", "火", "土"],
  八卦: ["乾", "坤", "震", "巽", "坎", "艮", "離", "兌"],
  數字: ["壹", "貳", "叁", "肆", "伍", "陸", "柒", "捌", "玖", "拾"],
  天干: ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"],
  地支: [
    "子",
    "丑",
    "寅",
    "卯",
    "辰",
    "巳",
    "午",
    "未",
    "申",
    "酉",
    "戌",
    "亥",
  ],
  方位: [
    "甲",
    "卯",
    "乙",
    "辰",
    "巽",
    "巳",
    "丙",
    "午",
    "丁",
    "未",
    "坤",
    "申",
    "庚",
    "酉",
    "辛",
    "戍",
    "干",
    "亥",
    "壬",
    "子",
    "癸",
    "丑",
    "艮",
    "寅",
  ],
  節氣: [
    "立  春",
    "雨  水",
    "驚  蟄",
    "春  分",
    "清  明",
    "谷  雨",
    "立  夏",
    "小  滿",
    "芒  種",
    "夏  至",
    "小  暑",
    "大  暑",
    "立  秋",
    "處  暑",
    "白  露",
    "秋  分",
    "寒  露",
    "霜  降",
    "立  冬",
    "小  雪",
    "大  雪",
    "冬  至",
    "小  寒",
    "大  寒",
  ],
  天星: [
    "天輔",
    "天壘",
    "天漢",
    "天廚",
    "天市",
    "天掊",
    "天苑",
    "天衡",
    "天官",
    "天罡",
    "太乙",
    "天屏",
    "太微",
    "天馬",
    "南極",
    "天常",
    "天鉞",
    "天關",
    "天潢",
    "少微",
    "天乙",
    "天魁",
    "天廄",
    "天皇",
  ],
  天干1: [
    "甲",
    " ",
    "乙",
    " ",
    "丙",
    " ",
    "丁",
    " ",
    "戊",
    " ",
    "己",
    " ",
    "庚",
    " ",
    "辛",
    " ",
    "壬",
    " ",
    "癸",
    " ",
    "甲",
    " ",
    "乙",
    " ",
  ],
  地支1: [
    "子",
    " ",
    "丑",
    " ",
    "寅",
    " ",
    "卯",
    " ",
    "辰",
    " ",
    "巳",
    " ",
    "午",
    " ",
    "未",
    " ",
    "申",
    " ",
    "酉",
    " ",
    "戌",
    " ",
    "亥",
    " ",
  ],
};
const data = https://www.cnblogs.com/smileZAZ/archive/2023/05/10/[
  {
    innerRing: 2,
    outerRing: 1.5,
    lineWidth: 0.1,
    circleWidth: [0.1, 0.1],
    lineNum: 8,
    text: [0xffffff],
    offsetX: 0,
    offsetY: 0,
    size: 0.3,
    direction: -1,
    duration: 40,
  },
  {
    innerRing: 3.5,
    outerRing: 0.7,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 24,
    text: text["方位"],
    offsetX: -0.2,
    offsetY: -0.08,
    size: 0.3,
    direction: 1,
    duration: 10,
  },
  {
    innerRing: 4.2,
    outerRing: 0.7,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 24,
    text: text["八卦"],
    offsetX: -0.2,
    offsetY: -0.08,
    size: 0.3,
    direction: -1,
    duration: 20,
  },
  {
    innerRing: 4.9,
    outerRing: 1.3,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 24,
    text: text["方位"],
    offsetX: -0.4,
    offsetY: -0.2,
    size: 0.6,
    direction: 1,
    duration: 30,
  },
  {
    innerRing: 6.2,
    outerRing: 0.4,
    lineWidth: 0.15,
    circleWidth: [0, 0],
    lineNum: 60,
    text: text["地支"],
    offsetX: -0.13,
    offsetY: 0.01,
    size: 0.2,
    direction: 1,
    duration: 25,
  },
  {
    innerRing: 6.6,
    outerRing: 0.4,
    lineWidth: 0.15,
    circleWidth: [0, 0],
    lineNum: 60,
    text: text["天干"],
    offsetX: -0.13,
    offsetY: -0.07,
    size: 0.2,
    direction: 1,
    duration: 25,
  },
  {
    innerRing: 7,
    outerRing: 0.5,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 36,
    text: text["天星"],
    offsetX: -0.27,
    offsetY: -0.03,
    size: 0.2,
    direction: -1,
    duration: 20,
  },
  {
    innerRing: 7.5,
    outerRing: 0.5,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 24,
    text: text["節氣"],
    offsetX: -0.36,
    offsetY: -0.03,
    size: 0.2,
    direction: 1,
    duration: 30,
  },
  {
    innerRing: 8,
    outerRing: 0.8,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 48,
    text: text["方位"],
    offsetX: -0.3,
    offsetY: -0.1,
    size: 0.4,
    direction: 1,
    duration: 35,
  },
  {
    innerRing: 8.8,
    outerRing: 0.8,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 32,
    text: text["八卦"],
    offsetX: -0.3,
    offsetY: -0.1,
    size: 0.4,
    direction: -1,
    duration: 60,
  },
  {
    innerRing: 9.6,
    outerRing: 0.4,
    lineWidth: 0.18,
    circleWidth: [0, 0],
    lineNum: 120,
    text: text["地支1"],
    offsetX: -0.13,
    offsetY: 0.01,
    size: 0.2,
    direction: 1,
    duration: 30,
  },
  {
    innerRing: 10,
    outerRing: 0.4,
    lineWidth: 0.18,
    circleWidth: [0, 0],
    lineNum: 120,
    text: text["天干1"],
    offsetX: -0.13,
    offsetY: -0.07,
    size: 0.2,
    direction: 1,
    duration: 30,
  },
  {
    innerRing: 10.4,
    outerRing: 0.5,
    lineWidth: 0.1,
    circleWidth: [0.1, 0.1],
    lineNum: 60,
    text: text["數字"],
    offsetX: -0.13,
    offsetY: -0.02,
    size: 0.2,
    direction: 1,
    duration: 25,
  },
  {
    innerRing: 10.9,
    outerRing: 0.5,
    lineWidth: 0.15,
    circleWidth: [0.1, 0.1],
    lineNum: 50,
    text: text["五行"],
    offsetX: -0.13,
    offsetY: -0.02,
    size: 0.2,
    direction: 1,
    duration: 35,
  },
  {
    innerRing: 11.7,
    outerRing: 1,
    lineWidth: 0.1,
    circleWidth: [1, 0],
    lineNum: 64,
    text: [0x000000],
    offsetX: 0,
    offsetY: 0,
    size: 0.3,
    direction: 1,
    duration: 30,
  },
];
const Rings = [];
const duration = [
  0, 0.7, 0.7, 0.7, 0.7, 0, 0.7, 0.7, 0.7, 0.7, 0.7, 0, 0.7, 0.7, 0.7,
];

//Ring
const Ring = ({
  innerRing,
  outerRing,
  lineWidth,
  circleWidth,
  lineNum,
  offsetX,
  offsetY,
  text,
  size,
  direction,
  duration,
}) => {
  const RingGroup = new THREE.Group();
  const circle = [0, outerRing];
  const material = new THREE.MeshStandardMaterial({
    color: 0xffffff,
    side: THREE.DoubleSide,
  });

  // create ring
  circle.forEach((i, j) => {
    const RingGeo = new THREE.RingGeometry(
      innerRing + i,
      innerRing + circleWidth[j] + i,
      64,
      1
    );
    const Ring = new THREE.Mesh(RingGeo, material);
    RingGroup.add(Ring);
  });

  // create line
  for (let i = 0; i < lineNum; i++) {
    const r = innerRing + circle[1] / 2;
    const rad = ((2 * Math.PI) / lineNum) * i;
    const x = Math.cos(rad) * r;
    const y = Math.sin(rad) * r;
    const planeGeo = new THREE.PlaneGeometry(lineWidth, circle[1]);
    const line = new THREE.Mesh(planeGeo, material);

    line.position.set(x, y, 0);
    line.rotation.set(0, 0, rad + Math.PI / 2);
    RingGroup.add(line);
  }

  // create text
  if (text.length > 1) {
    for (let i = 0; i < lineNum; i++) {
      const r = innerRing + circle[1] / 2;
      const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
      const x = Math.cos(rad) * r;
      const y = Math.sin(rad) * r;
      var txtGeo = new THREE.TextGeometry(text[i % text.length], {
        font: font,
        size: size,
        height: 0.001,
        curveSegments: 12,
      });
      txtGeo.translate(offsetX, offsetY, 0);
      var txtMater = new THREE.MeshStandardMaterial({ color: 0xffffff });
      var txtMesh = new THREE.Mesh(txtGeo, txtMater);
      txtMesh.position.set(x, y, 0);
      txtMesh.rotation.set(0, 0, rad + -Math.PI / 2);
      RingGroup.add(txtMesh);
    }
  }

  // create bagua
  if (text.length == 1) {
    const baguaData = https://www.cnblogs.com/smileZAZ/archive/2023/05/10/[
      [1, 1, 1],
      [0, 0, 0],
      [0, 0, 1],
      [0, 1, 0],
      [0, 1, 1],
      [1, 0, 0],
      [1, 0, 1],
      [1, 1, 0],
    ];
    for (let i = 0; i < lineNum; i++) {
      const r = innerRing + circle[1] / 2;
      const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
      const x = Math.cos(rad) * r;
      const y = Math.sin(rad) * r;
      RingGroup.add(
        createBagua(baguaData[i % 8], x, y, 0.0001, rad + Math.PI / 2, text[0]),
        createBagua(baguaData[i % 8], x, y, -0.0001, rad + Math.PI / 2, text[0])
      );
    }
  }

  // animation
  {
    gsap.to(RingGroup.rotation, {
      duration: duration,
      z: Math.PI * 2 * direction,
      repeat: -1,
      ease:"none",
    });
    
    const amColor = { r: 1, g: 1, b: 1 };
    const explode = gsap.timeline({ repeat: -1, delay: 5 });
    explode
      .to(RingGroup.position, {
        duration: 1,
        ease: "ease.inOut",
        y: Math.random() * 10 - 5,
        delay: 5,
      })
      .to(amColor, {
        r: 133 / 255,
        g: 193 / 255,
        b: 255 / 255,
        duration: 2,
        onUpdate: () =>
          ambientLight.color.setRGB(amColor.r, amColor.g, amColor.b),
      })
      .to(RingGroup.position, {
        duration: 1,
        ease: "ease.inOut",
        delay: 5,
        y: 0,
      })
      .to(amColor, {
        r: 1,
        g: 1,
        b: 1,
        duration: 3,
        onUpdate: () =>
          ambientLight.color.setRGB(amColor.r, amColor.g, amColor.b),
      });
  }

  // rotate
  RingGroup.rotateX(-Math.PI / 2);
  return RingGroup;
};

//taiji
const createTaiji = (position, scale) => {
  const taiji = new THREE.Group();
  const createCircle = (r, color, thetaStart, thetaLength) => {
    const material = new THREE.MeshBasicMaterial({
      color: color,
      side: THREE.DoubleSide,
    });
    const geometry = new THREE.CircleGeometry(r, 64, thetaStart, thetaLength);
    const circle = new THREE.Mesh(geometry, material);
    return circle;
  };

  const ying = createCircle(1.8, 0x000000, 0, Math.PI);
  const yang = createCircle(1.8, 0xffffff, Math.PI, Math.PI);
  const Lblack = createCircle(0.9, 0x000000, 0, Math.PI * 2);
  const Lwhite = createCircle(0.9, 0xffffff, 0, Math.PI * 2);
  const Sblack = createCircle(0.25, 0x000000, 0, Math.PI * 2);
  const Swhite = createCircle(0.25, 0xffffff, 0, Math.PI * 2);

  const Lblack1 = createCircle(0.9, 0x000000, 0, Math.PI * 2);
  const Lwhite1 = createCircle(0.9, 0xffffff, 0, Math.PI * 2);
  const Sblack1 = createCircle(0.25, 0x000000, 0, Math.PI * 2);
  const Swhite1 = createCircle(0.25, 0xffffff, 0, Math.PI * 2);

  Lblack.position.set(-0.9, 0, 0.001);
  Lwhite.position.set(0.9, 0, 0.001);
  Swhite.position.set(-0.9, 0, 0.002);
  Sblack.position.set(0.9, 0, 0.002);
  Lblack1.position.set(-0.9, 0, -0.001);
  Lwhite1.position.set(0.9, 0, -0.001);
  Swhite1.position.set(-0.9, 0, -0.002);
  Sblack1.position.set(0.9, 0, -0.002);

  taiji.add(
    ying,
    yang,
    Lblack,
    Lwhite,
    Swhite,
    Sblack,
    Lblack1,
    Lwhite1,
    Swhite1,
    Sblack1
  );
  gsap.to(taiji.rotation, {
    duration: 30,
    z: Math.PI * 2,
    repeat: -1,
    ease: "none",
  });
  taiji.rotateX(-Math.PI / 2);
  taiji.position.set(...position);
  taiji.scale.set(...scale);
  return taiji;
};
scene.add(createTaiji([0, 0, 0], [1, 1, 1]));

// bagua
const createBagua = (data, x, y, z, deg, color) => {
  const idx = [-0.32, 0, 0.32];
  const bagua = new THREE.Group();
  const material = new THREE.MeshStandardMaterial({
    color: color,
    side: THREE.DoubleSide,
  });
  data.forEach((i, j) => {
    if (i == 1) {
      const yang = new THREE.Mesh(new THREE.PlaneGeometry(1, 0.2), material);
      yang.position.set(0, idx[j], 0);
      bagua.add(yang);
    }
    if (i == 0) {
      const ying1 = new THREE.Mesh(
        new THREE.PlaneGeometry(0.45, 0.2),
        material
      );
      const ying2 = new THREE.Mesh(
        new THREE.PlaneGeometry(0.45, 0.2),
        material
      );
      ying1.position.set(-0.275, idx[j], 0);
      ying2.position.set(0.275, idx[j], 0);
      bagua.add(ying1, ying2);
    }
  });
  bagua.position.set(x, y, z);
  bagua.rotation.set(0, 0, deg);
  return bagua;
};


const showVideo = () => {
  const videoSrc = https://www.cnblogs.com/smileZAZ/archive/2023/05/10/["https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/yAC65vN6.mp4",
    "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/6Z5VZdZM.mp4",
  ];
  video.src = https://www.cnblogs.com/smileZAZ/archive/2023/05/10/videoSrc[Math.floor(Math.random() * 2)];
  video.crossOrigin ="anonymous";
  const texture = new THREE.VideoTexture(video);
  const videoGroup = new THREE.Group();
  for (let i = 0; i < 8; i++) {
    const r = 25;
    const rad = ((2 * Math.PI) / 8) * i;
    const x = Math.cos(rad) * r;
    const y = Math.sin(rad) * r;
    const planeGeo = new THREE.PlaneGeometry(16, 9);
    const material = new THREE.MeshBasicMaterial({
      color: 0xffffff,
      side: THREE.DoubleSide,
      map: texture,
    });
    const plane = new THREE.Mesh(planeGeo, material);

    plane.position.set(x, 4.5, y);
    if (i % 2 == 0) plane.rotation.set(0, rad + Math.PI / 2, 0);
    else plane.rotation.set(0, rad, 0);
    videoGroup.add(plane);
  }
  gsap.to(videoGroup.rotation, {
    duration: 30,
    y: -Math.PI * 2,
    repeat: -1,
    ease: "none",
  });
  scene.add(videoGroup);
};

//loadFont, Rings
loadFont.then(() => {
  data.forEach((item) => {
    Rings.push(Ring(item));
  });
  btn.innerText = "入 局";
  btn.style.opacity = 1;
  btn.style.cursor = "pointer";
});

//start
const start = function () {
  const showRing = (item) => {
    scene.add(item);
    item.scale.set(1.2, 1.2, 1.2);
    gsap.to(item.scale, {
      duration: 0.8,
      x: 1,
      y: 1,
      repeat: 0,
      ease: "easeInOut",
    });
  };
  const tl = gsap.timeline();
  Rings.forEach((item, idx) => {
    tl.to(".webgl", { duration: duration[idx] }).call(() => {
      showRing(item);
    });
  });
};



btn.addEventListener("click", () => {
  box.style.display = "none";
  start();
  showVideo();
  video.play();
  video.loop = true;
});


//----------------------
 

//Light
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
scene.add(ambientLight);
 

//Sizes
const sizes = {
  width: window.innerWidth,
  height: window.innerHeight,
};


// Camera
const camera = new THREE.PerspectiveCamera(
  75,
  sizes.width / sizes.height,
  1,
  1000
);
camera.position.y = 10;
camera.position.x = 10;
camera.position.z = 10;
camera.lookAt(scene.position);
scene.add(camera);


//Renderer
const renderer = new THREE.WebGLRenderer({
  canvas: canvas,
  antialias: true,
  alpha: true,
});

renderer.setSize(sizes.width, sizes.height);


//controls
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;
controls.maxDistance = 50;
controls.enablePan = false;

const tick = () => {
  renderer.render(scene, camera);
  controls.update();
  window.requestAnimationFrame(tick);
};
tick();


window.addEventListener("resize", () => {
  sizes.height = window.innerHeight;
  sizes.width = window.innerWidth;

  camera.aspect = sizes.width / sizes.height;
  camera.updateProjectionMatrix();

  renderer.setSize(sizes.width, sizes.height);
  renderer.setPixelRatio(window.devicePixelRatio);
});

本文轉載于:

https://juejin.cn/post/7220629398965108794

如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/552152.html

標籤:其他

上一篇:配置式表單渲染器的實作

下一篇:返回列表

標籤雲
其他(158818) Python(38125) JavaScript(25413) Java(18025) C(15225) 區塊鏈(8264) C#(7972) AI(7469) 爪哇(7425) MySQL(7175) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5871) 数组(5741) R(5409) Linux(5338) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4570) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2432) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1972) 功能(1967) Web開發(1951) HtmlCss(1935) python-3.x(1918) 弹簧靴(1913) C++(1913) xml(1889) PostgreSQL(1875) .NETCore(1860) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 記錄--ThreeJs手搓一個羅盤特效

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 先上效果 前言 最近在學Three.js.,對著檔案看了一周多,正好趕上碼上掘金的活動,就順便寫了一個小demo,手搓一個羅盤特效。 太極 先來看一下太極的實作方式,這里我們使用CircleGeometry,將其分解開來可以看出是由圓形和 ......

    uj5u.com 2023-05-11 08:24:49 more
  • 配置式表單渲染器的實作

    我們是袋鼠云數堆疊 UED 團隊,致力于打造優秀的一站式資料中臺產品。我們始終保持工匠精神,探索前端道路,為社區積累并傳播經驗價值。。 本文作者:奇銘(掘金) 需求背景 前段時間,離線計算產品接到改造資料同步表單的需求。 一方面,資料同步模塊的代碼可讀性和可維護性比較差,相對應的,在資料同步模塊開發新 ......

    uj5u.com 2023-05-11 08:23:24 more
  • SAP 開發環境搭建入門

    自2006年畢業之后一直從事企業管理軟體的開發與維護作業,期間經歷了Windows Forms, ASP.NET Web Forms, WPF, ASP.NET MVC, AngularJS TypeScript等技術階段。作業幾年后有幸運進入一家規范化的ERP軟體開發公司,接觸并深入了解ERP這個 ......

    uj5u.com 2023-05-11 08:21:21 more
  • ArcGIS Pro 3.0.1破解版安裝步驟

    記錄ArcGIS Pro 3.0.1破解版安裝步驟。 1、先安裝windowsdesktop組件:windowsdesktop-runtime-6.0.5-win-x64.exe 2、安裝ArcGIS Pro3.0軟體:ArcGISPro_30_182209.exe 等待安裝完成: 安裝完成后先不運 ......

    uj5u.com 2023-05-11 08:21:02 more
  • arcmap如何使用PyScripter進行編輯 以及使用程序中遇到的無法解

    一、環境配置 1.安裝PyScripter 安裝檔案連接: 鏈接:https://pan.baidu.com/s/1HauyVCs6UoXLFam0nkRtxA 提取碼:a6c3 2.arcmap內配置環境 選單欄,地理處理 地理處理選項 將腳本工具編輯器和除錯程式均設定為 安裝PyScripter ......

    uj5u.com 2023-05-11 08:20:50 more
  • SAP 開發環境搭建入門

    自2006年畢業之后一直從事企業管理軟體的開發與維護作業,期間經歷了Windows Forms, ASP.NET Web Forms, WPF, ASP.NET MVC, AngularJS TypeScript等技術階段。作業幾年后有幸運進入一家規范化的ERP軟體開發公司,接觸并深入了解ERP這個 ......

    uj5u.com 2023-05-11 08:20:25 more
  • ArcGIS Pro 3.0.1破解版安裝步驟

    記錄ArcGIS Pro 3.0.1破解版安裝步驟。 1、先安裝windowsdesktop組件:windowsdesktop-runtime-6.0.5-win-x64.exe 2、安裝ArcGIS Pro3.0軟體:ArcGISPro_30_182209.exe 等待安裝完成: 安裝完成后先不運 ......

    uj5u.com 2023-05-11 08:19:49 more
  • arcmap如何使用PyScripter進行編輯 以及使用程序中遇到的無法解

    一、環境配置 1.安裝PyScripter 安裝檔案連接: 鏈接:https://pan.baidu.com/s/1HauyVCs6UoXLFam0nkRtxA 提取碼:a6c3 2.arcmap內配置環境 選單欄,地理處理 地理處理選項 將腳本工具編輯器和除錯程式均設定為 安裝PyScripter ......

    uj5u.com 2023-05-11 08:14:30 more
  • HTML中meta標簽的那些屬性

    <meta> 標簽是 HTML 中用于描述網頁元資訊的元素。它位于 <head> 部分,不會顯示在頁面內容中,但對于瀏覽器、搜索引擎等具有重要作用。主要作用有:定義檔案的字符編碼、提供網頁的描述資訊、關鍵詞、作者、視口設定等,這些資訊有助于搜索引擎理解和索引網頁內容。 <meta> 標簽的主要屬性有 ......

    uj5u.com 2023-05-10 11:10:09 more
  • 深入理解前端位元組二進制知識以及相關API

    當前,前端對二進制資料有許多的API可以使用,這豐富了前端對檔案資料的處理能力,有了這些能力,就能夠對圖片等檔案的資料進行各種處理。 本文將著重介紹一些前端二進制資料處理相關的API知識,如Blob、File、FileReader、ArrayBuffer、TypeArray、DataView等等。 ......

    uj5u.com 2023-05-10 11:09:57 more