在滚动事件上反应更改图像源

时间:2018-07-08 22:44:17

标签: javascript css reactjs

作为React的新手,但熟悉JavaScript的人,在确定将某些功能确切地放置在给定的React组件中的确切位置时遇到了一些麻烦,并且不确定我遇到的这个特定问题是否与我的问题有关反应逻辑或加载图像时只是一个滞后时间,尽管如果是后者,我不理解这一点,因为我将图像预加载到了隐藏的div中。

我期望的工作流程如下:

  1. 加载两张图像,其中一幅height:0hide类的一部分“预加载”。
  2. 当窗口滚动时,有效地在这两个图像中的任何一个上切换hide类,以便根据滚动位置有效地更改显示哪个图像。发生这种情况时,标题的背景颜色也会发生变化。

在组件Header.jsx中,我具有以下内容:

componentDidMount() {
    if (this.props.changeColorOnScroll) {
      window.addEventListener("scroll", this.headerColorChange);
    }
  }
  headerColorChange() {
    console.log("Scroll Fire!");
    const { classes, color, changeColorOnScroll} = this.props;
    const windowsScrollTop = window.pageYOffset;
    if (windowsScrollTop > changeColorOnScroll.height) {
      document.body
        .getElementsByTagName("header")[0]
        .classList.remove(classes[color]);
      document.body
        .getElementsByTagName("header")[0]
        .classList.add(classes[changeColorOnScroll.color]);
     document
        .getElementById("headerLogoContainer").getElementsByClassName("logo")[0]
        .classList.add('hide');
     document
        .getElementById("headerLogoContainer").getElementsByClassName("logoalt")[0]
        .classList.remove('hide');
    } else {
      document.body
        .getElementsByTagName("header")[0]
        .classList.add(classes[color]);
      document.body
        .getElementsByTagName("header")[0]
        .classList.remove(classes[changeColorOnScroll.color]);
     document
        .getElementById("headerLogoContainer").getElementsByClassName("logo")[0]
        .classList.remove('hide');
     document
        .getElementById("headerLogoContainer").getElementsByClassName("logoalt")[0]
        .classList.add('hide');
    }
  } 

我认为这是在componentDidMount()上附加事件侦听器的正确位置。我的问题是,当我滚动时,即使我在此处预加载图像,窗口也会随着图像的变化而冻结:

render() {
    const {
      classes,
      color,
      rightLinks,
      leftLinks,
      brand,
      logo,
      logoalt,
      fixed,
      absolute
    } = this.props;
    const appBarClasses = classNames({
      [classes.appBar]: true,
      [classes[color]]: color,
      [classes.absolute]: absolute,
      [classes.fixed]: fixed
    });
    let brandComponent;
    if(!logo){
    brandComponent = (
      <Button className={classes.title}>
        {brand}
      </Button>
    );
    }else{
    brandComponent = (
      <div id='headerLogoContainer'>
      <img className='logo'  src={logo}></img>
      <img className='logoalt hide'  src={logoalt}></img>
      </div>
    );
    }
    return (
      <AppBar className={appBarClasses}>
        <Toolbar className={classes.container}>
          {leftLinks !== undefined ? brandComponent : null}
          <div className={classes.flex}>
            {leftLinks !== undefined ? (
              <Hidden smDown implementation="css">
                {leftLinks}
              </Hidden>
            ) : (
              brandComponent
            )}
          </div>

如您所见,在componentDidMount()中设置事件监听器,然后在渲染中将<div id='headerLogoContainer'>设置为brandComponent的一部分。我看到brandComponent成功呈现到DOM,但是在第一次滚动(向下)时,它锁定了屏幕(在Chrome中)或没有立即显示logoalt(在Edge和Firefox中) 。几秒钟后,图像切换,然后我可以向上或向下滚动,毫无问题地切换它。这使我认为这可能是一个预加载问题,但是当我将图像预加载到brandComponent中并在DOM中看到它时,为什么会这样呢?无需切换其他网络请求,只需切换hide图片即可显示。

所以我的问题是,如何基于滚动事件在React中更改图像源,而不会滞后或冻结滚动位置?

编辑:

我已经根据建议的答案更改了代码以利用内部状态,但是当图像更改时,它仍然具有滞后的效果:

class Header extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      mobileOpen: false,
      logoaltState: false
    };
    this.handleDrawerToggle = this.handleDrawerToggle.bind(this);
    this.headerColorChange = this.headerColorChange.bind(this);
  }
  handleDrawerToggle() {
    this.setState({ mobileOpen: !this.state.mobileOpen });
  }
  componentDidMount() {
    if (this.props.changeColorOnScroll) {
      window.addEventListener("scroll", this.headerColorChange);
    }
  }
  headerColorChange() {
    console.log("Scroll Fire!");
    const { classes, color, changeColorOnScroll} = this.props;
    const windowsScrollTop = window.pageYOffset;
    if (windowsScrollTop > changeColorOnScroll.height) {
      document.body
        .getElementsByTagName("header")[0]
        .classList.remove(classes[color]);
      document.body
        .getElementsByTagName("header")[0]
        .classList.add(classes[changeColorOnScroll.color]);
        this.setState({ logoaltState: true });
    } else {
      document.body
        .getElementsByTagName("header")[0]
        .classList.add(classes[color]);
      document.body
        .getElementsByTagName("header")[0]
        .classList.remove(classes[changeColorOnScroll.color]);
      this.setState({ logoaltState: false });
    }
  }

