So ermitteln Sie die Breite eines Reaktionselements

73

Ich versuche, eine Bereichseingabe zu erstellen, die einen Tooltip direkt über dem Schieberegler-Daumen anzeigt.

Ich habe einige Vanilla JS-Beispiele online durchgesehen und es scheint, dass ich die Breite des Elements haben muss, um dies zu erreichen.

Also habe ich mich nur gefragt, wie ich die Breite der Elemente bekomme?

Ziemlich genau das Äquivalent der JQuery-Methode $(element).width()

Rick Enciso
quelle
1
Im Jahr 2020 empfehle ich dringend, stattdessen useMeasure () von react-use zu verwenden. Es gibt Breite, Höhe, x-Position, y-Position und mehr zurück.
Christopher Regner

Antworten:

116
    class MyComponent extends Component {
      constructor(props){
        super(props)
        this.myInput = React.createRef()
      }

      componentDidMount () {
        console.log(this.myInput.current.offsetWidth)
      }

      render () {
        return (
        // new way - as of [email protected]
        <div ref={this.myInput}>some elem</div>
        // legacy way
        // <div ref={(ref) => this.myInput = ref}>some elem</div>
        )
      }
    }
Rick Enciso
quelle
6
Ich möchte nur sagen, dass diese Art der Erstellung von Refs anscheinend jetzt auch veraltet ist. (Jetzt erstellen Sie eine React.createRef()
Referenz
3
@aaaidan Habe gerade die Dokumente überprüft. Rückrufreferenzen sind nicht veraltet. Manchmal benötigen Sie sie, wenn Sie den Verweis auf ein geklontes Element nicht überschreiben möchten.
user1164937
Mit welcher ID möchten Sie die Breite des Div-Elements ermitteln, bei dem es sich um ein React-Element und nicht um ein natives HTML-Element handelt?
RezKesh
2
Vielen Dank, dass Sie den "Legacy" -Weg beibehalten haben.
Skwidbreth
39

Mit Haken :

const MyComponent = () => {
  const ref = useRef(null);
  useEffect(() => {
    console.log('width', ref.current ? ref.current.offsetWidth : 0);
  }, [ref.current]);
  return <div ref={ref}>Hello</div>;
};
Nelu
quelle
Statt beide verwenden useRefund useEffectSie können einfach einen Rückruf ref im Beispiel oben verwenden.
Izhaki
6
Es wird jedoch nicht aktualisiert, wenn die Größe geändert wird, sondern nur, wenn sich Ihre Referenz ändert
Marco Antônio,
2
Das funktioniert perfekt. Mit Typescript einfach hinzufügenconst ref = useRef<HTMLHeadingElement>(null);
Albert Tjornejoj
21

Eigentlich wäre es besser, diese Größenänderungslogik in einem benutzerdefinierten Hook zu isolieren . Sie können einen benutzerdefinierten Hook wie folgt erstellen:

const useResize = (myRef) => {
  const [width, setWidth] = useState(0)
  const [height, setHeight] = useState(0)

  useEffect(() => {
    const handleResize = () => {
      setWidth(myRef.current.offsetWidth)
      setHeight(myRef.current.offsetHeight)
    }

    window.addEventListener('resize', handleResize)

    return () => {
      window.removeEventListener('resize', handleResize)
    }
  }, [myRef])

  return { width, height }
}

und dann können Sie es verwenden wie:

const MyComponent = () => {
  const componentRef = useRef()
  const { width, height } = useResize(componentRef)

  return (
    <div ref={myRef}>
      <p>width: {width}px</p>
      <p>height: {height}px</p>
    <div/>
  )
}
Marco Antônio
quelle
1
Sie müssen den Listener für Größenänderungsereignisse an das Fenster anhängen, das ich denke: developer.mozilla.org/en-US/docs/Web/API/Window/resize_event
Gerbus
handleResize()auf den EventListeners oder useResize wird zurückkehren{ 0, 0}
kcNeko
2
Gibt die ursprüngliche Größe nicht zurück. Dh funktioniert erst nach einer Größenänderung. Um dies zu beheben, useResizemuss `if (myRef.current) {<setWidth SetHeight etc,>)
meesern
13

Dies ist im Grunde die Antwort von Marco Antônio für einen benutzerdefinierten React-Hook, der jedoch geändert wurde, um die Abmessungen zunächst und nicht erst nach einer Größenänderung festzulegen.

export const useContainerDimensions = myRef => {
  const getDimensions = () => ({
    width: myRef.current.offsetWidth,
    height: myRef.current.offsetHeight
  })

  const [dimensions, setDimensions] = useState({ width: 0, height: 0 })

  useEffect(() => {
    const handleResize = () => {
      setDimensions(getDimensions())
    }

    if (myRef.current) {
      setDimensions(getDimensions())
    }

    window.addEventListener("resize", handleResize)

    return () => {
      window.removeEventListener("resize", handleResize)
    }
  }, [myRef])

  return dimensions;
};

Auf die gleiche Weise verwendet:

const MyComponent = () => {
  const componentRef = useRef()
  const { width, height } = useContainerDimensions(componentRef)

  return (
    <div ref={componentRef}>
      <p>width: {width}px</p>
      <p>height: {height}px</p>
    <div/>
  )
}
meesern
quelle
4

Eine einfache und aktuelle Lösung ist die Verwendung des React React useRef-Hooks , der einen Verweis auf die Komponente / das Element speichert, kombiniert mit einem useEffect- Hook, der beim Rendern von Komponenten ausgelöst wird.

import React, {useState, useEffect, useRef} from 'react';

export default App = () => {
  const [width, setWidth] = useState(0);
  const elementRef = useRef(null);

  useEffect(() => {
    setWidth(elementRef.current.getBoundingClientRect().width);
  }, []); //empty dependency array so it only runs once at render

  return (
    <div ref={elementRef}>
      {width}
    </div>
  )
}
Charri
quelle
Ich bekomme columnRef.current.getBoundingRect ist keine Funktion
user3808307
1
Das bedeutet, dass der Verweis aus irgendeinem Grund beim ersten Rendern nicht an das Element gebunden wurde. Eine einfache Lösung dafür ist das Hinzufügen, if (!ref.current) return;bevor Sie die Referenz verwenden, und das Hinzufügen der Referenz zum Abhängigkeitsarray, damit es wie [ref]eine leere
aussieht
2
es ist nicht getBoundingRect, es ist getBoundingClientRect ()
Douglas Schmidt
3

Hier ist eine TypeScript-Version der Antwort von @ meseern , die unnötige Zuweisungen beim erneuten Rendern vermeidet:

import React, { useState, useEffect } from 'react';

export function useContainerDimensions(myRef: React.RefObject<any>) {
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });

  useEffect(() => {
    const getDimensions = () => ({
      width: (myRef && myRef.current.offsetWidth) || 0,
      height: (myRef && myRef.current.offsetHeight) || 0,
    });

    const handleResize = () => {
      setDimensions(getDimensions());
    };

    if (myRef.current) {
      setDimensions(getDimensions());
    }

    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, [myRef]);

  return dimensions;
}
sdll
quelle
Hallo! Was ist, wenn es sich um eine kontrollierte Komponente handelt?
Antenaina
2

Es empfiehlt sich, auf Ereignisse zur Größenänderung zu warten, um eine Größenänderung beim Rendern oder sogar eine Größenänderung des Benutzerfensters zu verhindern, die Ihre Anwendung beschädigen kann.

const MyComponent = ()=> {
  const myRef = useRef(null)

  const [myComponenetWidth, setMyComponentWidth] = useState('')

  const handleResize = ()=>{ 
    setMyComponentWidth(myRef.current.offsetWidth)
  }

  useEffect(() =>{
    if(myRef.current)
      myRef.current.addEventListener('resize', handleResize)

    return ()=> {
     myRef.current.removeEventListener('resize', handleResize)
    }
  }, [myRef])

  return (
  <div ref={MyRef}>Hello</div>
  )
}
Luís Guilherme Pelin Martins
quelle
1
Sie müssen den Listener für Größenänderungsereignisse an das Fenster anhängen, das ich denke: developer.mozilla.org/en-US/docs/Web/API/Window/resize_event
Gerbus
0

Dies könnte möglicherweise auf einfachere Weise durch Verwendung von Rückrufreferenzen gehandhabt werden .

Mit React können Sie eine Funktion an eine Referenz übergeben, die das zugrunde liegende DOM-Element oder den zugrunde liegenden Komponentenknoten zurückgibt. Siehe: https://reactjs.org/docs/refs-and-the-dom.html#callback-refs

const MyComponent = () => {
    const myRef = node => console.log(node ? node.innerText : 'NULL!');
    return <div ref={myRef}>Hello World</div>;
 }

Diese Funktion wird ausgelöst, wenn der zugrunde liegende Knoten geändert wird. Zwischen den Updates wird es null sein, daher müssen wir dies überprüfen. Beispiel:

const MyComponent = () => {
    const [time, setTime] = React.useState(123);
    const myRef = node => console.log(node ? node.innerText : 'NULL!');
    setTimeout(() => setTime(time+1), 1000);
    return <div ref={myRef}>Hello World {time}</div>;
}
/*** Console output: 
 Hello World 123
 NULL!
 Hello World 124
 NULL!
...etc
***/

Obwohl dies nicht die Größenänderung als solche behandelt (wir würden immer noch einen Listener für die Größenänderung benötigen, um die Größe des Fensters zu ändern), bin ich mir nicht sicher, ob das OP dies verlangt hat. In dieser Version wird die Größe des Knotens aufgrund eines Updates geändert.

Hier ist ein benutzerdefinierter Haken, der auf dieser Idee basiert:

export const useClientRect = () => {
    const [rect, setRect] = useState({width:0, height:0});
    const ref = useCallback(node => {
        if (node !== null) {
            const { width, height } = node.getBoundingClientRect();
            setRect({ width, height });
        }
    }, []);
    return [rect, ref];
};

Das Obige basiert auf https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node

Notieren Sie den Haken zurückkehrt einen ref Rückruf, anstatt dass sie bestanden einen ref. Und wir verwenden useCallback , um zu vermeiden, dass jedes Mal eine neue Ref-Funktion neu erstellt wird. nicht lebenswichtig, aber als gute Praxis angesehen.

Die Verwendung ist wie folgt (basierend auf dem Beispiel von Marco Antônio):

const MyComponent = ({children}) => {
  const [rect, myRef] = useClientRect();
  const { width, height } = rect;

  return (
    <div ref={myRef}>
      <p>width: {width}px</p>
      <p>height: {height}px</p>
      {children}
    <div/>
  )
}
Trevedhek
quelle