【问题标题】:How to achieve infinite scrolling in React with variable height elements?如何在 React 中使用可变高度元素实现无限滚动?
【发布时间】:2016-06-21 22:47:19
【问题描述】:

我正在尝试在 React (0.14)/Omniscient 应用程序中实现无限滚动。到目前为止,我一直在使用 react-infinity 库的修改版本,但它需要知道元素高度。如何实现动态元素高度的无限滚动?

我已经部署了一个名为InfinitelyScrolling 的演示应用程序来展示我正在尝试做的事情,其源代码位于GitHub。这个应用程序演示了不同高度的元素,react-infinity 强制每个元素具有一定的高度。

我修改过的实现无限滚动的 react-infinity 源代码如下:

'use strict';
var React = require('react');
let s = require('react-prefixr')

function realRender(direction) {
  var windowWidth = this.state.windowWidth;
  var windowHeight = this.state.windowHeight;
  var elementHeight = this.props.mobileWidth <= windowWidth ? this.props.elementHeight :
    this.props.elementMobileHeight;

  var windowY, elementY;
  if (direction === 'vertical') {
    windowY = windowHeight;
    elementY = elementHeight;
  } else {
    windowY = windowWidth;
    elementY = elementWidth;
  }

  var numElements = 1;

  // Number of pixels the container has been scrolled from the top
  var scrollStart = this.state.scrollTop - this.props.scrollDelta;
  console.log(`Scrolled ${this.state.scrollTop} pixels from the top`)
  var numBefore = Math.floor(scrollStart / elementHeight);
  console.log(`Number of elements before scroll start: ${numBefore}`)
  var numVisible = Math.ceil((numBefore * elementY + windowY) / elementY);
  console.log(`Number of visible elements: ${numVisible}`)

  // Keep some extra elements before and after visible elements
  var extra = numElements === 1 ? Math.ceil(numVisible / 2) : 2;
  var lowerLimit = (numBefore - extra) * numElements;
  var higherLimit = (numVisible + extra * 2) * numElements;
  console.log(`Lower limit: ${lowerLimit}, higherLimit: ${higherLimit}, extra: ${extra}`)

  var elementsToRender = [];
  this.props.data.forEach(function (obj, index) {
    if (index >= lowerLimit && index < higherLimit) {
      console.log(`Rendering data item ${index}:`, obj)
      var column, row;
      if (direction === 'vertical') {
        column = index % numElements;
        row = Math.floor(index / numElements);
      } else {
        row = index % numElements;
        column = Math.floor(index / numElements);
      }
      var id = obj.id != null ? obj.id : obj._id;
      var yOffset = (row * elementHeight);
      var subContainer = SubContainer(
        {
          key: id,
          transform: 'translate(0, ' + yOffset + 'px)',
          height: elementHeight + 'px',
        },
        this.props.childComponent(obj)
      );
      elementsToRender.push(subContainer);
    } else {
      console.log(`Skipping data item ${index}`)
    }
  }.bind(this));

  return React.createElement(this.props.containerComponent,
    {
      className: 'infinite-container', style: {
        height: (elementHeight * Math.ceil(this.props.data.length / numElements)) + 'px',
        width: '100%',
        position: 'relative',
      },
    },
    elementsToRender
  );
}

var SubContainer = React.createFactory(React.createClass({
  displayName: 'Sub-Infinity',
  getInitialState: function () {
    return {
      transform: this.props.transform + ' scale(1)',
      opacity: '0',
    };
  },
  componentDidMount: function (argument) {
    this.setState({transform: this.props.transform + ' scale(1)', opacity: '1',});
  },
  componentWillReceiveProps: function (newProps) {
    this.setState({transform: newProps.transform + ' scale(1)',});
  },
  componentWillEnter: function (cb) {
    this.setState({transform: this.props.transform + ' scale(1)', opacity: '0',});
    setTimeout(cb, 100);
  },
  componentDidEnter: function () {
    this.setState({transform: this.props.transform + ' scale(1)', opacity: '1',});
  },
  componentWillLeave: function (cb) {
    this.setState({transform: this.props.transform + ' scale(1)', opacity: '0',});
    setTimeout(cb, 400);
  },
  render: function () {
    return React.DOM.div({style: s({
      position: 'absolute',
      top: '0',
      left: '0',
      transform: this.state.transform,
      width: this.props.width,
      height: this.props.height,
      transition: this.props.transition,
      opacity: this.state.opacity,
    }),},
      this.props.children
    );
  },
}));

