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

先上效果

前言

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

太极

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

CircleGeometry

CircleGeometry 官网案例
radius 半径
segments 分段(三角面)的数量
thetaStart 第一个分段的起始角度
thetaLength 圆形扇区的中心角

这里不需要用到segments,但是需要颜色,所以定义一个函数传入半径、颜色、起始角度、中心角。

  1. const createCircle = (r, color, thetaStart, thetaLength) => {
  2. const material = new THREE.MeshBasicMaterial({
  3. color: color,
  4. side: THREE.DoubleSide
  5. });
  6. const geometry = new THREE.CircleGeometry(r, 64, thetaStart, thetaLength);
  7. const circle = new THREE.Mesh(geometry, material);
  8. return circle;
  9. };

我们只需要通过传参生产不同大小的圆或半圆,再进行位移就可以实现其效果。

参考代码/73-96行 还有一些需要注意的地方写在注释里了。

罗盘

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

内外圈

内外圈我们使用两个RingGeometry

RingGeometry 官网案例
innerRadius 内部半径
outerRadius 外部半径
thetaSegments 圆环的分段数
phiSegments 圆环的分段数
thetaStart 起始角度
thetaLength 圆心角

通过circle控制内外圆圈的尺寸,circleWidth控制圆圈的线宽

  1. const circleWidth = [0.1, 0.1]
  2. const circle = [0, 1];
  3. circle.forEach((i, j) => {
  4. const RingGeo = new THREE.RingGeometry(
  5. innerRing + i,
  6. innerRing + i + circleWidth[j],
  7. 64,
  8. 1
  9. );
  10. const Ring = new THREE.Mesh(RingGeo, material);
  11. RingGroup.add(Ring);
  12. });

分隔线

分隔线使用的是PlaneGeometry

PlaneGeometry 官网案例
width 宽度
height 高度
widthSegments 宽度分段数
heightSegments 高度分段数

关于分隔线,它的长度就是内外圈的差值,所以这里使用外圈的数值,确定与圆心的距离就要使用内圈的数值加上自身长度除2。除此之外,还需要计算分隔线与圆心的夹角。

  1. for (let i = 0; i < lineNum; i++) {
  2. const r = innerRing + circle[1] / 2;
  3. const rad = ((2 * Math.PI) / lineNum) * i;
  4. const x = Math.cos(rad) * r;
  5. const y = Math.sin(rad) * r;
  6. const planeGeo = new THREE.PlaneGeometry(lineWidth, circle[1]);
  7. const line = new THREE.Mesh(planeGeo, material);
  8.  
  9. line.position.set(x, y, 0);
  10. line.rotation.set(0, 0, rad + Math.PI / 2);
  11. RingGroup.add(line);
  12. }

文字

文字使用的是TextGeometry,定位与分隔线一致,只需要交错开来。

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

不过TextGeometry的使用有一个得注意得前提,我们需要引入字体文件。

  1. const fontLoader = new THREE.FontLoader();
  2. const fontUrl =
  3. "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/fonts.json";
  4. let font;
  5. const loadFont = new Promise((resolve, reject) => {
  6. fontLoader.load(
  7. fontUrl,
  8. function (loadedFont) {
  9. font = loadedFont;
  10. resolve();
  11. },
  12. undefined,
  13. function (err) {
  14. reject(err);
  15. }
  16. );
  17. });

八卦

圆环中除了文字之外,还能展示八卦,通过传递baguaData给createBagua生成每一个符号。

  1. const baguaData = [
  2. [1, 1, 1],
  3. [0, 0, 0],
  4. [0, 0, 1],
  5. [0, 1, 0],
  6. [0, 1, 1],
  7. [1, 0, 0],
  8. [1, 0, 1],
  9. [1, 1, 0],
  10. ];
  11. for (let i = 0; i < lineNum; i++) {
  12. const r = innerRing + circle[1] / 2;
  13. const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
  14. const x = Math.cos(rad) * r;
  15. const y = Math.sin(rad) * r;
  16. RingGroup.add(
  17. createBagua(baguaData[i % 8], x, y, 0 , rad + Math.PI / 2, text[0]),
  18. );
  19. }

createBagua参考代码/114-146行 ,和分隔线是一样的,使用了PlaneGeometry只是做了一些位置的设置。

视频贴图

