-
Notifications
You must be signed in to change notification settings - Fork 273
Rapid glitching on demo page #248
Comments
I can confirm this behavior in my local dev environment. It appears that the scroll event is caught in some loop. This also makes anything un-clickable while the glitch occurs. |
The source of the bug:
|
I also found this problem in the project. Do you have any good ways? |
I can confirm this behavior is happening to a project of mine as well. @ido1wald seems to be onto something. I'm not handling any events. Anyone has a solution to this? |
@duhaime @bj7 @ido1wald @BlackSkyJY @KendallWeihe Is anyone found any solution? I have it too and it's blocker, and I even can't find any hack-around... |
@Timopheym sorry brother, we re-created this entire component in our private repo, from scratch. |
@ido1wald Godness... is there any open-source alternative or may be not buggy tag of this one?... |
@Timopheym I too would roll my own. If you did and it were general enough you could open source it and carry the mantle! |
@duhaime I managed it with integration of https://github.com/bvaughn/react-virtualized/ currently i have some issues, but i hope to make well. |
The idea behind this component is surely interesting. At any given moment only some of the items are mounted. The rest are replaced by two divs (top and bottom spacers) of height the corresponding items would take. Initially only, say, the first 5 is mounted ( This component might suit well cases where heights of the items are known in advance. But when they aren't, especially when the items contain images (not to mention videos), it takes much more effort to make it work. What's the big deal about providing accurate heights of the items? The thing is that otherwise every time you cross the line where the first item (of the mounted bunch) gets unmounted, and the next one (after the mounted bunch) gets mounted, the thumb of the scrollbar twitches. Since Also if you mess up to provide Let's see what issues I've faced. Imagine a list of posts, handled by two components:
import React from 'react';
import Infinite from 'react-infinite';
import Post from './Post';
const defaultHeight = 300;
export default class Posts extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: [],
isInfiniteLoading: false,
elementHeights: defaultHeight,
};
this.noMorePosts = false;
this.offset = 0;
this.pageSize = 5;
this.postHeights = {};
}
componentDidMount() {
this.fetchPosts();
}
fetchPosts() {
api(this.offset, this.pageSize).then(({data}) => {
const newElementHeights = Array(data.length).fill(defaultHeight);
this.setState(prevState => ({
posts: prevState.posts.concat(data),
elementHeights: typeof prevState.elementHeights === 'number'
? newElementHeights
: prevState.elementHeights.concat(newElementHeights),
isInfiniteLoading: false,
}));
this.noMorePosts = data.length === 0;
})
this.offset += this.pageSize;
}
// posts report their heights to the Posts component
handlePostHeight = (id, i, height) => {
if (id in this.postHeights)
return;
this.postHeights[id] = height;
if (typeof this.state.elementHeights === 'number')
this.setState({elementHeights:
Array(this.state.posts.length).fill(defaultHeight)
.fill(height, i, i + 1)});
else
this.setState(prevState => ({elementHeights:
prevState.elementHeights.fill(height, i, i + 1)}));
}
handleInfiniteLoad = () => {
if (this.state.isInfiniteLoading || this.noMorePosts)
return;
this.setState({isInfiniteLoading: true});
this.fetchPosts();
}
// not a good way to use callback refs probably
handleCallbackRef = post => {
if ('postSpacingHeight' in this)
return;
this.postSpacingHeight = 0; // this line makes it reproducable (inaccurate height)
if (post.props.i !== 0) {
// here we get reference to non-first post
// the one which has, say, margin-top
// or in other words, non-zero spacing height
this.postSpacingHeight = post.getSpacingHeight();
}
}
render() {
return <div>
<Infinite elementHeight={this.state.elementHeights}
onInfiniteLoad={this.handleInfiniteLoad}
infiniteLoadBeginEdgeOffset={200}
useWindowAsScrollContainer={true}
isInfiniteLoading={this.state.isInfiniteLoading}>
{this.state.posts.map((p, i) =>
<Post i={i} key={p.id} attrs={p}
minHeight={
/* set min-height after height is known
to set it right away next time it mounts
to not wait for height to be calculated
to not wait for image in a post to load */
p.id in this.postHeights
? this.postHeights[p.id] - this.postSpacingHeight || 0 + 'px'
: 'auto'
}
onPostHeight={this.handlePostHeight}
ref={this.handleCallbackRef}/>
)}
</Infinite>
</div>;
}
}
const heights = [4, 89, 166, 315, 436, 23, 153, 337, 157, 340, 563, 417, 315, 351, 28, 156, 64, 123, 555, 578, 501, 218, 429, 559, 229, 213, 243, 346, 119, 521, 142, 102, 534, 313, 398, 385, 407, 230, 307, 472, 26, 374, 411, 495, 303, 397, 369, 232, 75, 340];
function api(offset, pageSize) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: heights.slice(offset, offset + pageSize).map((h, i) => ({
id: offset + i + 1,
url: `http://via.placeholder.com/300x${h}`,
}))
});
}, Math.random() * 2000);
});
}
import React from 'react';
import imagesLoaded from 'imagesloaded';
import computedStyle from 'computed-style';
import classNames from 'classnames';
import './Post.css';
export default class Post extends React.Component {
constructor(props) {
super(props);
this.selfRef = React.createRef();
this.wrapperRef = React.createRef();
}
componentDidMount() {
this.mounted = true;
imagesLoaded(this.selfRef.current, () => {
if (this.mounted) { // image might finish loading after component has unmounted
this.props.onPostHeight(this.props.attrs.id, this.props.i,
this.selfRef.current.offsetHeight);
}
});
}
componentWillUnmount() {
this.mounted = false;
}
getSpacingHeight() {
return this.selfRef.current.offsetHeight
- (this.wrapperRef.current.offsetHeight
- parseFloat(computedStyle(this.wrapperRef.current, 'border-top-width'))
- parseFloat(computedStyle(this.wrapperRef.current, 'padding-top')));
}
render() {
return <div
className={classNames('post', this.props.i === 0 && 'first')}
data-i={this.props.i} ref={this.selfRef}>
<div className="wrapper" ref={this.wrapperRef} style={{
minHeight: this.props.minHeight,
}}>
<img src={this.props.attrs.url} alt=""/>
</div>
</div>;
}
}
.post {
padding-top: 20px;
}
.post.first {
padding-top: 0;
}
.wrapper {
border-top: 1px solid #999;
padding-top: 20px;
}
.post.first .wrapper {
border-top: none;
padding-top: 0;
}
.post img {
display: block;
margin: auto;
} Click down arrow until it starts to twitch. You can find the needed files in this archive. Do note, every time heights change (add, edit, delete), you've got to make them known to I seem to have managed to make it work, but the code is way too complex. I decided, that if the user scrolls that much that it affects performance, the issue is either with the user (doesn't know when to give up), or with the user interface (lack of appropriate filters). So I'm going to try one of these. |
Just FYI, the demo page displays a strange rapid glitch when a user has scrolled to a particular location on the page:
As you can see at the end of the gif, the glitching disappears if one scrolls up (or down) just slightly from the mysterious spot.
This was observed in recent Chrome on OSX.
The text was updated successfully, but these errors were encountered: