@@ -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) {
0 commit comments