记录--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
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。
记录--ThreeJs手搓一个罗盘特效的更多相关文章
- 手搓一个“七夕限定”,用3D Engine 5分钟实现烟花绽放效果
七夕来咯!又到了给重要的人送惊喜的时刻. 今年,除了将心意融入花和礼物,作为程序员,用自己的代码本事手搓一个技术感十足"七夕限定"惊喜,我觉得,这是不亚于车马慢时代手写信的古典主义 ...
- 手搓一个兔子问题(分享一个C语言问题,持续更新...)
大家好,我是小七夜,今天就不分享C语言的基础知识了,分享一个比较好玩的C语言经典例题:兔子问题 题目是这样的:说有一个穷苦人这天捉到了一只公兔子,为了能繁衍后代他又买了一只母兔子,后来兔子开始生小兔子 ...
- 手搓一个C语言简单计算器。
#include <stdio.h> void xing(int shu); void biaoti(int kong,char * title); void zhuyemian(char ...
- 放弃antd table,基于React手写一个虚拟滚动的表格
缘起 标题有点夸张,并不是完全放弃antd-table,毕竟在react的生态圈里,对国人来说,比较好用的PC端组件库,也就antd了.即便经历了2018年圣诞彩蛋事件,antd的使用者也不仅不减,反 ...
- 使用Java Socket手撸一个http服务器
原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...
- 手撸一个SpringBoot-Starter
1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...
- 手写一个线程池,带你学习ThreadPoolExecutor线程池实现原理
摘要:从手写线程池开始,逐步的分析这些代码在Java的线程池中是如何实现的. 本文分享自华为云社区<手写线程池,对照学习ThreadPoolExecutor线程池实现原理!>,作者:小傅哥 ...
- Golang:手撸一个支持六种级别的日志库
Golang标准日志库提供的日志输出方法有Print.Fatal.Panic等,没有常见的Debug.Info.Error等日志级别,用起来不太顺手.这篇文章就来手撸一个自己的日志库,可以记录不同级别 ...
- CozyRSS开发记录9-快速实现一个RSS解析器
CozyRSS开发记录9-快速实现一个RSS解析器 1.再读RSS标准 既然需要自己实现一个RSS解析器,那自然需要仔细的读一读RSS的标准文档.在网上随便找了两份,一份英文一份中文: http:// ...
- 『练手』手写一个独立Json算法 JsonHelper
背景: > 一直使用 Newtonsoft.Json.dll 也算挺稳定的. > 但这个框架也挺闹心的: > 1.影响编译失败:https://www.cnblogs.com/zih ...
随机推荐
- Power BI 7 DAY
DAX 表达式(Data Analysis Expressions) DAX表达式的结果应用在数据透视表中 DAX表达式的结果作用于整列或者表中所有行 还需注意以下几点: a. 表名用"'' ...
- RocketMQ—RocketMQ消费重试和死信消息
RocketMQ-RocketMQ消费重试和死信消息 消费重试 生产者重试 设置重试的代码如下 // 失败的情况重发3次 producer.setRetryTimesWhenSendFailed(3) ...
- NC14522 珂朵莉的数列
题目链接 题目 题目描述 珂朵莉给了你一个序列,有 \(\frac{n\times(n+1)}2\) 个子区间,求出她们各自的逆序对个数,然后加起来输出 输入描述 第一行一个数 n 表示这个序列 a ...
- 基于 junit5 实现 junitperf 源码分析
前言 上一节介绍了基于 junit4 实现 junitperf,但是可以发现定义变量的方式依然不够优雅. 那可以让用户使用起来更加自然一些吗? 有的,junit5 为我们带来了更加强大的功能. 拓展阅 ...
- 【Unity3D】缩放、平移、旋转场景
1 前言 场景缩放.平移.旋转有两种实现方案,一种是对场景中所有物体进行同步变换,另一种方案是对相机的位置和姿态进行变换. 对于方案一,如果所有物体都在同一个根对象下(其子对象或孙子对象),那 ...
- Vue+SpringBoot+ElementUI实战学生管理系统-5.用户管理模块
1.章节介绍 前一篇介绍了项目的API接口设计,这一篇编写用户管理模块,需要的朋友可以拿去自己定制.:) 2.获取源码 源码是捐赠方式获取,详细请QQ联系我 :)! 3.项目截图 列表操作 动态图 4 ...
- cf的几道小题
1.E - Fridge 教训:做题的时候,没有想清楚问题,把问题复杂化了 #include <bits/stdc++.h> #define int long long using nam ...
- vmware之NAT模式配置
题外话之前的题外话,本文迁移自别的社区,三年前大学实习时写下本文,过了几年再回过头来看,虽然讲得浅显,作为入门笔记也勉强合格. ---------------------------------- ...
- ASCII编码的影响与作用:数字化时代的不可或缺之物
一.ASCII编码的起源 ASCII(American Standard Code for Information Interchange)编码是一种最早用于将字符转换为数字的编码系统.它诞生于20世 ...
- OsgEarth开发笔记(四):Qt5.15.2在QtCreator集成Osg3.6.3+OsgEarth3.1+OsgQt的vs2019x64版本开发环境搭建
前言 本篇非常麻烦,博主用QtCreator作为IDE,因为Osg3.6.3放弃对osgQt的支持,集成起来比较繁琐. 前提 基于前面三篇的基础上,才可以进行本篇. Demo演示:Qt ...