记录--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 = [
      [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://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/yAC65vN6.mp4",
    "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/6Z5VZdZM.mp4",
  ];
  video.src = 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 class="webgl"></canvas>
    <div class="box">
        <div>大道五十,天衍四九,人遁其一</div>
        <img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e80a3fa048e84f02bb5ef5b6b04af87f~tplv-k3u1fbpfcp-no-mark:240:240:240:160.awebp?">
        <div class="btn">推衍中...</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 "three@0.125.1";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { gsap } from "gsap@3.5.1";
// 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 = [
  {
    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 = [
      [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://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/yAC65vN6.mp4",
    "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/6Z5VZdZM.mp4",
  ];
  video.src = 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

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

热门相关:地球第一剑   无限杀路   横行霸道   学霸女神超给力   横行霸道