然后在我的渲染中...

render() {
    const {
      classes,
      color,
      rightLinks,
      leftLinks,
      brand,
      logo,
      logoalt,
      fixed,
      absolute
    } = this.props;
    const{
        mobileOpen,
        logoaltState
    } = this.state
    const appBarClasses = classNames({
      [classes.appBar]: true,
      [classes[color]]: color,
      [classes.absolute]: absolute,
      [classes.fixed]: fixed
    });
    let brandComponent;
    if(!logo){
    brandComponent = (
      <Button className={classes.title}>
        {brand}
      </Button>
    );
    }else{
    brandComponent = (
      <div id='headerLogoContainer'>
      <img className='logo'  src={logo} style={{display: logoaltState === false ? "block" : "none"}}></img>
      <img className='logoalt'   src={logoalt} style={{display: logoaltState === true ? "block" : "none"}}></img>
      </div>
    );
    }

不同的代码/方法,同样的问题...

编辑2:

我不认为这是应用程序逻辑的问题,因为正如我提到的,它只会冻结 第一次 ,图像/状态需要更改。我认为这是某种首次加载的问题,即使我没有任何网络请求,也可能是我错误地包含了图像源?还是在React中,图像需要通过其他方法进行预加载,因为包含图像源时,图像源会略微关闭。例如/static/media/logo.ad924167.pngrequire("assets/img/tovlogo.png"相反,我认为这是由于我的节点服务器设置,但是再次,我不太确定发生了什么,因为我对这个生态系统还很陌生……我使用的是 Material UI React Template ,即该组件的来源。

主题文件本身是这样的:https://www.creative-tim.com/product/material-kit-react?partner=104080,因此我想知道它是如何处理图像的,因为实际的src相对于require的性质...但是再次没有额外的图像请求...这只是CSS /样式更改...

编辑3:

这是我的调试器(Chrome:Performance)冻结时的外观:

debugger

我发现查看绘画时间时图像很大,但是不应该预加载地址……我想不是因为这是网络预加载而不是display:none;"的绘画预加载...也许我可以改用opacity CSS属性,但这会使其他CSS变得复杂...通常有更好的方法吗?

最终编辑:

我的图像超过10,000像素宽...我将其用作徽标,因此直到我执行性能指标后才意识到它有多糟糕...我猜这是 是冻结的原因...对不起React。 :(

1 个答案:

答案 0 :(得分:5)

我认为,如果您代替使用组件状态并根据滚动位置在活动图像之间进行切换,那么您可以更轻松地代替DOM元素上的类。

示例

total
class App extends React.Component {
  state = { active: 0 };

  componentDidMount() {
    window.addEventListener("scroll", this.handleScroll);
  }

  componentWillUnmount() {
    window.removeEventListener("scroll", this.handleScroll);
  }

  handleScroll = event => {
    const { pageYOffset } = window;
    const { active } = this.state;

    if (pageYOffset >= 500 && active === 0) {
      this.setState({ active: 1 });
    } else if (pageYOffset < 500 && active === 1) {
      this.setState({ active: 0 });
    }
  };

  render() {
    const { active } = this.state;
    return (
      <div style={{ height: 1000, width: 1000 }}>
        <div
          style={{
            height: "100%",
            width: "100%",
            backgroundColor: "red",
            display: active === 0 ? "block" : "none"
          }}
        />
        <div
          style={{
            height: "100%",
            width: "100%",
            backgroundColor: "blue",
            display: active === 1 ? "block" : "none"
          }}
        />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));