這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
先上效果
前言
最近在學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
標籤:其他
上一篇:配置式表單渲染器的實作
下一篇:返回列表