在罗盘外,还有一圈视频,这里是用到了VideoTexture,实现也很简单。唯一得注意的是视频的跨域问题,需要配置video.crossOrigin = "anonymous"

  1. const videoSrc = [
  2. "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/yAC65vN6.mp4",
  3. "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/6Z5VZdZM.mp4",
  4. ];
  5. video.src = videoSrc[Math.floor(Math.random() * 2)];
  6. video.crossOrigin = "anonymous";
  7. const texture = new THREE.VideoTexture(video);
  8. ...
  9.  
  10. const material = new THREE.MeshBasicMaterial({
  11. color: 0xffffff,
  12. side: THREE.DoubleSide,
  13. map: texture,
  14. });

动画

动画总共分为三个部分,一块是旋转动画,一块是分解动画和入场动画,我们使用gsap实现。

旋转动画

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

分解动画

  1. .to(RingGroup.position, {
  2. duration: 1,
  3. ease: "ease.inOut",
  4. y: Math.random() * 10 - 5,
  5. delay: 5,
  6. })
  7. .to(RingGroup.position, {
  8. duration: 1,
  9. ease: "ease.inOut",
  10. delay: 5,
  11. y: 0,
  12. })
  13. }

入场动画

  1. item.scale.set(1.2, 1.2, 1.2);
  2. gsap.to(item.scale, {
  3. duration: 0.8,
  4. x: 1,
  5. y: 1,
  6. repeat: 0,
  7. ease: "easeInOut",
  8. });

旋转动画与分解动画可以写在生成函数内,也可以写在添加scene时,但是入场动画只能写到scene后,因为在生成时,动画就添加上了,当我们点击开始的时候才会将其加入场景中,而这时动画可能已经执行了。

总代码

html

  1. <!--
  2. 灵感来源:一人之下里的八奇技————风后奇门,但是剧中和漫画中施展的罗盘有限,所以就参考了罗盘特效随便排布。
  3. 从抖音选取了两段剪辑随机播放。
  4.  
  5. 实现方式:Three.js
  6.  
  7. -->
  8.  
  9. <!DOCTYPE html>
  10. <html lang="en">
  11.  
  12. <head>
  13. <meta charset="UTF-8">
  14. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  15. </head>
  16.  
  17. <body>
  18. <canvas class="webgl"></canvas>
  19. <div class="box">
  20. <div>大道五十,天衍四九,人遁其一</div>
  21. <img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e80a3fa048e84f02bb5ef5b6b04af87f~tplv-k3u1fbpfcp-no-mark:240:240:240:160.awebp?">
  22. <div class="btn">推衍中...</div>
  23. </div>
  24. </body>
  25.  
  26. </html>

style

  1. *{
  2. margin: 0;
  3. padding: 0;
  4. }
  5. body {
  6. background-color: #3d3f42;
  7. }
  8.  
  9. .box{
  10. width: 350px;
  11. height: 250px;
  12. background-color: #000;
  13. position:absolute;
  14. top: calc(50% - 75px);
  15. left: calc(50% - 150px);
  16. border-radius: 10px;
  17. font-size: 16px;
  18. color: #fff;
  19. display: flex;
  20. flex-direction: column;
  21. justify-content: space-evenly;
  22. align-items: center;
  23. }
  24. .btn {
  25. width: 120px;
  26. height: 35px;
  27. line-height: 35px;
  28. color: #fff;
  29. border: 2px solid #fff;
  30. border-radius: 10px;
  31. font-size: 20px;
  32. transition: 0.5s;
  33. text-align: center;
  34. cursor:default;
  35. opacity: 0.5;
  36. }
  37. img{
  38. width: 200px;
  39. height: 150px;
  40. }

