Comments (10)
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.
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.
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.
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.
@Ethorsen Same request ~ 3Q
from react-virtuoso.
@nuintun - did you check the callback I exposed? If it does not work for you, let me know your use case.
from react-virtuoso.
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.
@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.
@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.
#76 has a solution.
from react-virtuoso.
Related Issues (20)
- How do I customize/add style to the wrapper <DIV> for Header/Footer in react-virtuoso?
- [BUG] - Possible bug with GroupedVirtuoso initialTopMostItemIndex and resizing items HOT 1
- [BUG] Unable to scroll to absolute bottom if there is a footer with position: sticky HOT 9
- How to specify a row in Virtuoso List so that it does not unload when scrolling HOT 2
- [BUG] `restoreStateFrom` appears to have a race condition when restoring state on component mount HOT 8
- React Virtuoso Message List - ref.current.scrollToItem based on the item.data instead of using the item index?
- [BUG] npm run browse-examples HOT 1
- [BUG] Simple header breaks restore state location HOT 3
- [BUG] When used in MUI's Tooltip component, extra white space will appear. HOT 1
- Chrome52 is not compatible HOT 1
- [BUG] Grid jittering/flickering HOT 1
- [BUG] VirtuosoGrid - endReached doesn't call if data set is less then the container HOT 1
- Drag the scrolllbar makes the grid blink in Firefox, but not in Chromium or Edge... HOT 1
- [BUG] react-beautiful-dnd + useWindowScroll HOT 1
- [BUG] followOutput doesn't scroll to bottom HOT 2
- [BUG] element disappearing when reverse scrolling in the ios HOT 4
- [BUG]After packaging a project that uses react-virtuoso, there is a problem embedding it into the iframe of another domain name HOT 2
- [BUG] `InitialTopMostItemIndex` causes variable height elements to blink/jump when scrolling up. HOT 1
- [BUG] Border does not get respected with fixed headers HOT 3
- Drag and drop not working as intended on mobile devices when using react-beautiful-dnd with react-virtuoso HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from react-virtuoso.