three.js響應(yīng)式設(shè)計(jì)實(shí)例詳解
1-canvas 的響應(yīng)式布局
canvas 畫(huà)布的尺寸有兩種:
- 像素尺寸,即canvas畫(huà)布在高度和寬度上有多少個(gè)像素,默認(rèn)是300*150
- css 尺寸,即css 里的width和height
在web前端,dom元素的響應(yīng)式布局一般是通過(guò)css 實(shí)現(xiàn)的。
而canvas 則并非如此,canvas 的響應(yīng)式布局需要考慮其像素尺寸。
接下來(lái),咱們就通過(guò)讓canvas 畫(huà)布自適應(yīng)瀏覽器的窗口的尺寸,來(lái)說(shuō)一下canvas 的響應(yīng)式布局。
1.將之前的RenderStructure.tsx 復(fù)制粘貼一份,改名ResponsiveDesign.tsx,用于寫(xiě)響應(yīng)式布局。
2.將ResponsiveDesign.tsx 頁(yè)面添加到路由中。
- src/app.tsx
import React from "react";
import { useRoutes } from "react-router-dom";
import "./App.css";
import MainLayout from "./view/MainLayout";
import Fundamentals from "./view/Fundamentals";
import ResponsiveDesign from "./view/ResponsiveDesign";
const App: React.FC = (): JSX.Element => {
const routing = useRoutes([
{
path: "/",
element: <MainLayout />,
},
{
path: "Fundamentals",
element: <Fundamentals />,
},
{
path: "ResponsiveDesign",
element: <ResponsiveDesign />,
},
]);
return <>{routing}</>;
};
export default App;3.在ResponsiveDesign.tsx中先取消renderer 的尺寸設(shè)置。
//renderer.setSize(innerWidth, innerHeight);
4.用css 設(shè)置canvas 畫(huà)布及其父元素的尺寸,使其充滿(mǎn)窗口。
- src/view/ResponsiveDesign
const ResponsiveDesign: React.FC = (): JSX.Element => {
……
return <div ref={divRef} className="canvasWrapper"></div>;
};- src/view/fullScreen.css
html {
height: 100%;
}
body {
margin: 0;
overflow: hidden;
height: 100%;
}
#root,.canvasWrapper,canvas{
width: 100%;
height: 100%;
}3.將fullScreen.css 導(dǎo)入ResponsiveDesign.tsx
import "./fullScreen.css";
效果如下:

由上圖可見(jiàn),立方體的邊界出現(xiàn)了鋸齒,這就是位圖被css拉伸后失真導(dǎo)致的,默認(rèn)canvas 畫(huà)布的尺寸只有300*150。
因此,我們需要用canvas 畫(huà)布的像素尺寸自適應(yīng)窗口。
4.建立一個(gè)讓canvas 像素尺寸隨css 尺寸同步更新的方法。
resizeRendererToDisplaySize(renderer);
// 將渲染尺寸設(shè)置為其顯示的尺寸,返回畫(huà)布像素尺寸是否等于其顯示(css)尺寸的布爾值
function resizeRendererToDisplaySize(renderer) {
const { width, height, clientWidth, clientHeight } = renderer.domElement;
const needResize = width !== clientWidth || height !== clientHeight;
if (needResize) {
renderer.setSize(clientWidth, clientHeight, false);
}
return needResize;
}- renderer.setSize(w,h,bool) 是重置渲染尺寸的方法,在此方法里會(huì)根據(jù)w,h參數(shù)重置canvas 畫(huà)布的尺寸。
this.setSize = function ( width, height, updateStyle ) {
if ( xr.isPresenting ) {
console.warn( 'THREE.WebGLRenderer: Can't change size while VR device is presenting.' );
return;
}
_width = width;
_height = height;
_canvas.width = Math.floor( width * _pixelRatio );
_canvas.height = Math.floor( height * _pixelRatio );
if ( updateStyle !== false ) {
_canvas.style.width = width + 'px';
_canvas.style.height = height + 'px';
}
this.setViewport( 0, 0, width, height );
};setSize() 方法中的bool 參數(shù)很重要,會(huì)用于判斷是否設(shè)置canvas 畫(huà)布的css 尺寸。
5.當(dāng)canvas 畫(huà)布的尺寸變化了,相機(jī)視口的寬高比也需要同步調(diào)整。這樣我們拖拽瀏覽器的邊界,縮放瀏覽器的時(shí)候,就可以看到canvas 畫(huà)布自適應(yīng)瀏覽器的尺寸了。
function animate() {
requestAnimationFrame(animate);
if (resizeRendererToDisplaySize(renderer)) {
const { clientWidth, clientHeight } = renderer.domElement;
camera.aspect = clientWidth / clientHeight;
camera.updateProjectionMatrix();
}
cubes.forEach((cube) => {
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
});
renderer.render(scene, camera);
}- camera.aspect 屬性是相機(jī)視口的寬高比
- 我們?cè)赪ebGL 里說(shuō)透視投影矩陣的時(shí)候說(shuō)過(guò),當(dāng)相機(jī)視口的寬高比變了,相機(jī)的透視投影矩陣也會(huì)隨之改變,因此我們需要使用camera.updateProjectionMatrix() 方法更新透視投影矩陣。
- 至于我們?yōu)槭裁床话迅孪鄼C(jī)視口寬高比的方法一起放進(jìn)resizeRendererToDisplaySize()里,這是為了降低resizeRendererToDisplaySize() 方法和相機(jī)的耦合度。具體要不要這么做視項(xiàng)目需求而定。
接下來(lái)咱們可以舉個(gè)例子,說(shuō)一下canvas 畫(huà)布響應(yīng)式布局的應(yīng)用場(chǎng)合。
示例:三維插圖
在下面的例子里,我們會(huì)給三維插圖一個(gè)縮放功能,從而更好的觀(guān)察細(xì)節(jié)。

