Infinite Scroll
التمرير اللانهائيIntersectionObserver
also calledendless scroll، continuous scroll، infinite list، auto load on scroll، endless feed، scroll pagination، سكرول لانهائي، انفينيت سكرول، التمرير المتواصل، التحميل عند التمرير، القائمة اللانهائية، الخلاصة المتواصلة
Infinite scroll fetches the next batch as the reader approaches the end of what is already rendered, then appends that batch to the same page. Scroll position is what fires the request, so the reader never decides that more should load. It is not pagination: pagination gives every slice a fixed address such as ?page=7 and swaps the content out, so the page stays the same size, while an infinite list piles rows into one DOM with no ceiling and no address to return to. It is not a load-more button either, since the mechanics are identical (same request, same append) and the only difference is that the button waits for a press, which by itself removes most of the accessibility damage. Nor is it virtualization, which is a rendering technique that keeps only the rows near the viewport in the DOM and works just as well on data that is already fully loaded, and the two get paired because an infinite list that never recycles rows chokes the page after a few batches. Two questions settle it: will anyone need to link to one specific item, and does anything the user needs live below the list? A yes to either means pagination or a button.
If you called it…
Live specimen
Interact with the demo. Every part is real and numbered.
المعروض ٦ من ١٢
Anatomy: every part, named
Hover a row to locate it in the demo above.
Build prompt
How it behaves right-to-left
This section is ours alone.
Which axis actually flips
never mirrorsA vertical feed does not mirror: page direction rules the inline axis, this list scrolls on the block axis, and row order and the direction of growth come out identical in Arabic and in English. The one trap is rootMargin, which is purely physical, written in CSS margin order (top, right, bottom, left), with no logical keyword anywhere in the API. A vertical list cares only about the third value and it is the same either way, while a horizontal list needs the second value in LTR and the fourth in RTL, so compute it from direction instead of hard-coding it.
The spinner and the skeleton rows
never mirrorsA spinner never mirrors: a blanket [dir="rtl"] svg { transform: scaleX(-1) } reverses its apparent rotation and it reads as spinning backwards. Only its position moves, so pin it with inset-inline-end when it hangs in a corner, or drop it in a flex row and let the inline axis place it. Skeleton rows that shimmer with linear-gradient(to right, …) need the override written by hand, because gradients take no logical keywords at all, so the sweep runs against the reading direction in Arabic until you swap in to left under [dir="rtl"].
The item counter
never mirrorsThe number itself does not flip: the Unicode bidi algorithm classifies digits as EN and AN and keeps them running left to right inside Arabic text, so "60 of 348" holds its internal order with no work from you. The one decision left is the numeral system, and it has to hold everywhere: new Intl.NumberFormat("ar-EG").format(348) returns Arabic-Indic digits, while adding -u-nu-latn to the tag returns Latin ones. Do not mix the two on one screen, because an Arabic-Indic counter above Latin row numbers reads as two different things being counted.
The horizontal carousel
mirrorsIn a horizontal infinite list the end of the row moves to the left in Arabic, and the sentinel moves with it. Do not detect the end with scrollLeft + clientWidth >= scrollWidth: Chrome, Firefox and Safari now agree on one model where scrollLeft is 0 at the logical start and goes negative as the user moves toward the end in RTL, so that condition never becomes true and Math.abs(el.scrollLeft) is the least you need. Better still, skip the arithmetic: make the sentinel the last node in the DOM and let IntersectionObserver decide, since DOM order is logical order in both directions and flex-direction: row already puts the first card at the logical start with no row-reverse anywhere.
The direction of incoming text
never mirrorsFeed content arrives mixed: an Arabic post, then a repository name in English, then a line that starts with a number. A row takes its direction from the row and never from the page, so put dir="auto" on the text element inside each row rather than on the list, and align with text-align: start instead of text-align: right so the alignment follows whatever each row resolved. Flipping the page changes nothing about a Latin row, and that is the point: resolve direction on the container alone and every Latin line hangs its full stop and its brackets at the wrong end.
In code
Each row is one framework's word for the same thing. Take the row your project speaks.
See also
Updated · 2026-08-19