0

I have a scene with some Text components from Drei that I'd like to make responsive (smaller text size and max-width on mobile. Also since they are stacked vertically, I'm currently eyeballing the size and manually moving the text down the y-axis.

How should I go about having the text resize when the window changes? How do you get the height of the Text component so you know where to place the next one?

import { Float, Text } from "@react-three/drei"

export default function AboutText() {
  const text = [
    "Blah blah",
    "Yada Yada",
    "Lorem Ipsum",
    "More Text"
  ]

  const TextJSX = text.map((paragraph)=>{
    return(
      <Float rotationIntensity={0.2} floatIntensity={0.5}>
        <Text color="white" 
        anchorX="center" 
        anchorY="middle" 
        fontSize={.2} 
        position={[0,5,1]} 
        maxWidth={6} 
      >
        <meshBasicMaterial          
          color={"#FFF"}
          fog={false}
        />
          {paragraph}
        </Text>
      </Float>
    )
  })

  return (
    <>
      {TextJSX}
    </>
)}

Desktop Desktop Text component

Mobile Mobile Text Component

Ashbury
  • 2,160
  • 3
  • 27
  • 52

1 Answers1

0

I figured out a solution, the bounding box calculation is async, so it's not available right away, but there is an 'onSync' callback from Text that you can tap into:

onSync={(mesh)=> {
  const visibleBounds = mesh.textRenderInfo.visibleBounds;
}

You can adjust the maxWidth and the fontSize by taking the viewport width and dividing by some number:

 <Text color="white" 
   anchorX="center" 
   anchorY="top" 
   fontSize={viewport.width / 100} 
   position={[0,startingPosition - (offset),1]}     
   maxWidth={viewport.width / 3}

For the margins between text, I pushed the size of the text plus a margin to a state array, and when iterating over the jsx took the sum of the heights at the current index as the start position.

The whole thing looks like this:

import { useThree } from "@react-three/fiber";
import { Float, Text } from "@react-three/drei"
import { useState, useEffect } from "react";

export default function AboutText() {
  const text = [
    "1. sample text",
    "2. sample text",
    "3. sample text",
  ]

  const [heights, setHeights] = useState([])
  const startingPosition = 5
  const { viewport } = useThree();
  
  useEffect(() => {
    console.log('heights updated:', heights);
  }, [heights, viewport]);

  const TextJSX = text.map((paragraph, i)=>{
    const offset = ((i > 0) && heights.length > 0 ) ? heights.slice(0, i).reduce((a, b) => a + b, 0) : 0
    console.log(viewport.width)
    return(
      <Float rotationIntensity={0.2} floatIntensity={.5} key={i}>
        <Text color="white" 
        anchorX="center" 
        anchorY="top" 
        fontSize={viewport.width / 100} 
        position={[0,startingPosition - (offset),1]}
        
        //width of text object
        maxWidth={viewport.width / 3}

        //text is async, will run onSync when it gets a height
        onSync={(mesh)=> {
          console.log("onsync")
          const visibleBounds = mesh.textRenderInfo.visibleBounds;
          const top = Math.abs(visibleBounds[1])
          const bottom = Math.abs(visibleBounds[3])
          const margin =  + 0.2
          
          //heights state will keep building unless it's cleared out.
          setHeights(prevHeights => {
            if(prevHeights.length === text.length){
              return [(top + bottom + margin)];
            } else {
              return [...prevHeights, (top + bottom + margin)]}
            }
          )
        }}
      >
        <meshBasicMaterial
          color={"#FFF"}
          fog={false}
        />
          {paragraph}
        </Text>
      </Float>
    )
  })

  return (
    <>
      {TextJSX}
    </>
)}
Ashbury
  • 2,160
  • 3
  • 27
  • 52