Giter Club home page Giter Club logo

Comments (10)

petyosi avatar petyosi commented on July 30, 2024 2

This is not the first time this is requested. It's fairly trivial to expose a callback for that. I will try to get to that in the upcoming weeks.

Cheers,

from react-virtuoso.

Ethorsen avatar Ethorsen commented on July 30, 2024

Came up with that solution, I'm getting the scroller based on its className. This works without the need of the customer scroll component.

Still not the best solution IMO, but it does work well enough for now.

const [backToTopShown, setBackToTopShown] = useState(false);

  const handleScrollingStateChange = useCallback((isScrolling) => {
    if (!isScrolling) {
      const listHeight = componentSize.height;
      const scroller = document.getElementsByClassName(classes.virtuoso)[0];

      // Check if we are above or below the threshold to show Back-to-Top button
      if (scroller && scroller.scrollTop > listHeight * 1.2) {
        !backToTopShown && setBackToTopShown(true);
      }
      else {
        backToTopShown && setBackToTopShown(false);
      }
    }
  }, [backToTopShown, classes, componentSize.height]);

from react-virtuoso.

Ethorsen avatar Ethorsen commented on July 30, 2024

Amazing.

I did commit my code for now and will create another task to revisit these once available in upcoming releases.

Thx again

from react-virtuoso.

petyosi avatar petyosi commented on July 30, 2024

Hi @Ethorsen ,

The new release exposed a rangeChanged callback. You can use that to display the button if the list range startIndex is higher than a certain value. Let me know if this works for you. Check this example.

Thanks!

from react-virtuoso.

nuintun avatar nuintun commented on July 30, 2024

@Ethorsen Same request ~ 3Q

from react-virtuoso.

petyosi avatar petyosi commented on July 30, 2024

@nuintun - did you check the callback I exposed? If it does not work for you, let me know your use case.

from react-virtuoso.

nuintun avatar nuintun commented on July 30, 2024

@petyosi

I write a component use pull refresh and virtuoso, when I drag down ,pull refresh will be triggered anywhere, but I just need to trigger pull refresh when scrollTop is 0!

see:

import styles from './index.module.less';

import React from 'react';
import ReactDOM from 'react-dom';
import propTypes from 'prop-types';
import classNames from 'classnames';
import { Virtuoso } from 'react-virtuoso';

const STATS = {
  INIT: styles.stateInit,
  RESET: styles.stateReset,
  LOADING: styles.stateLoading,
  PULLING: styles.statePulling,
  REFRESHED: styles.stateRefreshed,
  REFRESHING: styles.stateRefreshing,
  ENOUGH: `${styles.statePulling} ${styles.enough}`
};

export const PROGRESS = {
  DISABLE: 0,
  START: 1,
  DONE: 2
};

// 拖拽的缓动公式 - easeOutSine
function easing(distance) {
  // Current time
  const t = distance;
  // BegInnIng value
  const b = 0;
  // Duration
  // 允许拖拽的最大距离
  const d = window.screen.availHeight;
  // Change In value
  // 提示标签最大有效拖拽距离
  const c = d / 3.5;

  return c * Math.sin((t / d) * (Math.PI / 2)) + b;
}

// Test via a getter in the options object to see
// if the passive property is accessed
let supportsPassive = false;

try {
  const options = Object.defineProperty({}, 'passive', {
    get: () => (supportsPassive = true)
  });

  window.addEventListener('test', null, options);
} catch (e) {
  // Do nothing
}

const willPreventDefault = supportsPassive ? { passive: false } : false;

// Pull to refresh
// Tap bottom to load more
export default class VirtualList extends React.PureComponent {
  static defaultProps = {
    overscan: 1,
    autoLoadMore: true,
    refreshThreshold: 72,
    progress: PROGRESS.DISABLE,
    placeholder: <div className={styles.noData}>暂无数据</div>
  };

  /**
   * @property propTypes
   */
  static propTypes = {
    hasMore: propTypes.bool,
    onRefresh: propTypes.func,
    onLoadMore: propTypes.func,
    autoLoadMore: propTypes.bool,
    data: propTypes.array.isRequired,
    refreshThreshold: propTypes.number,
    children: propTypes.func.isRequired,
    placeholder: propTypes.oneOfType([propTypes.string, propTypes.element]),
    progress: propTypes.oneOf([PROGRESS.DISABLE, PROGRESS.START, PROGRESS.DONE])
  };

  state = {
    pullHeight: 0,
    status: STATS.INIT
  };

  bodyRef = React.createRef();

  vListRef = React.createRef();

  initialTouch = { clientY: 0, scrollTop: 0 };

  vListStyles = { width: '100%', height: '100%' };

  getClassName() {
    const { status } = this.state;
    const { className, progress } = this.props;

    return classNames(className, styles.vList, status, {
      [styles.vListProgress]: progress !== PROGRESS.DISABLE,
      [styles.progressCompleted]: progress === PROGRESS.DONE
    });
  }

  getSymbolStyle() {
    const { pullHeight } = this.state;

    if (pullHeight) {
      const height = Math.max(48, pullHeight);

      return { height, lineHeight: `${height}px` };
    }
  }

  getBodyStyle() {
    const { pullHeight } = this.state;

    if (pullHeight) {
      const transform = `translate3d(0, ${pullHeight}px, 0)`;

      return { transform };
    }
  }

  canLoad() {
    const { status } = this.state;

    return status !== STATS.REFRESHING && status !== STATS.LOADING;
  }

  canLoadMore() {
    const { hasMore, onLoadMore } = this.props;

    return hasMore && onLoadMore && this.canLoad();
  }

  canRefresh() {
    const { onRefresh } = this.props;
    const vList = this.vListRef.current;
    const atTop = !vList || ReactDOM.findDOMNode(vList).scrollTop === 0;

    return atTop && onRefresh && this.canLoad();
  }

