Scrollspy
متتبّع التمريرIntersectionObserver
also calledon this page، in-page navigation، active section nav، page outline، anchor nav، jump links، سكرول سباي، قائمة «في هذه الصفحة»، فهرس جانبي متتبّع، محتويات الصفحة، مؤشّر القسم الحالي، روابط داخل الصفحة
A scrollspy is the list of headings for one document, where the link matching the section on screen is the only one marked, and that mark moves to the next link as the page scrolls. Two pieces make it up: anchors pointing at ids inside the same page, and a watcher that decides which one is current right now. It is not a breadcrumb trail, not a table of contents, and not a reading progress bar. Breadcrumbs describe where the page sits in the site tree and hold still for as long as you stay on it, while a scrollspy says nothing about the site and reports only your position inside one document. A table of contents is this same list with the tracking taken out, so its links jump and nothing knows where you got to. A reading progress bar gives a continuous fraction of the document and no target to click, where a scrollspy names one section you can travel to. Scroll without clicking anything: if the list holds still, what you have is a table of contents.
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 side the column sits on
mirrorsThe list belongs at the logical end of the article: the right side in English, the left side in Arabic. Build the layout with grid-template-columns: 1fr 15rem, or with a flex row, since grid column tracks run along the inline axis and reverse with direction on their own. Do not pin the column with position: absolute plus right: 0, and do not float: right, because both stay on the right after the flip and sit on top of the Arabic text.
The active indicator bar
mirrorsThe thin bar marking the active link stands on the edge that faces the text: the left of the link in English, its right in Arabic. Write it as border-inline-start: 2px solid rather than border-left, draw it transparent on every link and colour only the active one, or the text shifts two pixels each time the section changes. Pair it with padding-inline-start so the gap between the bar and the word stays on the correct side.
Indenting nested levels
mirrorsAn h3 link is indented under the h2 link above it, and that indent runs along the inline axis, so it belongs on padding-inline-start of the item or margin-inline-start of the nested list. The trap is the reset rather than the indent: the browser stylesheet already gives ol and ul a padding-inline-start of 40px, so a line reading padding-left: 0 removes none of it in Arabic and leaves a 40px gap on the right. Zero it with padding-inline-start: 0 and add your own indent back, and if you draw a guide line down the nested list, put it on border-inline-start.
The observer does not mirror
never mirrorsrootMargin takes four physical values in top, right, bottom, left order; it accepts no logical keyword and does not change with dir. That costs a vertical scrollspy nothing, because only the top and bottom values do any work, so set the two side values to zero and leave them. The same holds for boundingClientRect on each IntersectionObserverEntry: its left and right are physical viewport coordinates, so when you sort sections to pick the highest one, sort on top, the one value direction never touches.
The reading progress bar
mirrorsA progress bar above the list fills from the logical start, which in Arabic is the right. The number itself has nothing to do with direction, since it comes from scrollTop on the vertical axis, and only the painting flips. transform-origin accepts no logical keyword at all, so either animate inline-size from 0 to 100% on an element anchored with inset-inline-start: 0, or hand-write transform-origin: right under [dir="rtl"].
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