记录–ThreeJs手搓一个罗盘特效

  • 记录–ThreeJs手搓一个罗盘特效已关闭评论
  • 275 次浏览
  • A+
所属分类:Web前端
摘要

最近在学Three.js.,对着文档看了一周多,正好赶上码上掘金的活动,就顺便写了一个小demo,手搓一个罗盘特效。


这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

记录--ThreeJs手搓一个罗盘特效

先上效果

记录--ThreeJs手搓一个罗盘特效

前言

最近在学Three.js.,对着文档看了一周多,正好赶上码上掘金的活动,就顺便写了一个小demo,手搓一个罗盘特效。

太极

先来看一下太极的实现方式,这里我们使用CircleGeometry,将其分解开来可以看出是由圆形和半圆形组成 。

记录--ThreeJs手搓一个罗盘特效

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行 还有一些需要注意的地方写在注释里了。

罗盘

接下来看罗盘的实现,罗盘由一个个圆环组成,一个圆环又由内圈、外圈、分隔线、文字、八卦构成。

记录--ThreeJs手搓一个罗盘特效

 

内外圈

内外圈我们使用两个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

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

 记录--ThreeJs手搓一个罗盘特效