源码地址: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响应式设计内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

three.js响应式设计实例详解的更多相关文章

  1. VUE响应式原理的实现详解

    这篇文章主要为大家详细介绍了VUE响应式原理的实现,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助

  2. Android自定义流式布局/自动换行布局实例

    这篇文章主要介绍了Android自定义流式布局/自动换行布局实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  3. Vue响应式原理及双向数据绑定示例分析

    这篇文章主要为大家介绍了Vue响应式原理及双向数据绑定的示例分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. Android中协调滚动布局的实现代码

    这篇文章主要介绍了Android中协调滚动常用的布局实现,类似这样的协调滚动布局,当底部列表滑动的时候,顶部的布局做响应的动作,我们都可以通过 AppBarLayout 和 MotionLayout 来实现,本文通过实例代码介绍的非常详细,需要的朋友参考下吧

  5. 关于vue自适应布局(各种浏览器,分辨率)的示例代码

    这篇文章主要介绍了vue自适应布局(各种浏览器,分辨率),主要使用了flex布局的flex:1属性和自适应的css+vh+百分比这种方式,开局设置overflow:hidden,主体main部分要设置:overflow:auto,需要的朋友可以参考下

  6. vue3.0响应式函数原理详细

    这篇文章主要介绍了vue3.0响应式函数原理,Vue3的响应式系统可以监听动态添加的属性还可以监听属性的删除操作,以及数组的索引以及length属性的修改操作。另外Vue3的响应式系统还可以作为模块单独使用。下面更多介绍,需要的小伙伴可以才可以参考一下

  7. Vue3 Reactive响应式原理逻辑详解

    这篇文章主要介绍了Vue3 Reactive响应式原理逻辑详解,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下

  8. jQuery EasyUi实战教程之布局篇

    jQuery EasyUI Layout是一种基于jQuery的布局框架,今天初次使用Jquery EasyUi,简单的做了个布局页面感觉还不错,特此分享脚本之家平台供大家学习

  9. Vue前端项目自适应布局的简单方法

    最近项目开发中遇到一个需求,需要实现宽度自动适应,所以下面这篇文章主要给大家介绍了关于Vue前端项目自适应布局的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下

  10. Three.js+React制作3D梦中海岛效果

    深居内陆的人们,大概每个人都有过大海之梦吧。本文使用React+Three.js技术栈,实现3D海洋和岛屿,感兴趣的小伙伴可以跟随小编一起学习一下

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部