04. 添加一些自适应
04 添加一些自适应
在上一节中,已经实现了
我们将给示例中的
优化第一项:修改canvas 尺寸
默认
第1 处修改:
打开项目中
body{
- margin:0;
}
html,body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ width: 100%;
}
#root {
+ height: inherit;
+ width: inherit;
}
由于我们已经给
html, body 设置了 宽高100% ,所以root 宽高 设置为inherit 即可。
第2 处修改:
新建文件
.full-screen {
display: block;
width: inherit;
height: inherit;
}
canvas 宽高继承于root ,root 继承于body ,而body 宽高均为100% ,所以最终canvas 宽高也为100% ,撑满整个屏幕。
特别提醒: 在上面样式中,我们设置了
为什么要这么做?
因为我们在后面代码中,需要获取
内联元素和没有
CSS 样式的元素,获取到的clientWidht 和clientHeight 的值永远为0
第3 处修改:
在
+ import './index.scss'
const HelloThreejs: React.FC = () => {
...
return (
<canvas ref={canvasRef} className='full-screen' />
)
}
此时,再次执行预览 yarn start
,就会发现
目前存在的问题:
可以观察到
所以立方体出现了 扭曲、模糊、锯齿。
那我们继续修改代码。
第4 处修改:
修改
...
const render = (time: number) => {
time = time * 0.001
+ const canvas = renderer.domElement //获取 canvas
+ camera.aspect = canvas.clientWidth / canvas.clientHeight //设置镜头宽高比
+ camera.updateProjectionMatrix() //通知镜头更新视椎(视野)
cubes.map(cube => { ... }
}
...
第5 处修改:
第
继续修改
...
const render = (time: number) => {
time = time * 0.001
const canvas = renderer.domElement //获取 canvas
camera.aspect = canvas.clientWidth / canvas.clientHeight //设置镜头宽高比
camera.updateProjectionMatrix() //通知镜头更新视椎(视野)
+ renderer.setSize(canvas.clientWidth, canvas.clientHeight, false)
+ //第3个参数为可选参数,默认值为 true,false 意思是阻止因渲染内容尺寸发生变化而去修改 canvas 尺寸
cubes.map(cube => { ... }
}
...
经过上面一番修改,浏览器中
关于
在本示例中
我查看了一下
发现了其中以下代码片段:
this.setSize = function ( width, height, updateStyle ) {
...
if ( updateStyle !== false ) {
_canvas.style.width = width + 'px';
_canvas.style.height = height + 'px';
}
...
}
可以看出,假设第
因此我们可以得出结论:
如何应对高清屏?
从上面示例可以看出,浏览器中渲染的画面尺寸,完全是按照
对于高清屏
第1 种策略( 推荐) :不做任何策略
假设
也就是说原本只需渲染
假设
事实上高清屏本身都会做显示优化,即使不做任何处理,画面清晰度并不会明显特别差。
因此,什么都不做,其实是一个非常好的策略。
假设就是想设置成高清屏,那又该如何操作呢?
第2 种策略( 强烈不推荐) :通过renderer.setPixelRatio 来配置渲染分辨率倍数
在浏览器中,通过
然后告知渲染器,以后任何
renderer.setPixelRatio(window.devicePixelRatio)
强烈不推荐这种做法。
第3 种策略( 勉强推荐) :按屏幕分辨率比值,计算出对应渲染尺寸
这种策略思路是:通过分辨率比值,计算出实际上应该渲染的最大尺寸,然后渲染出这个尺寸,再将画面内容渲染到
举例:假设
这样的操作,会使 渲染器
对应的渲染代码为:
const canvas = renderer.domElement
const ratio = window.devicePixelRatio
const newWidth = Math.floor(canvas.clientWidth * ratio)
const newHeight = Math.floor(canvas.clientHeight * ratio)
renderer.setSize(newWidth,newHeight,false) //特别注意,第 3 个参数一定要为 false
尽管第
你看在线视频时,关于清晰度会做哪种选择?
A:蓝光1080P ,画面超级清晰,但播放时会有点卡顿
B:高清720 P ,画面清晰度能够接受,播放时也非常流畅
至此,关于
等一等,我们现在的代码正确吗?
目前来说,虽然实际运行没有一点问题,但代码实际上并不是最优的。
现在做给渲染器添加尺寸发生变化的代码是放在了
我们需要改进的地方时:仅在浏览器窗口尺寸发生
需要说明的地方:
- 监听浏览器窗口尺寸变化,对应的是
window.addEventListener( ‘resize’, xxxx) - 当
React 卸载后,一定记得移除监听window.removeEventListener( ‘resize’, xxxx) - 为了在移除监听时可以找到 在
useEffect 中定义的resize 事件处理函数,我们会在示例代码中,再通过useRef 创建一个变量指向 事件处理函数。
最终修改后的代码:
import React, { useRef, useEffect } from 'react'
import { WebGLRenderer, PerspectiveCamera, Scene, BoxGeometry, Mesh, DirectionalLight, MeshPhongMaterial } from 'three'
import './index.scss'
const HelloThreejs: React.FC = () => {
const canvasRef = useRef<HTMLCanvasElement>(null)
const resizeHandleRef = useRef<() => void>()
useEffect(() => {
if (canvasRef.current) {
//创建渲染器
const renderer = new WebGLRenderer({ canvas: canvasRef.current })
//创建镜头
//PerspectiveCamera() 中的 4 个参数分别为:
//1、fov(field of view 的缩写),可选参数,默认值为 50,指垂直方向上的角度,注意该值是度数而不是弧度
//2、aspect,可选参数,默认值为 1,画布的高宽比,例如画布高300像素,宽150像素,那么意味着高宽比为 2
//3、near,可选参数,默认值为 0.1,近平面,限制摄像机可绘制最近的距离,若小于该距离则不会绘制(相当于被裁切掉)
//4、far,可选参数,默认值为 2000,远平面,限制摄像机可绘制最远的距离,若超出该距离则不会绘制(相当于被裁切掉)
//以上 4 个参数在一起,构成了一个 “视椎”,关于视椎的概念理解,暂时先不作详细描述。
const camera = new PerspectiveCamera(75, 2, 0.1, 5)
//创建场景
const scene = new Scene()
//创建几何体
const geometry = new BoxGeometry(1, 1, 1)
//创建材质
//我们需要让立方体能够反射光,所以不使用MeshBasicMaterial,而是改用MeshPhongMaterial
//const material = new MeshBasicMaterial({ color: 0x44aa88 })
const material1 = new MeshPhongMaterial({ color: 0x44aa88 })
const material2 = new MeshPhongMaterial({ color: 0xc50d0d })
const material3 = new MeshPhongMaterial({ color: 0x39b20a })
//创建网格
const cube1 = new Mesh(geometry, material1)
cube1.position.x = -2
scene.add(cube1)//将网格添加到场景中
const cube2 = new Mesh(geometry, material2)
cube2.position.x = 0
scene.add(cube2)//将网格添加到场景中
const cube3 = new Mesh(geometry, material3)
cube3.position.x = 2
scene.add(cube3)//将网格添加到场景中
const cubes = [cube1, cube2, cube3]
//创建光源
const light = new DirectionalLight(0xFFFFFF, 1)
light.position.set(-1, 2, 4)
scene.add(light)//将光源添加到场景中
//设置透视镜头的Z轴距离,以便我们以某个距离来观察几何体
//之前初始化透视镜头时,设置的近平面为 0.1,远平面为 5
//因此 camera.position.z 的值一定要在 0.1 - 5 的范围内,超出这个范围则画面不会被渲染
camera.position.z = 2
//渲染器根据场景、透视镜头来渲染画面,并将该画面内容填充到 DOM 的 canvas 元素中
//renderer.render(scene, camera)//由于后面我们添加了自动渲染渲染动画,所以此处的渲染可以注释掉
//添加自动旋转渲染动画
const render = (time: number) => {
time = time * 0.001
// cube.rotation.x = time
// cube.rotation.y = time
cubes.forEach(cube => {
cube.rotation.x = time
cube.rotation.y = time
})
renderer.render(scene, camera)
window.requestAnimationFrame(render)
}
window.requestAnimationFrame(render)
const handleResize = () => {
const canvas = renderer.domElement
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
renderer.setSize(canvas.clientWidth, canvas.clientHeight, false)
}
handleResize() //默认打开时,即重新触发一次
resizeHandleRef.current = handleResize //将 resizeHandleRef.current 与 useEffect() 中声明的函数进行绑定
window.addEventListener('resize', handleResize) //添加窗口 resize 事件处理函数
}
return () => {
if (resizeHandleRef && resizeHandleRef.current) {
window.removeEventListener('resize', resizeHandleRef.current)
}
}
}, [canvasRef])
return (
<canvas ref={canvasRef} className='full-screen' />
)
}
export default HelloThreejs
再次补充说明:
尽管代码已经有所改进,但上述代码中,创建
合理的应该是通过
将原本集中的代码分散到更多小的 代码块 中。
包括浏览器窗口
这里就先暂时这样,不再做改进,等到将来再去做稍微复杂点的 场景应用 时,会再次优化代码结构。
以下内容更新于
2021.05.11
通过ResizeObserver 来监听画布尺寸变化
在本文以及本教程的所有后面章节中,我们都是通过监听
由于这些示例中实际上只存在一个
但是在实际的项目中,有可能
- 通过
CSS 修改<canvas > 标签的宽高 - 在
flex 布局下,当其他元素尺寸发生变化时,影响到<canvas > ,从而造成画布发生尺寸变化。 - …
很明显,通过
因此我们要寻找其他监听 画布 标签尺寸发生变化的方式。
我们可以通过浏览器最新的
在
ResizeObserver 出现之前,只能对window 添加resize 监听,无法对DOM 元素添加尺寸变化监听。
observer 单词意思是 “观察”,也就是设计模式中的 “观察模式”,但是我个人习惯性有时候称呼为 “监控模式”
- observe():开始监控
( 观察) 某元素尺寸变化 - unobserve():停止监控
( 观察) 某元素尺寸变化 - disconnect():取消和结束目标元素上所有的监控
( 观察)
更多详细介绍,请查阅:
https://developer.mozilla.org/zh-CN/docs/Web/API/ResizeObserver
实际示例代码:
const handleResize = () => {
const canvas = renderer.domElement
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
renderer.setSize(canvas.clientWidth, canvas.clientHeight, false)
}
handleResize()
//我们不再添加 window resize 监控(观察)
- window.addEventListener('resize', handleResize)
//改为使用 ResizeObserver 来监控(观察)尺寸变化
+ const resizeObserver = new ResizeObserver(() => {
+ handleResize()
+ })
+ resizeObserver.observe(canvasRef.current)
//当我们卸载组件前,一定要 清除掉 监控(观察)
return () =>{
- window.removeEventListener('resize', resizeHandleRef.current)
+ resizeObserver.disconnect()
}
请注意,
resizeObserver.observe() 方法中,可以有第2 个可选参数。例如:resizeObserver.observe(canvasRef.current, { box: ‘border-box’ })
如果第
2 个可选参数不填,那么默认值为{ box: ‘content-box’ }
与本文无关的事情
我在查阅
https://github.com/mdn/translated-content/pull/817
目前该
或许此刻已经更新了。
以上内容更新于
2021.05.11
那么接下来,会系统学习一下
大楼究竟能改多高,取决于地基有多深,加油!