var Infinite = React.createClass({
  displayName: 'React-Infinity',

  getDefaultProps: function () {
    return {
      data: [],
      maxColumns: 100,
      transition: '0.5s ease',
      id: null,
      className: 'infinite-container',
      elementClassName: '',
      component: 'div',
      containerComponent: 'div',
      mobileWidth: 480,
      justifyOnMobile: true,
      scrollDelta: 0,
      direction: 'vertical',
      preRender: false,
    };
  },

  propTypes: {
    data: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
    maxColumns: React.PropTypes.number,
    id: React.PropTypes.string,
    className: React.PropTypes.string,
    elementHeight: React.PropTypes.number,
    mobileWidth: React.PropTypes.number,
    elementMobileHeight: React.PropTypes.number,
    elementMobileWidth: React.PropTypes.number,
    justifyOnMobile: React.PropTypes.bool,
    preRender: React.PropTypes.bool,
    scrollDelta: React.PropTypes.number,
  },

  getInitialState: function () {
    return {
      scrollTop: 0,
      windowWidth: this.props.windowWidth || 800,
      windowHeight: this.props.windowHeight || 600,
      loaded: false,
      extra: {
        count: 0,
      },
    };
  },

  componentDidMount: function () {
    global.addEventListener('resize', this.onResize);

    global.addEventListener('scroll', this.onScroll);
    this.onScroll()

    this.setState({
      loaded: true,
      windowWidth: global.innerWidth,
      windowHeight: global.innerHeight,
      elementHeight: this.props.elementHeight ||
        this.refs.element1.getDOMNode().getClientRects()[0].height,
      scrollTop: global.scrollY || 0,
    });
  },

  onScroll: function () {
    var scrollTop = global.scrollY;

    if (this.state.scrollTop !== scrollTop) {
      this.setState({scrollTop: scrollTop,});
    }
  },

  onResize: function () {
    this.setState({windowHeight: global.innerHeight, windowWidth: global.innerWidth,});
  },

  componentWillUnmount: function () {
    global.removeEventListener('resize', this.onResize);
    global.removeEventListener('scroll', this.onScroll);
  },

  render: function(){
    if (!this.state.loaded) {
      return this.props.preRender ? React.createElement(this.props.containerComponent,
        {
          className: this.props.className,
          id: this.props.id,
          style: {
            fontSize: '0',
            position: 'relative',
          },
        }, this.props.data.map(function (elementData, i) {
          return React.createElement(this.props.component, {
            style: {display: 'inline-block', margin: '32px', verticalAlign: 'top',},
          }, React.createElement(this.props.childComponent, elementData));
        }.bind(this)))
        : null;
    }

    var direction = this.props.direction;
    if (direction !== 'horizontal' && direction !== 'vertical') {
      direction = 'vertical';
      console.warn('the prop `direction` must be either "vertical" or "horizontal". It is set to',
        direction);
    }
    return realRender.call(this, direction);
  },
});

module.exports = Infinite;

【问题讨论】:

    标签: javascript reactjs infinite-scroll


    【解决方案1】:

    我已经在一个 flex 容器中使用多个砌体组件(每个组件的高度可变,图像来自互联网)实现了无限滚动。

    我使用了 react-list (https://github.com/orgsync/react-list)。

    这是一个例子:

    import loadAccount from 'my-account-loader';
    import React from 'react';
    import ReactList from 'react-list';
    
    class MyComponent extends React.Component {
      state = {
        accounts: []
      };
    
      componentWillMount() {
        loadAccounts(this.handleAccounts);
      }
    
      handleAccounts(accounts) {
        this.setState({accounts});
      }
    
      renderItem(index, key) {
        return <div key={key}>{this.state.accounts[index].name}</div>;
      }
    
      render() {
        return (
          <div>
            <h1>Accounts</h1>
            <div style={{overflow: 'auto', maxHeight: 400}}>
              <ReactList
                itemRenderer={this.renderItem}
                length={this.state.accounts.length}
                type='variable'
              />
            </div>
          </div>
        );
      }
    }

    如果元素高度可变,则类型必须设置为variable

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-12
      • 2015-05-18
      • 2017-08-16
      • 1970-01-01
      • 1970-01-01
      • 2019-09-23
      相关资源
      最近更新 更多