Three.js ensures that the mesh texture does not stretch, instead it covers its container perfectly

I have a container where I apply an image using three.js and mesh.

Here's how I add my mesh to the scene:

this.$els = {
    el: el,
    image: el.querySelector('.ch__image') <-- size of container image is 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 them behave like object-fit: cover or background-size: cover?

Answer №1

Experiment with the cover() function demonstrated in this code snippet. The concept is to leverage both the aspect ratio of your container and the image to replicate a similar effect as 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.org/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.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="77031f051212374759464642">[email protected]</a>/build/three.js"></script>

By the way: Instead of relying on window.innerWidth and window.innerHeight, consider using the dimensions of the container instead.

Answer №2

I utilize a Canvas element to adjust the size of an image using CanvasContext.drawImage, then proceed to create a CanvasTexture. The process is successful.

import * as THREE from 'three';

function generateTexture(
  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;
  });
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to access the functions within an object?

I am looking to execute a function that is an object attribute. For instance, let's take a look at the following code snippet: var obj={ a:"One", b:"two", c:'three', d:function f(){ console.log("Hello World"); } } I am trying ...

What's the most efficient way to iterate through this Array and display its contents in HTML?

I'm struggling to sort a simple array and I think the issue might be related to the time format. I'm not sure how to reference it or how I can properly sort the time in this array format for future sorting. //function defined to input values ...

Discover all CSS elements with Python Selenium and replace them all

When automating with Python Selenium, I am trying to modify the CSS element style to change the theme color of a page. An example of the page element is shown below: <div style="background: #e7e7e7"> <div style="border-bottom: #e ...

Seeking a sleeker approach to composing various components with shared functions

Currently, I have a scenario where I have identical components that display data but also need to handle state manipulation and saving. There are 5 other similar components with slight differences in their functions. I am looking for a more efficient way t ...

Adjust the CSS of a dynamically generated jQuery checkbox button in real-time

I am working on a project where I am creating a series of jQuery checkboxes dynamically within a loop. Here is how I am doing it: var checkbox = $('<input>').attr({type: 'checkbox', id: checkbox_id); panel.append(checkbox); panel ...

The number input component that is meant to be reusable is experiencing functionality issues within the NUXT framework

I have a reusable input component that is being used in various places. Everything works well with the number input, but the issue arises when I completely clear the input. This action triggers a warning message in the console: [Vue warn]: Invalid prop: t ...

Passing Variables from Node JS to Pug Template's HTML and JavaScript Sections

Here is a route that sends various variables to a Pug template: items.js route router.get('/edit/:itemObjectId', async function(req, res, next) { var itemObjectId = req.params.itemObjectId; var equipmentCategoryArr = []; var lifeExp ...

How to ensure the right size for your sections in HTML5

I am currently learning HTML5 and attempting to create a specific sized section in the browser where various elements like buttons and text will be displayed based on user interaction. However, I am running into an issue with adjusting the size of the sect ...

How to avoid property sharing in Angular recursive components

I am currently working on a recursive component that generates a tree structure with collapsible functionality. However, I am facing an issue where the state variable active is being shared among child components. Is there a way to prevent this from happen ...

Utilize JavaScript to create a toggle menu feature that can target multiple variables with just one click function

I am attempting to create a collapsing menu on click functionality with additional modifications. One of the changes I would like to implement is altering the background of another element when the menu collapses. Currently, the code snippet only works fo ...

The React Bit Dev module is showing a 404 error

Attempting to incorporate the semantic-ui-react table reusable component from the Bit.dev community into my application. The link I am using is: To add this component to your application, use: npm i @bit/semantic-org.semantic-ui-react.table However, when ...

Tips for aligning an HTML button with a hyperlink

<form method="get" action=https://www.wwf.de/spenden-helfen/allgemeine-spende> <button type="submit">Donate Now</button> I am facing an issue where the button is appearing randomly on my website, but I need it to ...

What is the best way to create a React text box that exclusively accepts numeric values or remains empty, and automatically displays the number keypad on mobile devices?

While there are numerous similar questions on StackOverflow, none of them fully address all of my requirements in a single solution. Any assistance would be greatly appreciated. The Issue at Hand Within my React application, I am in need of a text box tha ...

Utilize CSS properties to pass as arguments to a JavaScript function

Is there a way for me to make my CSS animation functions more efficient? I have similar functions for different properties like height, width, and left. Can I modify the function below to accept a CSS property argument instead of hardcoding height? Window ...

AngularJS ngAnimate triggering prematurely

In the current setup, p2 animates in while p1 is still animating out. After that, p1 disappears and p2 glitches up the page. The desired effect is for 1 to fade out and then have 2 fade in. html: <nav> <a ng-click="changeView('p1' ...

Floating Action Button is not properly attached to its parent container

When developing my React Js app, I decided to utilize the impressive libraries of Material UI v4. One particular component I customized is a Floating Action Button (FAB). The FAB component, illustrated as the red box in the image below, needs to remain p ...

Eliminate certain inline styles using jQuery

I am facing an issue with an asp menu I am using. It is automatically inserting style="width:3px;" into my menu table tds, creating an unsightly gap between my tabs. Instead of asking our developer to customize the menu just to fix this cosmetic flaw, I am ...

How to show a placeholder in a select input using ReactJS

I'm currently trying to incorporate placeholder text into a select input field using ReactJS, but it doesn't seem to be working as intended. Here is the code snippet I am working with: <Input type="select" placeholder="placeholder"> ...

Angular 2 Mouseover Functionality

Can anyone share the correct method for creating a hover-like event in the latest Angular2 framework? In the previous Angular1 version, we used ng-Mouseover for this purpose, but it seems like it is no longer available in Angular2. I have searched throug ...

Ways to automatically adjust the margins of my HTML body page

I'm currently working on a school project to develop a website. The challenge I'm facing is customizing the navbar and header background colors in such a way that they extend seamlessly to the end of the page. Despite my efforts, there seems to b ...