js

  1. import * as THREE from "three@0.125.1";
  2. import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
  3. import { gsap } from "gsap@3.5.1";
  4. // Canvas
  5. const canvas = document.querySelector("canvas.webgl");
  6. const box = document.querySelector(".box");
  7. const btn = document.querySelector(".btn");
  8. const video = document.createElement("video");
  9.  
  10. // Scene
  11. const scene = new THREE.Scene();
  12.  
  13. //----------------------
  14.  
  15. const fontLoader = new THREE.FontLoader();
  16. const fontUrl =
  17. "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/fonts.json";
  18. let font;
  19. const loadFont = new Promise((resolve, reject) => {
  20. fontLoader.load(
  21. fontUrl,
  22. function (loadedFont) {
  23. font = loadedFont;
  24. resolve();
  25. },
  26. undefined,
  27. function (err) {
  28. reject(err);
  29. }
  30. );
  31. });
  32. const text = {
  33. 五行: ["金", "木", "水", "火", "土"],
  34. 八卦: ["乾", "坤", "震", "巽", "坎", "艮", "离", "兑"],
  35. 数字: ["壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖", "拾"],
  36. 天干: ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"],
  37. 地支: [
  38. "子",
  39. "丑",
  40. "寅",
  41. "卯",
  42. "辰",
  43. "巳",
  44. "午",
  45. "未",
  46. "申",
  47. "酉",
  48. "戌",
  49. "亥",
  50. ],
  51. 方位: [
  52. "甲",
  53. "卯",
  54. "乙",
  55. "辰",
  56. "巽",
  57. "巳",
  58. "丙",
  59. "午",
  60. "丁",
  61. "未",
  62. "坤",
  63. "申",
  64. "庚",
  65. "酉",
  66. "辛",
  67. "戍",
  68. "干",
  69. "亥",
  70. "壬",
  71. "子",
  72. "癸",
  73. "丑",
  74. "艮",
  75. "寅",
  76. ],
  77. 节气: [
  78. "立 春",
  79. "雨 水",
  80. "惊 蛰",
  81. "春 分",
  82. "清 明",
  83. "谷 雨",
  84. "立 夏",
  85. "小 满",
  86. "芒 种",
  87. "夏 至",
  88. "小 暑",
  89. "大 暑",
  90. "立 秋",
  91. "处 暑",
  92. "白 露",
  93. "秋 分",
  94. "寒 露",
  95. "霜 降",
  96. "立 冬",
  97. "小 雪",
  98. "大 雪",
  99. "冬 至",
  100. "小 寒",
  101. "大 寒",
  102. ],
  103. 天星: [
  104. "天辅",
  105. "天垒",
  106. "天汉",
  107. "天厨",
  108. "天市",
  109. "天掊",
  110. "天苑",
  111. "天衡",
  112. "天官",
  113. "天罡",
  114. "太乙",
  115. "天屏",
  116. "太微",
  117. "天马",
  118. "南极",
  119. "天常",
  120. "天钺",
  121. "天关",
  122. "天潢",
  123. "少微",
  124. "天乙",
  125. "天魁",
  126. "天厩",
  127. "天皇",
  128. ],
  129. 天干1: [
  130. "甲",
  131. " ",
  132. "乙",
  133. " ",
  134. "丙",
  135. " ",
  136. "丁",
  137. " ",
  138. "戊",
  139. " ",
  140. "己",
  141. " ",
  142. "庚",
  143. " ",
  144. "辛",
  145. " ",
  146. "壬",
  147. " ",
  148. "癸",
  149. " ",
  150. "甲",
  151. " ",
  152. "乙",
  153. " ",
  154. ],
  155. 地支1: [
  156. "子",
  157. " ",
  158. "丑",
  159. " ",
  160. "寅",
  161. " ",
  162. "卯",
  163. " ",
  164. "辰",
  165. " ",
  166. "巳",
  167. " ",
  168. "午",
  169. " ",
  170. "未",
  171. " ",
  172. "申",
  173. " ",
  174. "酉",
  175. " ",
  176. "戌",
  177. " ",
  178. "亥",
  179. " ",
  180. ],
  181. };
  182. const data = [
  183. {
  184. innerRing: 2,
  185. outerRing: 1.5,
  186. lineWidth: 0.1,
  187. circleWidth: [0.1, 0.1],
  188. lineNum: 8,
  189. text: [0xffffff],
  190. offsetX: 0,
  191. offsetY: 0,
  192. size: 0.3,
  193. direction: -1,
  194. duration: 40,
  195. },
  196. {
  197. innerRing: 3.5,
  198. outerRing: 0.7,
  199. lineWidth: 0.15,
  200. circleWidth: [0.1, 0.1],
  201. lineNum: 24,
  202. text: text["方位"],
  203. offsetX: -0.2,
  204. offsetY: -0.08,
  205. size: 0.3,
  206. direction: 1,
  207. duration: 10,
  208. },
  209. {
  210. innerRing: 4.2,
  211. outerRing: 0.7,
  212. lineWidth: 0.15,
  213. circleWidth: [0.1, 0.1],
  214. lineNum: 24,
  215. text: text["八卦"],
  216. offsetX: -0.2,
  217. offsetY: -0.08,
  218. size: 0.3,
  219. direction: -1,
  220. duration: 20,
  221. },
  222. {
  223. innerRing: 4.9,
  224. outerRing: 1.3,
  225. lineWidth: 0.15,
  226. circleWidth: [0.1, 0.1],
  227. lineNum: 24,
  228. text: text["方位"],
  229. offsetX: -0.4,
  230. offsetY: -0.2,
  231. size: 0.6,
  232. direction: 1,
  233. duration: 30,
  234. },
  235. {
  236. innerRing: 6.2,
  237. outerRing: 0.4,
  238. lineWidth: 0.15,
  239. circleWidth: [0, 0],
  240. lineNum: 60,
  241. text: text["地支"],
  242. offsetX: -0.13,
  243. offsetY: 0.01,
  244. size: 0.2,
  245. direction: 1,
  246. duration: 25,
  247. },
  248. {
  249. innerRing: 6.6,
  250. outerRing: 0.4,
  251. lineWidth: 0.15,
  252. circleWidth: [0, 0],
  253. lineNum: 60,
  254. text: text["天干"],
  255. offsetX: -0.13,
  256. offsetY: -0.07,
  257. size: 0.2,
  258. direction: 1,
  259. duration: 25,
  260. },
  261. {
  262. innerRing: 7,
  263. outerRing: 0.5,
  264. lineWidth: 0.15,
  265. circleWidth: [0.1, 0.1],
  266. lineNum: 36,
  267. text: text["天星"],
  268. offsetX: -0.27,
  269. offsetY: -0.03,
  270. size: 0.2,
  271. direction: -1,
  272. duration: 20,
  273. },
  274. {
  275. innerRing: 7.5,
  276. outerRing: 0.5,
  277. lineWidth: 0.15,
  278. circleWidth: [0.1, 0.1],
  279. lineNum: 24,
  280. text: text["节气"],
  281. offsetX: -0.36,
  282. offsetY: -0.03,
  283. size: 0.2,
  284. direction: 1,
  285. duration: 30,
  286. },
  287. {
  288. innerRing: 8,
  289. outerRing: 0.8,
  290. lineWidth: 0.15,
  291. circleWidth: [0.1, 0.1],
  292. lineNum: 48,
  293. text: text["方位"],
  294. offsetX: -0.3,
  295. offsetY: -0.1,
  296. size: 0.4,
  297. direction: 1,
  298. duration: 35,
  299. },
  300. {
  301. innerRing: 8.8,
  302. outerRing: 0.8,
  303. lineWidth: 0.15,
  304. circleWidth: [0.1, 0.1],
  305. lineNum: 32,
  306. text: text["八卦"],
  307. offsetX: -0.3,
  308. offsetY: -0.1,
  309. size: 0.4,
  310. direction: -1,
  311. duration: 60,
  312. },
  313. {
  314. innerRing: 9.6,
  315. outerRing: 0.4,
  316. lineWidth: 0.18,
  317. circleWidth: [0, 0],
  318. lineNum: 120,
  319. text: text["地支1"],
  320. offsetX: -0.13,
  321. offsetY: 0.01,
  322. size: 0.2,
  323. direction: 1,
  324. duration: 30,
  325. },
  326. {
  327. innerRing: 10,
  328. outerRing: 0.4,
  329. lineWidth: 0.18,
  330. circleWidth: [0, 0],
  331. lineNum: 120,
  332. text: text["天干1"],
  333. offsetX: -0.13,
  334. offsetY: -0.07,
  335. size: 0.2,
  336. direction: 1,
  337. duration: 30,
  338. },
  339. {
  340. innerRing: 10.4,
  341. outerRing: 0.5,
  342. lineWidth: 0.1,
  343. circleWidth: [0.1, 0.1],
  344. lineNum: 60,
  345. text: text["数字"],
  346. offsetX: -0.13,
  347. offsetY: -0.02,
  348. size: 0.2,
  349. direction: 1,
  350. duration: 25,
  351. },
  352. {
  353. innerRing: 10.9,
  354. outerRing: 0.5,
  355. lineWidth: 0.15,
  356. circleWidth: [0.1, 0.1],
  357. lineNum: 50,
  358. text: text["五行"],
  359. offsetX: -0.13,
  360. offsetY: -0.02,
  361. size: 0.2,
  362. direction: 1,
  363. duration: 35,
  364. },
  365. {
  366. innerRing: 11.7,
  367. outerRing: 1,
  368. lineWidth: 0.1,
  369. circleWidth: [1, 0],
  370. lineNum: 64,
  371. text: [0x000000],
  372. offsetX: 0,
  373. offsetY: 0,
  374. size: 0.3,
  375. direction: 1,
  376. duration: 30,
  377. },
  378. ];
  379. const Rings = [];
  380. const duration = [
  381. 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,
  382. ];
  383.  
  384. //Ring
  385. const Ring = ({
  386. innerRing,
  387. outerRing,
  388. lineWidth,
  389. circleWidth,
  390. lineNum,
  391. offsetX,
  392. offsetY,
  393. text,
  394. size,
  395. direction,
  396. duration,
  397. }) => {
  398. const RingGroup = new THREE.Group();
  399. const circle = [0, outerRing];
  400. const material = new THREE.MeshStandardMaterial({
  401. color: 0xffffff,
  402. side: THREE.DoubleSide,
  403. });
  404.  
  405. // create ring
  406. circle.forEach((i, j) => {
  407. const RingGeo = new THREE.RingGeometry(
  408. innerRing + i,
  409. innerRing + circleWidth[j] + i,
  410. 64,
  411. 1
  412. );
  413. const Ring = new THREE.Mesh(RingGeo, material);
  414. RingGroup.add(Ring);
  415. });
  416.  
  417. // create line
  418. for (let i = 0; i < lineNum; i++) {
  419. const r = innerRing + circle[1] / 2;
  420. const rad = ((2 * Math.PI) / lineNum) * i;
  421. const x = Math.cos(rad) * r;
  422. const y = Math.sin(rad) * r;
  423. const planeGeo = new THREE.PlaneGeometry(lineWidth, circle[1]);
  424. const line = new THREE.Mesh(planeGeo, material);
  425.  
  426. line.position.set(x, y, 0);
  427. line.rotation.set(0, 0, rad + Math.PI / 2);
  428. RingGroup.add(line);
  429. }
  430.  
  431. // create text
  432. if (text.length > 1) {
  433. for (let i = 0; i < lineNum; i++) {
  434. const r = innerRing + circle[1] / 2;
  435. const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
  436. const x = Math.cos(rad) * r;
  437. const y = Math.sin(rad) * r;
  438. var txtGeo = new THREE.TextGeometry(text[i % text.length], {
  439. font: font,
  440. size: size,
  441. height: 0.001,
  442. curveSegments: 12,
  443. });
  444. txtGeo.translate(offsetX, offsetY, 0);
  445. var txtMater = new THREE.MeshStandardMaterial({ color: 0xffffff });
  446. var txtMesh = new THREE.Mesh(txtGeo, txtMater);
  447. txtMesh.position.set(x, y, 0);
  448. txtMesh.rotation.set(0, 0, rad + -Math.PI / 2);
  449. RingGroup.add(txtMesh);
  450. }
  451. }
  452.  
  453. // create bagua
  454. if (text.length == 1) {
  455. const baguaData = [
  456. [1, 1, 1],
  457. [0, 0, 0],
  458. [0, 0, 1],
  459. [0, 1, 0],
  460. [0, 1, 1],
  461. [1, 0, 0],
  462. [1, 0, 1],
  463. [1, 1, 0],
  464. ];
  465. for (let i = 0; i < lineNum; i++) {
  466. const r = innerRing + circle[1] / 2;
  467. const rad = ((2 * Math.PI) / lineNum) * i + Math.PI / lineNum;
  468. const x = Math.cos(rad) * r;
  469. const y = Math.sin(rad) * r;
  470. RingGroup.add(
  471. createBagua(baguaData[i % 8], x, y, 0.0001, rad + Math.PI / 2, text[0]),
  472. createBagua(baguaData[i % 8], x, y, -0.0001, rad + Math.PI / 2, text[0])
  473. );
  474. }
  475. }
  476.  
  477. // animation
  478. {
  479. gsap.to(RingGroup.rotation, {
  480. duration: duration,
  481. z: Math.PI * 2 * direction,
  482. repeat: -1,
  483. ease: "none",
  484. });
  485.  
  486. const amColor = { r: 1, g: 1, b: 1 };
  487. const explode = gsap.timeline({ repeat: -1, delay: 5 });
  488. explode
  489. .to(RingGroup.position, {
  490. duration: 1,
  491. ease: "ease.inOut",
  492. y: Math.random() * 10 - 5,
  493. delay: 5,
  494. })
  495. .to(amColor, {
  496. r: 133 / 255,
  497. g: 193 / 255,
  498. b: 255 / 255,
  499. duration: 2,
  500. onUpdate: () =>
  501. ambientLight.color.setRGB(amColor.r, amColor.g, amColor.b),
  502. })
  503. .to(RingGroup.position, {
  504. duration: 1,
  505. ease: "ease.inOut",
  506. delay: 5,
  507. y: 0,
  508. })
  509. .to(amColor, {
  510. r: 1,
  511. g: 1,
  512. b: 1,
  513. duration: 3,
  514. onUpdate: () =>
  515. ambientLight.color.setRGB(amColor.r, amColor.g, amColor.b),
  516. });
  517. }
  518.  
  519. // rotate
  520. RingGroup.rotateX(-Math.PI / 2);
  521. return RingGroup;
  522. };
  523.  
  524. //taiji
  525. const createTaiji = (position, scale) => {
  526. const taiji = new THREE.Group();
  527. const createCircle = (r, color, thetaStart, thetaLength) => {
  528. const material = new THREE.MeshBasicMaterial({
  529. color: color,
  530. side: THREE.DoubleSide,
  531. });
  532. const geometry = new THREE.CircleGeometry(r, 64, thetaStart, thetaLength);
  533. const circle = new THREE.Mesh(geometry, material);
  534. return circle;
  535. };
  536.  
  537. const ying = createCircle(1.8, 0x000000, 0, Math.PI);
  538. const yang = createCircle(1.8, 0xffffff, Math.PI, Math.PI);
  539. const Lblack = createCircle(0.9, 0x000000, 0, Math.PI * 2);
  540. const Lwhite = createCircle(0.9, 0xffffff, 0, Math.PI * 2);
  541. const Sblack = createCircle(0.25, 0x000000, 0, Math.PI * 2);
  542. const Swhite = createCircle(0.25, 0xffffff, 0, Math.PI * 2);
  543.  
  544. const Lblack1 = createCircle(0.9, 0x000000, 0, Math.PI * 2);
  545. const Lwhite1 = createCircle(0.9, 0xffffff, 0, Math.PI * 2);
  546. const Sblack1 = createCircle(0.25, 0x000000, 0, Math.PI * 2);
  547. const Swhite1 = createCircle(0.25, 0xffffff, 0, Math.PI * 2);
  548.  
  549. Lblack.position.set(-0.9, 0, 0.001);
  550. Lwhite.position.set(0.9, 0, 0.001);
  551. Swhite.position.set(-0.9, 0, 0.002);
  552. Sblack.position.set(0.9, 0, 0.002);
  553. Lblack1.position.set(-0.9, 0, -0.001);
  554. Lwhite1.position.set(0.9, 0, -0.001);
  555. Swhite1.position.set(-0.9, 0, -0.002);
  556. Sblack1.position.set(0.9, 0, -0.002);
  557.  
  558. taiji.add(
  559. ying,
  560. yang,
  561. Lblack,
  562. Lwhite,
  563. Swhite,
  564. Sblack,
  565. Lblack1,
  566. Lwhite1,
  567. Swhite1,
  568. Sblack1
  569. );
  570. gsap.to(taiji.rotation, {
  571. duration: 30,
  572. z: Math.PI * 2,
  573. repeat: -1,
  574. ease: "none",
  575. });
  576. taiji.rotateX(-Math.PI / 2);
  577. taiji.position.set(...position);
  578. taiji.scale.set(...scale);
  579. return taiji;
  580. };
  581. scene.add(createTaiji([0, 0, 0], [1, 1, 1]));
  582.  
  583. // bagua
  584. const createBagua = (data, x, y, z, deg, color) => {
  585. const idx = [-0.32, 0, 0.32];
  586. const bagua = new THREE.Group();
  587. const material = new THREE.MeshStandardMaterial({
  588. color: color,
  589. side: THREE.DoubleSide,
  590. });
  591. data.forEach((i, j) => {
  592. if (i == 1) {
  593. const yang = new THREE.Mesh(new THREE.PlaneGeometry(1, 0.2), material);
  594. yang.position.set(0, idx[j], 0);
  595. bagua.add(yang);
  596. }
  597. if (i == 0) {
  598. const ying1 = new THREE.Mesh(
  599. new THREE.PlaneGeometry(0.45, 0.2),
  600. material
  601. );
  602. const ying2 = new THREE.Mesh(
  603. new THREE.PlaneGeometry(0.45, 0.2),
  604. material
  605. );
  606. ying1.position.set(-0.275, idx[j], 0);
  607. ying2.position.set(0.275, idx[j], 0);
  608. bagua.add(ying1, ying2);
  609. }
  610. });
  611. bagua.position.set(x, y, z);
  612. bagua.rotation.set(0, 0, deg);
  613. return bagua;
  614. };
  615.  
  616. const showVideo = () => {
  617. const videoSrc = [
  618. "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/yAC65vN6.mp4",
  619. "https://xtjj-1253239320.cos.ap-shanghai.myqcloud.com/6Z5VZdZM.mp4",
  620. ];
  621. video.src = videoSrc[Math.floor(Math.random() * 2)];
  622. video.crossOrigin = "anonymous";
  623. const texture = new THREE.VideoTexture(video);
  624. const videoGroup = new THREE.Group();
  625. for (let i = 0; i < 8; i++) {
  626. const r = 25;
  627. const rad = ((2 * Math.PI) / 8) * i;
  628. const x = Math.cos(rad) * r;
  629. const y = Math.sin(rad) * r;
  630. const planeGeo = new THREE.PlaneGeometry(16, 9);
  631. const material = new THREE.MeshBasicMaterial({
  632. color: 0xffffff,
  633. side: THREE.DoubleSide,
  634. map: texture,
  635. });
  636. const plane = new THREE.Mesh(planeGeo, material);
  637.  
  638. plane.position.set(x, 4.5, y);
  639. if (i % 2 == 0) plane.rotation.set(0, rad + Math.PI / 2, 0);
  640. else plane.rotation.set(0, rad, 0);
  641. videoGroup.add(plane);
  642. }
  643. gsap.to(videoGroup.rotation, {
  644. duration: 30,
  645. y: -Math.PI * 2,
  646. repeat: -1,
  647. ease: "none",
  648. });
  649. scene.add(videoGroup);
  650. };
  651.  
  652. //loadFont, Rings
  653. loadFont.then(() => {
  654. data.forEach((item) => {
  655. Rings.push(Ring(item));
  656. });
  657. btn.innerText = "入 局";
  658. btn.style.opacity = 1;
  659. btn.style.cursor = "pointer";
  660. });
  661.  
  662. //start
  663. const start = function () {
  664. const showRing = (item) => {
  665. scene.add(item);
  666. item.scale.set(1.2, 1.2, 1.2);
  667. gsap.to(item.scale, {
  668. duration: 0.8,
  669. x: 1,
  670. y: 1,
  671. repeat: 0,
  672. ease: "easeInOut",
  673. });
  674. };
  675. const tl = gsap.timeline();
  676. Rings.forEach((item, idx) => {
  677. tl.to(".webgl", { duration: duration[idx] }).call(() => {
  678. showRing(item);
  679. });
  680. });
  681. };
  682.  
  683. btn.addEventListener("click", () => {
  684. box.style.display = "none";
  685. start();
  686. showVideo();
  687. video.play();
  688. video.loop = true;
  689. });
  690.  
  691. //----------------------
  692.  
  693. //Light
  694. const ambientLight = new THREE.AmbientLight(0xffffff, 1);
  695. scene.add(ambientLight);
  696.  
  697. //Sizes
  698. const sizes = {
  699. width: window.innerWidth,
  700. height: window.innerHeight,
  701. };
  702.  
  703. // Camera
  704. const camera = new THREE.PerspectiveCamera(
  705. 75,
  706. sizes.width / sizes.height,
  707. 1,
  708. 1000
  709. );
  710. camera.position.y = 10;
  711. camera.position.x = 10;
  712. camera.position.z = 10;
  713. camera.lookAt(scene.position);
  714. scene.add(camera);
  715.  
  716. //Renderer
  717. const renderer = new THREE.WebGLRenderer({
  718. canvas: canvas,
  719. antialias: true,
  720. alpha: true,
  721. });
  722.  
  723. renderer.setSize(sizes.width, sizes.height);
  724.  
  725. //controls
  726. const controls = new OrbitControls(camera, canvas);
  727. controls.enableDamping = true;
  728. controls.maxDistance = 50;
  729. controls.enablePan = false;
  730.  
  731. const tick = () => {
  732. renderer.render(scene, camera);
  733. controls.update();
  734. window.requestAnimationFrame(tick);
  735. };
  736. tick();
  737.  
  738. window.addEventListener("resize", () => {
  739. sizes.height = window.innerHeight;
  740. sizes.width = window.innerWidth;
  741.  
  742. camera.aspect = sizes.width / sizes.height;
  743. camera.updateProjectionMatrix();
  744.  
  745. renderer.setSize(sizes.width, sizes.height);
  746. renderer.setPixelRatio(window.devicePixelRatio);
  747. });

