Skip to content

Commit 6bea24d

Browse files
Further refine syntaxes virtualization approach
1 parent a2aedf9 commit 6bea24d

2 files changed

Lines changed: 48 additions & 75 deletions

File tree

src/components/syntaxes/SyntaxDisplayManager.astro

Lines changed: 48 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -351,8 +351,13 @@ export interface VersionFilterData {
351351
if (index === -1) {
352352
return;
353353
}
354-
processRender(index);
355-
filteredItems[index].element.scrollIntoView();
354+
loadElements(index - nextIndexToLoad);
355+
loadObserver.disconnect();
356+
loadElements(10);
357+
filteredItems[index].element.scrollIntoView({
358+
block: 'center',
359+
behavior: 'smooth',
360+
});
356361
}
357362

358363
// Listen for sidebar clicks
@@ -371,79 +376,50 @@ export interface VersionFilterData {
371376

372377
const noResultsElement = document.getElementById('syntaxes-no-results')!;
373378

374-
const OVERSCAN_COUNT = 5;
375-
let visibleRange: { start: number, end: number } = { start: -1, end: -1 };
376-
// used to ignore observations that trigger on initial observation call
377-
const justObserved: Set<Element> = new Set();
379+
const ESTIMATED_CARD_HEIGHT = 300;
380+
const INITIAL_LOAD_AMOUNT = 10;
381+
let nextIndexToLoad = 0;
378382

379-
const visibleObserver = new IntersectionObserver((entries) => {
383+
const loadObserver = new IntersectionObserver(entries => {
380384
for (const entry of entries) {
381-
if (justObserved.delete(entry.target) || !entry.isIntersecting) {
385+
if (!entry.isIntersecting || entry.intersectionRatio < 0.3) {
382386
continue;
383387
}
384-
// calculate new range
385-
let index = parseInt((entry.target as HTMLElement).style.order);
386-
index = Math.min(Math.max(index + (entry.boundingClientRect.top <= 0 ? 1 : -1), 0), filteredItems.length);
387-
processRender(index);
388+
loadObserver.unobserve(entry.target);
389+
loadElements(1);
388390
}
389-
}, { threshold: 0.2 });
391+
}, { threshold: 0.3 });
390392

391-
// exists for handling edge case of first/last element
392-
// basically, force highlight on these elements when they are fully focused
393-
const justObservedScroll: Set<Element> = new Set();
394-
const scrollObserver = new IntersectionObserver((entries) => {
393+
// basically, observe whatever is in the middle of the screen
394+
const scrollObserver = new IntersectionObserver(entries => {
395395
for (const entry of entries) {
396-
if (justObservedScroll.delete(entry.target) || !entry.isIntersecting) { // check because initial observation will fire regardless
397-
continue;
396+
if (entry.isIntersecting) {
397+
const index = parseInt((entry.target as HTMLElement).style.order);
398+
syncSidebarTo(filteredItems[index].syntaxId);
399+
break;
398400
}
399-
const index = parseInt((entry.target as HTMLElement).style.order);
400-
syncSidebarTo(filteredItems[index].syntaxId);
401401
}
402-
}, { threshold: 1 });
403-
404-
function processRender(index: number): void {
405-
const newRange = {
406-
start: Math.max(index - OVERSCAN_COUNT, 0),
407-
end: Math.min(index + OVERSCAN_COUNT, filteredItems.length),
408-
};
409-
410-
// hide old items
411-
for (let i = visibleRange.start; i < visibleRange.end; i++) {
412-
if (i >= newRange.start && i < newRange.end) {
413-
continue;
402+
}, { rootMargin: '-50% 0px -50% 0px' });
403+
const scrollEndObserver = new IntersectionObserver(entries => {
404+
for (const entry of entries) {
405+
if (entry.isIntersecting) {
406+
const index = entry.target === filteredItems[0].element ? 0 : filteredItems.length - 1;
407+
syncSidebarTo(filteredItems[index].syntaxId);
408+
break;
414409
}
415-
visibleObserver.unobserve(filteredItems[i].element);
416-
filteredItems[i].element.style.display = 'none';
417410
}
411+
}, { threshold: 0.95 });
418412

419-
// show new items
420-
for (let i = newRange.start; i < newRange.end; i++) {
421-
if (i >= visibleRange.start && i < visibleRange.end) {
422-
continue;
413+
function loadElements(count: number): void {
414+
for (let i = 0; i < count; i++) {
415+
if (nextIndexToLoad === filteredItems.length) {
416+
break;
423417
}
424-
filteredItems[i].element.style.display = '';
425-
justObserved.add(filteredItems[i].element);
426-
visibleObserver.observe(filteredItems[i].element);
427-
}
428-
visibleRange = newRange;
429-
430-
// sync sidebar
431-
syncSidebarTo(filteredItems[index].syntaxId);
432-
433-
// handle edge case for highlighting first/last element
434-
if (visibleRange.start === 0) {
435-
scrollObserver.unobserve(filteredItems[0].element);
436-
justObservedScroll.add(filteredItems[0].element);
437-
scrollObserver.observe(filteredItems[0].element);
438-
} else {
439-
scrollObserver.unobserve(filteredItems[0].element);
440-
}
441-
if (visibleRange.end === filteredItems.length && scrollObserver) {
442-
scrollObserver.unobserve(filteredItems[filteredItems.length - 1].element);
443-
justObservedScroll.add(filteredItems[filteredItems.length - 1].element);
444-
scrollObserver.observe(filteredItems[filteredItems.length - 1].element);
445-
} else {
446-
scrollObserver.unobserve(filteredItems[filteredItems.length - 1].element);
418+
const element = filteredItems[nextIndexToLoad].element;
419+
element.style.display = '';
420+
loadObserver.observe(element);
421+
scrollObserver.observe(element);
422+
nextIndexToLoad++;
447423
}
448424
}
449425

@@ -458,8 +434,10 @@ export interface VersionFilterData {
458434
const newFilteredItems = getFilteredItems();
459435

460436
// hide all currently visible elements
461-
for (let i = visibleRange.start; i < visibleRange.end; i++) {
462-
visibleObserver.unobserve(filteredItems[i].element);
437+
loadObserver.disconnect();
438+
scrollObserver.disconnect();
439+
scrollEndObserver.disconnect();
440+
for (let i = 0; i < nextIndexToLoad; i++) {
463441
filteredItems[i].element.style.display = 'none';
464442
}
465443
filteredItems = newFilteredItems;
@@ -469,12 +447,14 @@ export interface VersionFilterData {
469447
item.element.style.order = index.toString();
470448
});
471449

450+
// now render
451+
nextIndexToLoad = 0;
452+
loadElements(INITIAL_LOAD_AMOUNT);
453+
472454
// prepare sidebar
473455
updateSidebar();
474-
475-
// now render
476-
visibleRange = { start: -1, end: -1 };
477-
processRender(0);
456+
scrollEndObserver.observe(filteredItems[0].element);
457+
scrollEndObserver.observe(filteredItems[filteredItems.length - 1].element);
478458

479459
// show message when no results found
480460
if (filteredItems.length === 0) {

src/pages/syntaxes/[...id].astro

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,13 +159,6 @@ const sidebarEntries = syntaxes.map(syntax => ({
159159
padding: 0;
160160
}
161161

162-
:global(html:is([data-is-syntaxes])) {
163-
scrollbar-width: none;
164-
}
165-
:global(html:is([data-is-syntaxes]))::-webkit-scrollbar {
166-
display: none;
167-
}
168-
169162
dialog {
170163
margin: 0;
171164
padding: 1rem;

0 commit comments

Comments
 (0)