I have a container which i apply an image to using a three.js and mesh.
That's how I apply my mesh to the scene:
this.$els = {
el: el,
image: el.querySelector('.ch__image') <-- size of container image is being applied to
};
this.loader = new THREE.TextureLoader();
this.image = this.loader.load(this.$els.image.dataset.src);
this.sizes = new THREE.Vector2(0, 0);
this.offset = new THREE.Vector2(0, 0);
getSizes() {
const { width, height, top, left } = this.$els.image.getBoundingClientRect();
this.sizes.set(width, height);
this.offset.set(left - window.innerWidth / 2 + width / 2, -top + window.innerHeight / 2 - height / 2)
}
createMesh() {
this.geometry = new THREE.PlaneBufferGeometry(1, 1, 1, 1);
this.material = new THREE.MeshBasicMaterial({
map: this.image
});
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.mesh.position.set(this.offset.x, this.offset.y, 0);
this.mesh.scale.set(this.sizes.x, this.sizes.y, 1);
this.scene.add(this.mesh)
}
Currently images/textures are being stretched to fit the container - how can I make the behave just like object-fit: cover
or background-size: cover
?
I have a container which i apply an image to using a three.js and mesh.
That's how I apply my mesh to the scene:
this.$els = {
el: el,
image: el.querySelector('.ch__image') <-- size of container image is being applied to
};
this.loader = new THREE.TextureLoader();
this.image = this.loader.load(this.$els.image.dataset.src);
this.sizes = new THREE.Vector2(0, 0);
this.offset = new THREE.Vector2(0, 0);
getSizes() {
const { width, height, top, left } = this.$els.image.getBoundingClientRect();
this.sizes.set(width, height);
this.offset.set(left - window.innerWidth / 2 + width / 2, -top + window.innerHeight / 2 - height / 2)
}
createMesh() {
this.geometry = new THREE.PlaneBufferGeometry(1, 1, 1, 1);
this.material = new THREE.MeshBasicMaterial({
map: this.image
});
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.mesh.position.set(this.offset.x, this.offset.y, 0);
this.mesh.scale.set(this.sizes.x, this.sizes.y, 1);
this.scene.add(this.mesh)
}
Currently images/textures are being stretched to fit the container - how can I make the behave just like object-fit: cover
or background-size: cover
?
2 Answers
Reset to default 7Try it with the cover()
function presented in this code snippet. The idea is to use the aspect of your container and of the image to achieve a similar result like background-size: cover
.
var camera, scene, renderer;
var texture;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 );
camera.position.z = 1;
scene = new THREE.Scene();
texture = new THREE.TextureLoader().load( 'https://threejs/examples/textures/crate.gif', () => {
cover( texture, window.innerWidth / window.innerHeight );
scene.background = texture;
} );
texture.matrixAutoUpdate = false;
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
var aspect = window.innerWidth / window.innerHeight;
camera.aspect = aspect;
camera.updateProjectionMatrix();
cover( texture, aspect );
renderer.setSize( window.innerWidth, window.innerHeight );
}
function cover( texture, aspect ) {
var imageAspect = texture.image.width / texture.image.height;
if ( aspect < imageAspect ) {
texture.matrix.setUvTransform( 0, 0, aspect / imageAspect, 1, 0, 0.5, 0.5 );
} else {
texture.matrix.setUvTransform( 0, 0, 1, imageAspect / aspect, 0, 0.5, 0.5 );
}
}
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
body {
margin: 0;
}
canvas {
display: block;
}
<script src="https://cdn.jsdelivr/npm/[email protected]/build/three.js"></script>
BTW: Instead of using window.innerWidth
and window.innerHeight
, use the dimensions of the container instead.
I create a Canvas element, and edit the size of image by CanvasContext.drwaImage, then make a CanvasTexture. It works
import * as THREE from 'three';
function createCoverTexture(
url: string,
width: number,
height: number
): Promise<THREE.CanvasTexture> {
return new Promise((resolve, reject) => {
const aspect = width / height;
const image = new Image();
image.crossOrigin = 'anonymous';
image.onload = () => {
const [sw, sh, left, top] =
image.height < image.width / aspect
? [
image.height * aspect,
image.height,
image.width / 2 - (image.height * aspect) / 2,
0,
]
: [
image.width,
image.width / aspect,
0,
image.height / 2 - image.width / aspect / 2,
];
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = sw;
canvas.height = sh;
ctx?.drawImage(image, left, top, sw, sh, 0, 0, sw, sh);
resolve(new THREE.CanvasTexture(canvas));
};
image.onerror = reject;
image.src = url;
});
}