本文转载于:

https://juejin.cn/post/7220629398965108794

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

记录--ThreeJs手搓一个罗盘特效的更多相关文章

  1. 手搓一个“七夕限定”,用3D Engine 5分钟实现烟花绽放效果

    七夕来咯!又到了给重要的人送惊喜的时刻. 今年,除了将心意融入花和礼物,作为程序员,用自己的代码本事手搓一个技术感十足"七夕限定"惊喜,我觉得,这是不亚于车马慢时代手写信的古典主义 ...

  2. 手搓一个兔子问题(分享一个C语言问题,持续更新...)

    大家好,我是小七夜,今天就不分享C语言的基础知识了,分享一个比较好玩的C语言经典例题:兔子问题 题目是这样的:说有一个穷苦人这天捉到了一只公兔子,为了能繁衍后代他又买了一只母兔子,后来兔子开始生小兔子 ...

  3. 手搓一个C语言简单计算器。

    #include <stdio.h> void xing(int shu); void biaoti(int kong,char * title); void zhuyemian(char ...

  4. 放弃antd table,基于React手写一个虚拟滚动的表格

    缘起 标题有点夸张,并不是完全放弃antd-table,毕竟在react的生态圈里,对国人来说,比较好用的PC端组件库,也就antd了.即便经历了2018年圣诞彩蛋事件,antd的使用者也不仅不减,反 ...

  5. 使用Java Socket手撸一个http服务器

    原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...

  6. 手撸一个SpringBoot-Starter

    1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...

  7. 手写一个线程池,带你学习ThreadPoolExecutor线程池实现原理

    摘要:从手写线程池开始,逐步的分析这些代码在Java的线程池中是如何实现的. 本文分享自华为云社区<手写线程池,对照学习ThreadPoolExecutor线程池实现原理!>,作者:小傅哥 ...

  8. Golang:手撸一个支持六种级别的日志库

    Golang标准日志库提供的日志输出方法有Print.Fatal.Panic等,没有常见的Debug.Info.Error等日志级别,用起来不太顺手.这篇文章就来手撸一个自己的日志库,可以记录不同级别 ...

  9. CozyRSS开发记录9-快速实现一个RSS解析器

    CozyRSS开发记录9-快速实现一个RSS解析器 1.再读RSS标准 既然需要自己实现一个RSS解析器,那自然需要仔细的读一读RSS的标准文档.在网上随便找了两份,一份英文一份中文: http:// ...

  10. 『练手』手写一个独立Json算法 JsonHelper

    背景: > 一直使用 Newtonsoft.Json.dll 也算挺稳定的. > 但这个框架也挺闹心的: > 1.影响编译失败:https://www.cnblogs.com/zih ...