1.新建一個(gè)Illustration 頁(yè)。
- src/view/Illustration.tsx
import React, { useRef, useEffect, useState } from "react";
import { BoxGeometry, DirectionalLight, Mesh, MeshPhongMaterial, PerspectiveCamera, Scene, WebGLRenderer } from "three";
import "./Illustration.css";
const { innerWidth, innerHeight } = window;
const scene = new Scene();
const camera = new PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 1000);
const renderer = new WebGLRenderer();
// renderer.setSize(innerWidth, innerHeight, false);
// 光源
const color = 0xffffff;
const intensity = 1;
const light = new DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
const geometry = new BoxGeometry();
const material = new MeshPhongMaterial({ color: 0x44aa88 });
camera.position.z = 5;
const cubes = [-2, 0, 2].map((num) => makeInstance(num));
scene.add(...cubes);
// 將渲染尺寸設(shè)置為其顯示的尺寸,返回畫(huà)布像素尺寸是否等于其顯示(css)尺寸的布爾值
function resizeRendererToDisplaySize(renderer: WebGLRenderer) {
const { width, height, clientWidth, clientHeight } = renderer.domElement;
const needResize = width !== clientWidth || height !== clientHeight;
if (needResize) {
renderer.setSize(clientWidth, clientHeight, false);
}
return needResize;
}
function makeInstance(x: number) {
const cube = new Mesh(geometry, material);
cube.position.x = x;
return cube;
}
function animate() {
requestAnimationFrame(animate);
if (resizeRendererToDisplaySize(renderer)) {
const { clientWidth, clientHeight } = renderer.domElement;
camera.aspect = clientWidth / clientHeight;
camera.updateProjectionMatrix();
}
cubes.forEach((cube) => {
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
});
renderer.render(scene, camera);
}
const Illustration: React.FC = (): JSX.Element => {
const divRef = useRef<HTMLDivElement>(null);
let [btnState, setBtnState] = useState(["small", "放大"]);
const toggle = () => {
if (btnState[0] === "small") {
setBtnState(["big", "縮小"]);
} else {
setBtnState(["small", "放大"]);
}
};
useEffect(() => {
const { current } = divRef;
if (current) {
current.innerHTML = "";
current.append(renderer.domElement);
}
animate();
}, []);
return (
<div className="cont">
<p>
立方體,也稱(chēng)正方體,是由6個(gè)正方形面組成的正多面體,故又稱(chēng)正六面體。它有12條邊和8個(gè)頂點(diǎn)。其中正方體是特殊的長(zhǎng)方體。立方體是一種特殊的正四棱柱、長(zhǎng)方體、三角偏方面體、菱形多面體、平行六面體,就如同正方形是特殊的矩形、菱形、平行四邊形一様。立方體具有正八面體對(duì)稱(chēng)性,即考克斯特BC3對(duì)稱(chēng)性,施萊夫利符號(hào)
,考克斯特-迪肯符號(hào),與正八面體對(duì)偶。
</p>
<div className="inllustration">
<div ref={divRef} className={`canvasWrapper ${btnState[0]}`}></div>
<button className="btn" onClick={toggle}>
{btnState[1]}
</button>
</div>
<p>
立方體有11種不同的展開(kāi)圖,即是說(shuō),我們可以有11種不同的方法切開(kāi)空心立方體的7條棱而將其展平為平面圖形,見(jiàn)圖1。 [2] 立方體的11種不同展開(kāi)圖。
如果我們要將立方體涂色而使相鄰的面不帶有相同的顏色,則我們至少需要3種顏色(類(lèi)似于四色問(wèn)題)。
立方體是唯一能夠獨(dú)立密鋪三維歐幾里得空間的柏拉圖正多面體,因此立方體堆砌也是四維唯一的正堆砌(三維空間中的堆砌拓?fù)渖系葍r(jià)于四維多胞體)。它又是柏拉圖立體中唯一一個(gè)有偶數(shù)邊面——正方形面的,因此,它是柏拉圖立體中獨(dú)一無(wú)二的環(huán)帶多面體(它所有相對(duì)的面關(guān)于立方體中心中心對(duì)稱(chēng))。
將立方體沿對(duì)角線(xiàn)切開(kāi),能得到6個(gè)全等的正4棱柱(但它不是半正的,底面棱長(zhǎng)與側(cè)棱長(zhǎng)之比為2:√3)將其正方形面貼到原來(lái)的立方體上,能得到菱形十二面體(Rhombic
Dodecahedron)(兩兩共面三角形合成一個(gè)菱形)。
</p>
<p>
立方體的對(duì)偶多面體是正八面體。 當(dāng)正八面體在立方體之內(nèi): 正八面體體積: 立方體體積=[(1/3)×高×底面積]×2: 邊=(1/3)(n/2)[(n)/2]2: n=1: 6 星形八面體的對(duì)角線(xiàn)可組成一個(gè)立方體。
截半立方體:從一條棱斬去另一條棱的中點(diǎn)得出 截角立方體
超正方體:立方體在高維度的推廣。更加一般的,立方體是一個(gè)大家族,即立方形家族(又稱(chēng)超方形、正測(cè)形)的3維成員,它們都具有相似的性質(zhì)(如二面角都是90°、有類(lèi)似的超體積公式,即Vn-cube=a等)。
長(zhǎng)方體、偏方面體的特例。
</p>
<p>
立方體是唯一能夠獨(dú)立密鋪三維歐幾里得空間的柏拉圖正多面體,因此立方體堆砌也是四維唯一的正堆砌(三維空間中的堆砌拓?fù)渖系葍r(jià)于四維多胞體)。它又是柏拉圖立體中唯一一個(gè)有偶數(shù)邊面——正方形面的,因此,它是柏拉圖立體中獨(dú)一無(wú)二的環(huán)帶多面體(它所有相對(duì)的面關(guān)于立方體中心中心對(duì)稱(chēng))。
將立方體沿對(duì)角線(xiàn)切開(kāi),能得到6個(gè)全等的正4棱柱(但它不是半正的,底面棱長(zhǎng)與側(cè)棱長(zhǎng)之比為2:√3)將其正方形面貼到原來(lái)的立方體上,能得到菱形十二面體(Rhombic
Dodecahedron)(兩兩共面三角形合成一個(gè)菱形)。
</p>
</div>
);
};
export default Illustration;2.設(shè)置css 樣式
- src/view/Illustration.css
p {
text-indent: 2em;
line-height: 24px;
font-size: 14px;
}
.cont {
width: 80%;
max-width: 900px;
margin: auto;
}
.inllustration{
position: relative;
float: left;
}
.canvasWrapper {
margin-right: 15px;
transition-property: width, height;
transition-duration: 1s, 1s;
}
.small {
width: 150px;
height: 150px;
}
.big {
width: 100%;
height: 100%;
}
.canvasWrapper canvas {
width: 100%;
height: 100%;
}
.btn {
position: absolute;
top: 0;
left: 0;
cursor: pointer;
}3.在App.tsx 中,基于Illustration頁(yè)新建一個(gè)路由
- src/App.tsx
import React from "react";
import { useRoutes } from "react-router-dom";
import Basics from "./view/Basics";
import RenderStructure from "./view/RenderStructure";
import ResponsiveDesign from "./view/ResponsiveDesign";
import Illustration from "./view/Illustration";
const App: React.FC = (): JSX.Element => {
const routing = useRoutes([
……
{
path: "Illustration",
element: <Illustration />,
},
]);
return <>{routing}</>;
};
export default App;4.在首頁(yè)Basics.tsx中再開(kāi)一個(gè)鏈接
import React from "react";
import { Link } from "react-router-dom";
const Basics: React.FC = (): JSX.Element => {
return (
<nav style={{ width: "60%", margin: "auto" }}>
<h2>three.js 基礎(chǔ)示例</h2>
<ul>
……
<li>
<Link to="/Illustration">Illustration 三維插圖</Link>
</li>
</ul>
</nav>
);
};
export default Basics;接下來(lái),在首頁(yè)點(diǎn)擊Illustration 鏈接,就可以看到效果。
2-自適應(yīng)設(shè)備分辨率
當(dāng)今大多數(shù)的PC端和移動(dòng)端顯示器都是HD-DPI顯示器。
HD-DPI 是High Definition-Dots Per Inch 的簡(jiǎn)稱(chēng),意思是高分辨率顯示器。
不同設(shè)備的顯示器的分辨率是不一樣的。
做過(guò)測(cè)試的會(huì)知道,我們需要用不同的設(shè)備測(cè)試項(xiàng)目,比如下面微信小程序開(kāi)發(fā)者工具里提供的機(jī)型。

