three.js响应式设计实例详解

目录
  • 1-canvas 的响应式布局
    • 示例:三维插图
  • 2-自适应设备分辨率
  • 总结

源码地址:github.com/buglas/thre…

1-canvas 的响应式布局

canvas 画布的尺寸有两种:

  • 像素尺寸,即canvas画布在高度和宽度上有多少个像素,默认是300*150
  • css 尺寸,即css 里的width和height

在web前端,dom元素的响应式布局一般是通过css 实现的。

而canvas 则并非如此,canvas 的响应式布局需要考虑其像素尺寸。

接下来,咱们就通过让canvas 画布自适应浏览器的窗口的尺寸,来说一下canvas 的响应式布局。

1.将之前的RenderStructure.tsx 复制粘贴一份,改名ResponsiveDesign.tsx,用于写响应式布局。

2.将ResponsiveDesign.tsx 页面添加到路由中。

  • 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 的尺寸设置。

//renderer.setSize(innerWidth, innerHeight);

4.用css 设置canvas 画布及其父元素的尺寸,使其充满窗口。

  • 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 导入ResponsiveDesign.tsx

import "./fullScreen.css";

效果如下:

由上图可见,立方体的边界出现了锯齿,这就是位图被css拉伸后失真导致的,默认canvas 画布的尺寸只有300*150。

因此,我们需要用canvas 画布的像素尺寸自适应窗口。

4.建立一个让canvas 像素尺寸随css 尺寸同步更新的方法。

resizeRendererToDisplaySize(renderer);

// 将渲染尺寸设置为其显示的尺寸,返回画布像素尺寸是否等于其显示(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) 是重置渲染尺寸的方法,在此方法里会根据w,h参数重置canvas 画布的尺寸。
    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 参数很重要,会用于判断是否设置canvas 画布的css 尺寸。

5.当canvas 画布的尺寸变化了,相机视口的宽高比也需要同步调整。这样我们拖拽浏览器的边界,缩放浏览器的时候,就可以看到canvas 画布自适应浏览器的尺寸了。

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 属性是相机视口的宽高比
  • 我们在WebGL 里说透视投影矩阵的时候说过,当相机视口的宽高比变了,相机的透视投影矩阵也会随之改变,因此我们需要使用camera.updateProjectionMatrix() 方法更新透视投影矩阵。
  • 至于我们为什么不把更新相机视口宽高比的方法一起放进resizeRendererToDisplaySize()里,这是为了降低resizeRendererToDisplaySize() 方法和相机的耦合度。具体要不要这么做视项目需求而定。

接下来咱们可以举个例子,说一下canvas 画布响应式布局的应用场合。

示例:三维插图

在下面的例子里,我们会给三维插图一个缩放功能,从而更好的观察细节。

1.新建一个Illustration 页。

  • 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);

// 将渲染尺寸设置为其显示的尺寸,返回画布像素尺寸是否等于其显示(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>
        立方体,也称正方体,是由6个正方形面组成的正多面体,故又称正六面体。它有12条边和8个顶点。其中正方体是特殊的长方体。立方体是一种特殊的正四棱柱、长方体、三角偏方面体、菱形多面体、平行六面体,就如同正方形是特殊的矩形、菱形、平行四边形一様。立方体具有正八面体对称性,即考克斯特BC3对称性,施莱夫利符号
        ,考克斯特-迪肯符号,与正八面体对偶。
      </p>
      <div className="inllustration">
        <div ref={divRef} className={`canvasWrapper ${btnState[0]}`}></div>
        <button className="btn" onClick={toggle}>
          {btnState[1]}
        </button>
      </div>
      <p>
        立方体有11种不同的展开图,即是说,我们可以有11种不同的方法切开空心立方体的7条棱而将其展平为平面图形,见图1。 [2] 立方体的11种不同展开图。
        如果我们要将立方体涂色而使相邻的面不带有相同的颜色,则我们至少需要3种颜色(类似于四色问题)。
        立方体是唯一能够独立密铺三维欧几里得空间的柏拉图正多面体,因此立方体堆砌也是四维唯一的正堆砌(三维空间中的堆砌拓扑上等价于四维多胞体)。它又是柏拉图立体中唯一一个有偶数边面——正方形面的,因此,它是柏拉图立体中独一无二的环带多面体(它所有相对的面关于立方体中心中心对称)。
        将立方体沿对角线切开,能得到6个全等的正4棱柱(但它不是半正的,底面棱长与侧棱长之比为2:√3)将其正方形面贴到原来的立方体上,能得到菱形十二面体(Rhombic
        Dodecahedron)(两两共面三角形合成一个菱形)。
      </p>
      <p>
        立方体的对偶多面体是正八面体。 当正八面体在立方体之内: 正八面体体积: 立方体体积=[(1/3)×高×底面积]×2: 边=(1/3)(n/2)[(n)/2]2: n=1: 6 星形八面体的对角线可组成一个立方体。
        截半立方体:从一条棱斩去另一条棱的中点得出 截角立方体
        超正方体:立方体在高维度的推广。更加一般的,立方体是一个大家族,即立方形家族(又称超方形、正测形)的3维成员,它们都具有相似的性质(如二面角都是90°、有类似的超体积公式,即Vn-cube=a等)。
        长方体、偏方面体的特例。
      </p>
      <p>
        立方体是唯一能够独立密铺三维欧几里得空间的柏拉图正多面体,因此立方体堆砌也是四维唯一的正堆砌(三维空间中的堆砌拓扑上等价于四维多胞体)。它又是柏拉图立体中唯一一个有偶数边面——正方形面的,因此,它是柏拉图立体中独一无二的环带多面体(它所有相对的面关于立方体中心中心对称)。
        将立方体沿对角线切开,能得到6个全等的正4棱柱(但它不是半正的,底面棱长与侧棱长之比为2:√3)将其正方形面贴到原来的立方体上,能得到菱形十二面体(Rhombic
        Dodecahedron)(两两共面三角形合成一个菱形)。
      </p>
    </div>
  );
};

