1-canvas 的响应式布局——保持画布尺寸和画板尺寸一致
canvas 的尺寸有两种:
- 一种是width、height属性,一般称其为画布尺寸,即图形绘制的地方。画布尺寸(像素尺寸),即canvas画布在高度和宽度上有多少个像素,默认值分别为300px、150px
/* canvas的画布尺寸-图形绘制区域*/
<canvas id="canvas" width="300" height="300">
- 另一种是css样式里的width、height属性,可通过内联样式、内部样式表或外部样式表设置。一般称其为画板尺寸,用于渲染绘制完成的图形。默认值为空。
/*CSS样式中的画板尺寸*/
<canvas id="canvas" style="width:300px; height:300px;">
<style>
/*CSS样式中的画板尺寸*/
#canvas {
width:300px;
height:300px;
}
</style>
在web前端,dom元素的响应式布局一般是通过css 实现的。
而canvas 则并非如此,canvas 的响应式布局需要考虑其像素尺寸。
- 如果设置了画布尺寸,未设置画板尺寸,或者两者都未设置,那么画板尺寸随画布尺寸改变。
- 但若设置了画板尺寸,而未设置画布尺寸,或者分别设置了,那么画板尺寸将不再随画布尺寸而改变。
如果
canvas画布尺寸和画板尺寸设置的不一样时,就会产生一个问题,渲染时画布要通过缩放来使其与画板尺寸一样,那么画布上已经绘制好的图形也会随之缩放,随之导致变形失真。
接下来,让canvas 画布自适应浏览器的窗口的尺寸,来实现canvas 的响应式布局。
1.1 示例:一个立方体
<template>
<div id="threeBox"></div>
</template>
<script>
import * as THREE from 'three'
export default {
data() {
return {
}
},
mounted() {
this.init();
},
methods: {
init() {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.querySelector("#threeBox").appendChild( renderer.domElement );
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshNormalMaterial();
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
animate()
function animate() {
requestAnimationFrame( animate );
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render( scene, camera );
}
}
}
}
</script>
1.2 先取消renderer 的尺寸设置。
//renderer.setSize(innerWidth, innerHeight);
// renderer.setSize( window.innerWidth, window.innerHeight );
1.3 用css 设置canvas 画布及其父元素的尺寸,使其充满窗口。
html {
height: 100%;
}
body {
margin: 0;
overflow: hidden;
height: 100%;
}
#threeBox, canvas{
width: 100%;
height: 100%;
}
效果如下:
由上图可见,立方体的边界出现了锯齿,这就是位图被css拉伸后失真导致的,默认canvas 画布的尺寸只有300*150。
因此,我们需要用canvas 画布的像素尺寸自适应窗口
1.4 让canvas 像素尺寸随css 尺寸同步更新
建立一个让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 画布的尺寸。
setSize() 方法中的bool 参数很重要,会用于判断是否设置canvas 画布的css 尺寸。
1.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属性是相机视口的宽高比- 当相机视口的宽高比变了,相机的透视投影矩阵也会随之改变,因此我们需要使用
camera.updateProjectionMatrix()方法更新透视投影矩阵。 - 之所以不把更新相机视口宽高比的方法一起放进
resizeRendererToDisplaySize()里,这是为了降低resizeRendererToDisplaySize() 方法和相机的耦合度。
1.6 修改后效果
<template>
<div id="threeBox"></div>
</template>
<script>
/* eslint-disable */
import * as THREE from 'three'
export default {
data() {
return {
}
},
mounted() {
this.init();
},
methods: {
init() {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer();
// renderer.setSize( window.innerWidth, window.innerHeight );
document.querySelector("#threeBox").appendChild( renderer.domElement );
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshNormalMaterial();
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
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;
}
camera.position.z = 5;
animate()
function animate() {
requestAnimationFrame(animate);
if (resizeRendererToDisplaySize(renderer)) {
const { clientWidth, clientHeight } = renderer.domElement;
camera.aspect = clientWidth / clientHeight;
camera.updateProjectionMatrix();
}
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
},
}
}
</script>
<style lang="scss">
html {
height: 100%;
}
body {
margin: 0;
overflow: hidden;
height: 100%;
}
#threeBox, canvas{
width: 100%;
height: 100%;
}
</style>