以上圖中的iPhone6/7/8 為例:
- 375667 代表的手機(jī)的屏幕的物理尺寸,如果我們?cè)谄渲薪⒁粋€(gè)100% 充滿(mǎn)屏幕的,那其尺寸就是375667。
- Dpr 代表像素密度,2 表示手機(jī)屏幕在寬度上有3752 個(gè)像素,在高度上有6672 個(gè)像素,因此iPhone6/7/8 的屏幕的像素尺寸就是750*1334。
當(dāng)我們?cè)谶@種像素尺寸大于物理尺寸的高分辨率顯示器里繪圖的時(shí)候,就需要考慮一個(gè)問(wèn)題。
若我們直接在iPhone6/7/8 里建立一個(gè)充滿(mǎn)屏幕的canvas,那其像素尺寸就是375*667。
這個(gè)尺寸并沒(méi)發(fā)揮高分辨率顯示器的優(yōu)勢(shì),我們需要先將其像素尺寸設(shè)置為7501334,然后再將其css 尺寸設(shè)置為375667。
這樣,就可以讓canvas畫(huà)布以高分辨率的姿態(tài)顯示在顯示器里。
代碼示例:
function resizeRendererToDisplaySize(renderer: WebGLRenderer) {
const { width, height, clientWidth, clientHeight } = renderer.domElement;
const [w, h] = [clientWidth * devicePixelRatio, clientHeight * devicePixelRatio];
const needResize = width !== w || height !== h;
if (needResize) {
renderer.setSize(w, h, false);
}
return needResize;
}上面的devicePixelRatio 就是設(shè)備像素密度,是window下的屬性,即window.devicePixelRatio。
其實(shí),有的時(shí)候若不刻意觀(guān)察,canvas 有沒(méi)有自適應(yīng)設(shè)備分辨率是很難看出的。
因此,若是對(duì)畫(huà)面的渲染質(zhì)量要求不高,可以什么都不做,這樣也能避免canvas 畫(huà)布像素尺寸變大后降低渲染效率的問(wèn)題。
關(guān)于響應(yīng)式設(shè)計(jì)咱們就說(shuō)到這,接下來(lái)咱們說(shuō)一下three.js 里的內(nèi)置幾何體。
總結(jié)
到此這篇關(guān)于three.js響應(yīng)式設(shè)計(jì)的文章就介紹到這了,更多相關(guān)three.js響應(yīng)式設(shè)計(jì)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
20分鐘輕松創(chuàng)建自己的Bootstrap站點(diǎn)
這篇文章主要教大家如何在短短的20分鐘內(nèi)輕松創(chuàng)建自己的Bootstrap站點(diǎn),學(xué)會(huì)使用twitter bootstrap建立一個(gè)站點(diǎn),從而鞏固Bootstrap一系列基礎(chǔ)知識(shí),感興趣的小伙伴們可以參考一下2016-05-05
json_decode 索引為數(shù)字時(shí)自動(dòng)排序問(wèn)題解決方法
這篇文章主要介紹了使用son_encode 給前端返回?cái)?shù)據(jù),結(jié)果順序不對(duì),經(jīng)debug調(diào)試,發(fā)現(xiàn)是json_encode 函數(shù)的問(wèn)題,變成 " " + 數(shù)字即可,需要的朋友可以參考下2020-03-03
將CKfinder整合進(jìn)CKEditor3.0的新方法
最新發(fā)布的CKFinder 1.4版 已經(jīng)提供了對(duì)CKEditor3.0的支持2010-01-01
Webpack框架核心概念(知識(shí)點(diǎn)整理)
webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的模塊打包器(module bundler)。這篇文章主要介紹了Webpack框架核心概念(知識(shí)點(diǎn)整理),需要的朋友可以參考下2017-12-12
JS中LocalStorage與SessionStorage五種循序漸進(jìn)的使用方法
這篇文章主要介紹了JS中LocalStorage與SessionStorage五種循序漸進(jìn)的使用方法,需要的朋友可以參考下2017-07-07
一文詳解Promise.race()方法功能及應(yīng)用場(chǎng)景
這篇文章主要為大家介紹了Promise.race()方法功能及應(yīng)用場(chǎng)景詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
TypeScript中Map對(duì)象使用及Map與字典的區(qū)別詳解
Map對(duì)象主要的應(yīng)用場(chǎng)景在于數(shù)據(jù)重組和數(shù)據(jù)儲(chǔ)存,下面這篇文章主要給大家介紹了TypeScript中Map對(duì)象使用及Map與字典的區(qū)別的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01