  calculateDistance(touch) {
    return touch.clientY - this.initialTouch.clientY;
  }

  loadMore = () => {
    this.setState({ status: STATS.LOADING });
    this.props.onLoadMore(() => this.setState({ status: STATS.INIT }));
  };

  onTouchStart = e => {
    if (this.canRefresh() && e.touches.length === 1) {
      const { scrollTop } = this.bodyRef.current;

      this.initialTouch = { scrollTop, clientY: e.touches[0].clientY };
    }
  };

  onTouchMove = e => {
    if (e.cancelable && this.canRefresh()) {
      const { refreshThreshold } = this.props;
      const { scrollTop } = this.bodyRef.current;
      const distance = this.calculateDistance(e.touches[0]);

      if (distance > 0 && scrollTop <= 0) {
        let pullDistance = distance - this.initialTouch.scrollTop;

        if (pullDistance < 0) {
          // 修复 webview 滚动过程中 touchstart 时计算 viewport.scrollTop 不准
          pullDistance = 0;

          this.initialTouch.scrollTop = distance;
        }

        const pullHeight = easing(pullDistance);

        // 减弱滚动
        pullHeight && e.preventDefault();

        this.setState({ pullHeight, status: pullHeight >= refreshThreshold ? STATS.ENOUGH : STATS.PULLING });
      }
    }
  };

  onTouchEnd = () => {
    if (this.canRefresh()) {
      if (this.state.status === STATS.ENOUGH) {
        // Refreshing
        this.setState({ pullHeight: 0, status: STATS.REFRESHING });
      } else if (!this.bodyRef.current.scrollTop) {
        // Reset
        this.setState({ pullHeight: 0, status: STATS.RESET });
      } else {
        this.setState({ pullHeight: 0, status: STATS.INIT });
      }
    }
  };

  onTransitionEnd = e => {
    // Only body self transition can trigger events
    if (e.target === this.bodyRef.current) {
      switch (this.state.status) {
        // Trigger refresh action
        case STATS.REFRESHING:
          this.props.onRefresh(
            () => {
              this.setState({ pullHeight: 0, status: STATS.REFRESHED });
              // Close success message after 300ms
              setTimeout(() => this.setState({ status: STATS.INIT }), 300);
            },
            () => this.setState({ pullHeight: 0, status: STATS.RESET })
          );
          break;
        case STATS.RESET:
          this.setState({ status: STATS.INIT });
          break;
      }
    }
  };

  onReachEnd = () => {
    const { autoLoadMore } = this.props;

    autoLoadMore && this.canLoadMore() && this.loadMore();
  };

  componentDidMount() {
    const { bodyRef } = this;
    const { autoLoadMore } = this.props;

    autoLoadMore && this.canLoadMore() && this.loadMore();

    this.initialTouch.scrollTop = bodyRef.current.scrollTop;

    bodyRef.current.addEventListener('touchstart', this.onTouchStart, willPreventDefault);
    bodyRef.current.addEventListener('touchmove', this.onTouchMove, willPreventDefault);
    bodyRef.current.addEventListener('touchend', this.onTouchEnd, willPreventDefault);
    bodyRef.current.addEventListener('touchcancel', this.onTouchEnd, willPreventDefault);
  }

  componentWillUnmount() {
    const { bodyRef } = this;

    bodyRef.current.removeEventListener('touchstart', this.onTouchStart);
    bodyRef.current.removeEventListener('touchmove', this.onTouchMove);
    bodyRef.current.removeEventListener('touchend', this.onTouchEnd);
    bodyRef.current.removeEventListener('touchcancel', this.onTouchEnd);
  }

  renderRows = index => {
    const { data, children } = this.props;

    return children(data[index]);
  };

  renderFooter = () => {
    const { hasMore } = this.props;

    if (!hasMore) return null;

    return (
      <div className={styles.vListFooter}>
        <div className={styles.vListBtn} onClick={this.loadMore} />
        <div className={styles.vListLoading}>
          <i className={styles.spinning} />
        </div>
      </div>
    );
  };

  renderBody = () => {
    const { data, style, className, hasMore, placeholder, ...restProps } = this.props;

    const totalCount = data.length;

    if (totalCount) {
      return (
        <Virtuoso
          {...restProps}
          ref={this.vListRef}
          item={this.renderRows}
          totalCount={totalCount}
          style={this.vListStyles}
          footer={this.renderFooter}
          endReached={this.onReachEnd}
        />
      );
    }

    return hasMore ? null : placeholder;
  };

  render() {
    const { style } = this.props;

    return (
      <div style={style} className={this.getClassName()}>
        <div className={styles.vListSymbol} style={this.getSymbolStyle()}>
          <div className={styles.vListMsg}>
            <i />
          </div>
          <div className={styles.vListLoading}>
            <i className={styles.spinning} />
          </div>
        </div>
        <div ref={this.bodyRef}
          style={this.getBodyStyle()}
          className={styles.vListBody}
          onTransitionEnd={this.onTransitionEnd}
        >
          {this.renderBody()}
        </div>
      </div>
    );
  }
}

from react-virtuoso.

petyosi avatar petyosi commented on July 30, 2024

@nuintun - You can use custom scroll container for that purpose - you can consume the scrollTop property as you wish. Check this demo:
https://virtuoso.dev/custom-scroll-container/

from react-virtuoso.

nuintun avatar nuintun commented on July 30, 2024

@petyosi My first attempt was use custom scroll container, but I can't get rest props in ScrollContainer, so I gave up using custom scroll container, see #76

from react-virtuoso.

petyosi avatar petyosi commented on July 30, 2024

#76 has a solution.

from react-virtuoso.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.