I'm trying to create a calendar that can be infinitely scrolled to the left and to the right. It has to load new content dynamically as the user scrolls forward (easy) or backward (the problem is here).
When I add content to the end of the page, it works fine - the scrollbar adjusts to the new container.scrollWidth
. But when I have to add content to the start, the whole calendar moves to the right in a huge 400px jump, because the container.scrollLeft
property hasn't changed and there's now a new element at the start.
I'm trying to mitigate this by increasing the container.scrollLeft
by 400px - width of the newly created element. This approach works, but only when scrolling with mousewheel (shift+mousewheel to scroll sideways) or mobile touchscreen.
If I use my mouse to drag the scrollbar, it kind of glitches out - my guess is it keeps trying to scroll to the old scrollLeft
position and disregards that I increased it.
Could you please suggest a way to achieve this behavior for all ways of scrolling?
It would also be great if you could just point me to a site that uses this technique so I could investigate myself.
Here's my semi-working example:
function Container() {
const rectangleWidth = 400;
const container = React.useRef(null);
const [leftRectangles, setLeftRectangles] = React.useState([0]);
const [rightRectangles, setRightRectangles] = React.useState([1, 2, 3, 4]);
// When we just rendered a new rectangle in the left of our container,
// move the scroll position back
React.useEffect(() => {
container.current.scrollLeft += rectangleWidth;
}, [leftRectangles]);
const loadLeftRectangle = () => {
const newAddress = leftRectangles[0] - 1;
setLeftRectangles([newAddress, ...leftRectangles]);
};
const loadRightRectangle = () => {
const newAddress = rightRectangles[rightRectangles.length - 1] + 1;
setRightRectangles([...rightRectangles, newAddress]);
};
const handleScroll = (e) => {
// When there is only 100px of content left, load new content
const loadingOffset = 100;
if (e.target.scrollLeft < loadingOffset) {
loadLeftRectangle(e.target);
} else if (e.target.scrollLeft > e.target.scrollWidth - e.target.clientWidth - loadingOffset) {
loadRightRectangle(e.target);
}
};
return (
<div
className="container"
onScroll={handleScroll}
ref={container}
>
{leftRectangles.map((address) => (
<div className="rectangle" key={address}>
{address}
</div>
))}
{rightRectangles.map((address) => (
<div className="rectangle" key={address}>
{address}
</div>
))}
</div>
);
}
ReactDOM.render(<Container />, document.querySelector("#app"))
.container {
display: flex;
overflow-x: scroll;
}
.rectangle {
border: 1px solid #000;
box-sizing: border-box;
flex-shrink: 0;
height: 165px;
width: 400px;
font-size: 50px;
line-height: 165px;
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
An element with position:fixed is fixed with respect to the viewport. It stays where it is, even if the document is scrolled.
If you want to set the scroll position of document. body , you can scroll the entire window altogether using window. scrollTo() ; it takes either a pair of coordinates (x,y) or an options object – if you just want to scroll nicely to the top, try window. scrollTo({top:0,behavior:'smooth'}); .
forward, then scroll offset is the amount the top of the sliver has been scrolled past the top of the viewport. This value is typically used to compute whether this sliver should still protrude into the viewport via SliverGeometry.
A paragraph element is defined within this division element with a height and width greater than that of the div element for the scroll bars to be visible. Another div element is created with the “output” class to show the scroll position of the first division element.
this is typically done by JavaScript adding the current scroll position to the postback data, then the new page renders JavaScript to adjust the scroll position. If it’s a simple page, you just need the scroll position of the body.
Another div element is created with the “output” class to show the scroll position of the first division element. Use the addEventListener () method to attach an event listener to the div element with an id of “scroll-element”. The event to be attached is the scroll event.
The scrollTop property gets or sets the number of pixels that an element’s content is scrolled vertically whereas the scrollLeft property gets or sets the number of pixels that an element’s content is scrolled from its left edge. The innerHTML is repeatedly updated on scrolling through the HTML document.
I think this is a case where one should not use native browser scroll areas. If you think about it, scrollbars have no meaning if they continue to get longer infinitely in both directions. Or their only meaning is "area which has been viewed". A scrollbar is not a good metaphor for an infinite area, and therefore a scrollbox is a poor way to do what you want to do here.
If you think of it another way, you are only showing a set of months which fit within a known screen width. The way I would approach this would be to absolute position each calendar inside a container and calculate their positions in a render loop based on where in a virtual view the user is shuttling. This also would allow you to remove calendars once they go too far off screen and create/buffer them offscreen for display before the user scrolls to their position. It would prevent you from having an arbitrarily wide object which would eventually slow down rendering.
One approach to this would be to simply number each month since 1970 and treat your view as having a fractional position viewX
along this line. The month for x
should have a position of viewX - x
. When the user is dragging you move viewX
inversely and reposition the buffered elements. When the user stops dragging, you take the last velocity and slow it until viewX - x
is an integer.
This would avoid most cross-browser issues and offset problems. It only requires a render loop while the display is in motion.
Use the read-write
scrollLeft
to get the scroll position prior to dynamically adding content then use the .scrollLeft
method to reposition the scroll position back to where you want it. You might need fire off a dialog displaying an indeterminant progress indicator ( or simply display text "working..." ) which displays during the process to prevent janking.
The trick for cross browser functionality is that dialog
element which is well known to be challenging regarding consistency across device types so I would recommend using a UI library for your dialog or build your own but keep it super simple. That progress indicator will be the fix for screen jank.
Another feature to consider regarding janking would be CSS transitions where the addition of content (e.g. a block element) would gradually fade/animate in to the viewport.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With