export default Illustration;

2.设置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页新建一个路由

  • 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.在首页Basics.tsx中再开一个链接

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 基础示例</h2>
      <ul>
        ……
        <li>
          <Link to="/Illustration">Illustration 三维插图</Link>
        </li>
      </ul>
    </nav>
  );
};

export default Basics;

接下来,在首页点击Illustration 链接,就可以看到效果。

2-自适应设备分辨率

当今大多数的PC端和移动端显示器都是HD-DPI显示器。

HD-DPI 是High Definition-Dots Per Inch 的简称,意思是高分辨率显示器。

不同设备的显示器的分辨率是不一样的。

做过测试的会知道,我们需要用不同的设备测试项目,比如下面微信小程序开发者工具里提供的机型。

以上图中的iPhone6/7/8 为例:

  • 375667 代表的手机的屏幕的物理尺寸,如果我们在其中建立一个100% 充满屏幕的,那其尺寸就是375667。
  • Dpr 代表像素密度,2 表示手机屏幕在宽度上有3752 个像素,在高度上有6672 个像素,因此iPhone6/7/8 的屏幕的像素尺寸就是750*1334。

当我们在这种像素尺寸大于物理尺寸的高分辨率显示器里绘图的时候,就需要考虑一个问题。

若我们直接在iPhone6/7/8 里建立一个充满屏幕的canvas,那其像素尺寸就是375*667。

这个尺寸并没发挥高分辨率显示器的优势,我们需要先将其像素尺寸设置为7501334,然后再将其css 尺寸设置为375667。

这样,就可以让canvas画布以高分辨率的姿态显示在显示器里。

代码示例:

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 就是设备像素密度,是window下的属性,即window.devicePixelRatio。

其实,有的时候若不刻意观察,canvas 有没有自适应设备分辨率是很难看出的。

因此,若是对画面的渲染质量要求不高,可以什么都不做,这样也能避免canvas 画布像素尺寸变大后降低渲染效率的问题。

关于响应式设计咱们就说到这,接下来咱们说一下three.js 里的内置几何体。

参考链接:threejs.org/manual/#en/…

总结

到此这篇关于three.js响应式设计的文章就介绍到这了,更多相关three.js响应式设计内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Three.js快速入门教程

    引言 本文主要是讲解Three.js的相关概念,帮助读者对Three.js以及相关知识形成比较完整的理解. 近年来web得到了快速的发展.随着HTML5的普及,网页的表现能力越来越强大.网页上已经可以做出很多复杂的动画,精美的效果. 但是,人总是贪的.那么,在此之上还能做什么呢?其中一种就是通过WebGL在网页中绘制高性能的3D图形. OpenGL,WebGL到Three.js OpenGL大概许多人都有所耳闻,它是最常用的跨平台图形库. WebGL是基于OpenGL设计的面向web的图形标准,

  • THREE.JS入门教程(1)THREE.JS使用前了解

    Three.js是一个伟大的开源WebGL库,WebGL允许JavaScript操作GPU,在浏览器端实现真正意义的3D.但是目前这项技术还处在发展阶段,资料极为匮乏,爱好者学习基本要通过Demo源码和Three.js本身的源码来学习. 国外网站 aerotwist.com 有六篇较为简单的入门教程,我尝试着将其翻译过来,与大家分享. 我在一些实验项目中使用了Three.js,我发现它对快速上手浏览器3D编程确实很有帮助.通过Three.js,你不仅可以创建相机.物体.光线.材质等等,还可以选择

  • Three.js实现简单3D房间布局

    本文实例为大家分享了Three.js实现简单3D房间布局的具体代码,供大家参考,具体内容如下 废话不说了,直接上成果图. 代码如下 <!doctype html> <html lang="en"> <head> <title>房间布局</title> <meta charset="utf-8"> <meta name="viewport" content="w

  • three.js响应式设计实例详解

    目录 1-canvas 的响应式布局 示例:三维插图 2-自适应设备分辨率 总结 源码地址:github.com/buglas/thre… 1-canvas 的响应式布局 canvas 画布的尺寸有两种: 像素尺寸,即canvas画布在高度和宽度上有多少个像素,默认是300*150 css 尺寸,即css 里的width和height 在web前端,dom元素的响应式布局一般是通过css 实现的. 而canvas 则并非如此,canvas 的响应式布局需要考虑其像素尺寸. 接下来,咱们就通过让c

  • bootstrap响应式表格实例详解

    Bootstrap 的响应式 CSS 能够自适应于台式机.平板电脑和手机,现在就bootstrap的响应式举一个例子: 如上图所示,要实现该表格在手机等移动端上只显示代号.名称.和价格,其他以查看详情的方式显示(也就是下图:) 首先,先实现在移动端能由左图到右图的转换: 代码如下: <meta charset="UTF-8"> <title></title> <!--引入bootstrap的css文件--> <link type=&

  • 基于rem的移动端响应式适配方案(详解)

    视口 在前一段时间,我曾经写过一篇关于viewport的文章.最近由于在接触移动端开发,对viewport有了新的理解.于是,打算重新写一篇文章,介绍移动端视口的相关概念. 关于这篇文章说到的所有知识,本质上离不开以下代码 <meta name="viewport" content="width=device-width, initial-scala=1, maximum-scale=1, minimum-scale=1, user-scalable=no"

  • Node.js REPL (交互式解释器)实例详解

    Node.js  REPL (交互式解释器)实例详解 Node.js REPL(Read Eval Print Loop:交互式解释器) 表示一个电脑的环境,类似 Window 系统的终端,我们可以在终端中输入命令,并接收系统的响应. Node 自带了交互式解释器,可以执行以下任务: 读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中. 执行 - 执行输入的数据结构 打印 - 输出结果 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出. 多行表达式

  • 用原生 JS 实现 innerHTML 功能实例详解

    都知道浏览器和服务端是通过 HTTP 协议进行数据传输的,而 HTTP 协议又是纯文本协议,那么浏览器在得到服务端传输过来的 HTML 字符串,是如何解析成真实的 DOM 元素的呢,也就是我们常说的生成 DOM Tree,最近了解到状态机这样一个概念,于是就萌生一个想法,实现一个 innerHTML 功能的函数,也算是小小的实践一下. 函数原型 我们实现一个如下的函数,参数是 DOM 元素和 HTML 字符串,将 HTML 字符串转换成真实的 DOM 元素且 append 在参数一传入的 DOM

  • Node.js  REPL (交互式解释器)实例详解

    Node.js  REPL (交互式解释器)实例详解 Node.js REPL(Read Eval Print Loop:交互式解释器) 表示一个电脑的环境,类似 Window 系统的终端,我们可以在终端中输入命令,并接收系统的响应. Node 自带了交互式解释器,可以执行以下任务: 读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中. 执行 - 执行输入的数据结构 打印 - 输出结果 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出. 多行表达式

  • Angular.js之作用域scope'@','=','&'实例详解

    什么是scope AngularJS 中,作用域是一个指向应用模型的对象,它是表达式的执行环境.作用域有层次结构,这个层次和相应的 DOM 几乎是一样的.作用域能监控表达式和传递事件. 在 HTML 代码中,一旦一个 ng-app 指令被定义,那么一个作用域就产生了,由 ng-app 所生成的作用域比较特殊,它是一个根作用域($rootScope),它是其他所有$Scope 的最顶层. 除了用 ng-app 指令可以产生一个作用域之外,其他的指令如 ng-controller,ng-repeat

  • 判断js数据类型的函数实例详解

    function judgeType(change) { if (arguments.length == 0) { return '0';//无参数传入 } if (change === null) { return 'null' } if (change === undefined && arguments.length > 0) { return 'undefined' } if (change instanceof Function) { return 'function' }

  • nodejs 使用 js 模块的方法实例详解

    Intro# 最近需要用 nodejs 做一个爬虫,Google 有一个 Puppeteer 的项目,可以用它来做爬虫,有关 Puppeteer 的介绍网上也有很多,在这里就不做详细介绍了. node 小白,开始的时候有点懵逼,模块导出也不会. 官方文档上说支持 *.mjs 但是还要改文件扩展名,感觉有点怪怪的,就没用,主要是基于js的模块使用. 模块导出的两种方式# 因为对 C# 比较熟悉,从我对 C# 的理解中,将 nodejs 中模块导出分成两种形式: 1.一个要实例化才能调用的模块 2.

  • JavaWeb Refresh响应头代码实例详解

    这篇文章主要介绍了JavaWeb Refresh响应头代码实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.Refresh响应头:可以理解为定时的重定向,在指定的时间后发生页面的跳转 二.代码示例: package cn.xxx.Servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.Ht

随机推荐