React: Can't get correct width of element afte

2019-02-27 11:34发布

问题:

I'm trying to setup a Marquee in React if a piece of text is greater than its container but I can't get the correct width of the container, even after the component has rendered.

I read in another answer React “after render” code? that you have to use requestAnimationFrame which I'm trying and it's still not working.

If I log the width of the container it shows a width of 147px which is set using min-width in the stylesheet but the correct width should be 320px which is set using a media query when the screens min-width is 600px.

This is a child component, the parent is rendered inside an iFrame if it makes any difference and the iFrame's width is well over 600px.

The JS:

module.exports = React.createClass({

  componentDidUpdate: function () {
    // Setup marquee
    this.initMarquee();
  },

  render: function () {
    // Setup marquee
    this.initMarquee();

    var artistName = this.props.artist.artistName;
    var trackName  = this.props.track.trackName;

    return (
      <div className="MV-player-trackData no-select" ref="mvpTrackData">
        <div className="MV-player-trackData-marquee-wrap" ref="mvpMarqueeWrap">
          <div className="MV-player-trackData-marquee" ref="mvpMarquee">
            <a className="MV-player-trackData-link no-select" href={this.props.storeUrl} target="_blank">
              <span id="mvArtistName">{artistName}</span> &ndash; <span id="mvTrackName">{trackName}</span>
            </a>
          </div>
        </div>
      </div>
    )
  },

  initMarquee: function () {
    if ( typeof requestAnimationFrame !== 'undefined' ) {
      //store a this ref, and
      var self = this;
      //wait for a paint to setup marquee
      window.requestAnimationFrame(function() {
        self.marquee();
      });
    }
    else {
      // Suport older browsers
      window.setTimeout(this.marquee, 2000);
    }
  },

  marquee: function () {
    var marquee     = React.findDOMNode(this.refs.mvpMarquee);
    var marqueeWrap = React.findDOMNode(this.refs.mvpMarqueeWrap);

    // If the marquee is greater than its container then animate it
    if ( marquee.clientWidth > marqueeWrap.clientWidth ) {
      marquee.className += ' is-animated';
    }
    else {
      marquee.className = marquee.className.replace('is-animated', '');
    }
  }

});

The CSS:

.MV-player-trackData-marquee-wrap {
  height: 20px;
  line-height: 20px;
  min-width: 147px;
  overflow: hidden;
  position: relative;
  width: 100%;

  @media only screen and (min-width : 600px) {
    min-width: 320px;
  }
}

I've tried a number of different solutions including laidout and react-component-width-mixin but neither of them work. I tried react component width mixin because in another part of my app I'm trying to get the value of window.innerWidth but that also returns 0 after rendering, unless I set a timeout for around 2 seconds, unfortunately though sometimes 2 seconds isn't long enough due to data loading and other callbacks so this can brake easily.

Any help is really appreciated. Thanks.

Update: One of the answers correctly pointed out i should be calling this.initMarquee(); inside componentDidMount which I was doing, unfortunately I pasted the wrong code from when I was testing to see if it made a difference calling it inside render. The correct code looks like this:

componentDidMount: function () {
  // Setup marquee
  this.initMarquee();
},

Unfortunately this doesn't work either, I still receive the incorrect width for marqueeWrap.

Update: 24/06/2015 Just to clarify, this is the marquee effect I'm trying to achieve, only when the text is bigger than its container as it's pointless scrolling it when it is not bigger.

Also here is a link to a Github Issue from the React team speaking about why React renders before the browser paints. - So as that is the case, I want to know how do I reliably get the width of the element in question.

回答1:

One possible problem that can occur is that your CSS has not loaded yet when componentDidMount fires. This can happen with webpack and including the css in your bundle that also contains your js even if you have included the css before your component in the project.



回答2:

There are several issues, as you've pointed out, in dealing with the virtual DOM. You definitely don't want to be attempting to use jQuery to manipulate DOM nodes and React is likely to scream at your for attempting to.

There's a library called React-Context which would do exactly what you're asking. You would expose its api to your wrapper component and then be able to listen to events on components within the virtual dom.

This library is a little dusty, however it should work with the code sample you shared.



回答3:

I'm confused by this question, what are you trying to do with the width?

That aside, React is only JavaScript, therefore you should be able to fire some code on your componentDidMount function which is executed after the element has a physical space in your browsers dom. Therefore, just like any JavaScript code you will be able to access that div and get it's width etc...

JavaScript way...

document.getElementById("mydiv").offsetWidth;

jQuery way...

$('#mydiv').width();

If the problem you are trying to solve is dynamically changing the width of a div because it does not look right after it's rendered etc... this should really be done using CSS.



回答4:

You should not call this.initMarquee(); in the render() method.

In general, you should not work with the DOM at all in the render() method.

Try to call this.initMarquee(); in the componentDidMount method.

(and I really don't understand the usage of requestAnimationFrame in this case)