随机推荐

  1. Power BI 7 DAY

    DAX 表达式(Data Analysis Expressions) DAX表达式的结果应用在数据透视表中 DAX表达式的结果作用于整列或者表中所有行 还需注意以下几点: a. 表名用"'' ...

  2. RocketMQ—RocketMQ消费重试和死信消息

    RocketMQ-RocketMQ消费重试和死信消息 消费重试 生产者重试 设置重试的代码如下 // 失败的情况重发3次 producer.setRetryTimesWhenSendFailed(3) ...

  3. NC14522 珂朵莉的数列

    题目链接 题目 题目描述 珂朵莉给了你一个序列,有 \(\frac{n\times(n+1)}2\) 个子区间,求出她们各自的逆序对个数,然后加起来输出 输入描述 第一行一个数 n 表示这个序列 a ...

  4. 基于 junit5 实现 junitperf 源码分析

    前言 上一节介绍了基于 junit4 实现 junitperf,但是可以发现定义变量的方式依然不够优雅. 那可以让用户使用起来更加自然一些吗? 有的,junit5 为我们带来了更加强大的功能. 拓展阅 ...

  5. 【Unity3D】缩放、平移、旋转场景

    1 前言 ​ 场景缩放.平移.旋转有两种实现方案,一种是对场景中所有物体进行同步变换,另一种方案是对相机的位置和姿态进行变换. ​ 对于方案一,如果所有物体都在同一个根对象下(其子对象或孙子对象),那 ...

  6. Vue+SpringBoot+ElementUI实战学生管理系统-5.用户管理模块

    1.章节介绍 前一篇介绍了项目的API接口设计,这一篇编写用户管理模块,需要的朋友可以拿去自己定制.:) 2.获取源码 源码是捐赠方式获取,详细请QQ联系我 :)! 3.项目截图 列表操作 动态图 4 ...

  7. cf的几道小题

    1.E - Fridge 教训:做题的时候,没有想清楚问题,把问题复杂化了 #include <bits/stdc++.h> #define int long long using nam ...

  8. vmware之NAT模式配置

    ​ 题外话之前的题外话,本文迁移自别的社区,三年前大学实习时写下本文,过了几年再回过头来看,虽然讲得浅显,作为入门笔记也勉强合格. ---------------------------------- ...

  9. ASCII编码的影响与作用:数字化时代的不可或缺之物

    一.ASCII编码的起源 ASCII(American Standard Code for Information Interchange)编码是一种最早用于将字符转换为数字的编码系统.它诞生于20世 ...

  10. OsgEarth开发笔记(四):Qt5.15.2在QtCreator集成Osg3.6.3+OsgEarth3.1+OsgQt的vs2019x64版本开发环境搭建

    前言   本篇非常麻烦,博主用QtCreator作为IDE,因为Osg3.6.3放弃对osgQt的支持,集成起来比较繁琐.   前提   基于前面三篇的基础上,才可以进行本篇.   Demo演示:Qt ...