mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 14:55:43 +00:00
fix(grid): apply scrollMargin so VirtualCardGrid renders below tall content (#764)
react-virtual computes virtual-item positions relative to the scroll element, but the wrapper isn't at the scroll element's top — on Album Detail the "More by …" grid sits under the entire tracklist. Without a matching `scrollMargin`, the virtualizer thinks every row is far above the viewport and unmounts it, so cards visibly disappear as you scroll past long tracklists and never appear at all once the offset exceeds the viewport height (52-track album in the report). Measure the wrapper's Y-offset against the scroll element inside a useLayoutEffect, pass it as `scrollMargin`, and translate each row by `vRow.start - scrollMargin` so positions land back in wrapper-local coordinates. ResizeObserver on the scroll element + its content keeps the offset correct when content above grows. `getTotalSize()` already subtracts the margin internally (virtual-core/src/index.ts:1304), so the wrapper height stays unchanged. Affects every page using VirtualCardGrid; the bug only surfaced on Album Detail because every other call site puts the grid near the top of the scroll content.
This commit is contained in:
committed by
GitHub
parent
8b92e85321
commit
33a1b8709d
@@ -1,4 +1,4 @@
|
||||
import React, { useRef } from 'react';
|
||||
import React, { useLayoutEffect, useRef, useState } from 'react';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { APP_MAIN_SCROLL_VIEWPORT_ID } from '../constants/appScroll';
|
||||
import { useElementClientHeightById } from '../hooks/useResizeClientHeight';
|
||||
@@ -43,11 +43,40 @@ export function VirtualCardGrid<T>({
|
||||
const mainScrollViewportHeight = useElementClientHeightById(APP_MAIN_SCROLL_VIEWPORT_ID);
|
||||
const overscan = Math.max(2, Math.ceil(mainScrollViewportHeight / Math.max(1, rowHeightEst)));
|
||||
|
||||
// Vertical offset between the scroll element's top and this grid's top.
|
||||
// react-virtual computes row positions relative to the scroll element, so
|
||||
// without this margin a grid that sits below tall content (e.g. the
|
||||
// "More by …" rail under a long tracklist) would have its first rows
|
||||
// placed at negative positions and unmounted as out-of-viewport.
|
||||
const [scrollMargin, setScrollMargin] = useState(0);
|
||||
useLayoutEffect(() => {
|
||||
if (disableVirtualization) return;
|
||||
const wrap = wrapRef.current;
|
||||
const scrollEl = document.getElementById(APP_MAIN_SCROLL_VIEWPORT_ID);
|
||||
if (!wrap || !scrollEl) return;
|
||||
const measure = () => {
|
||||
const margin =
|
||||
wrap.getBoundingClientRect().top
|
||||
- scrollEl.getBoundingClientRect().top
|
||||
+ scrollEl.scrollTop;
|
||||
setScrollMargin(prev => (Math.abs(prev - margin) < 1 ? prev : margin));
|
||||
};
|
||||
measure();
|
||||
const ro = new ResizeObserver(measure);
|
||||
ro.observe(scrollEl);
|
||||
// Anything above the grid resizing (tracklist growing, hero collapsing)
|
||||
// shifts the grid down; observe the scroll content too so we re-measure.
|
||||
const scrollContent = scrollEl.firstElementChild as Element | null;
|
||||
if (scrollContent) ro.observe(scrollContent);
|
||||
return () => ro.disconnect();
|
||||
}, [disableVirtualization, layoutSignal, virtualRowCount]);
|
||||
|
||||
const virtualizer = useVirtualizer({
|
||||
count: disableVirtualization ? 0 : virtualRowCount,
|
||||
getScrollElement: () => document.getElementById(APP_MAIN_SCROLL_VIEWPORT_ID),
|
||||
estimateSize: () => rowHeightEst,
|
||||
overscan,
|
||||
scrollMargin,
|
||||
});
|
||||
|
||||
useRemeasureGridVirtualizer(virtualizer, {
|
||||
@@ -93,6 +122,9 @@ export function VirtualCardGrid<T>({
|
||||
{virtualizer.getVirtualItems().map(vRow => {
|
||||
const start = vRow.index * cols;
|
||||
const rowItems = items.slice(start, start + cols);
|
||||
// `vRow.start` is measured from the scroll element's top; our wrapper
|
||||
// already sits `scrollMargin` below that, so subtract to land back
|
||||
// in wrapper-local coordinates.
|
||||
return (
|
||||
<div
|
||||
key={vRow.key}
|
||||
@@ -101,7 +133,7 @@ export function VirtualCardGrid<T>({
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
transform: `translateY(${vRow.start}px)`,
|
||||
transform: `translateY(${vRow.start - scrollMargin}px)`,
|
||||
display: 'grid',
|
||||
gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
|
||||
gap: gridGap,
|
||||
|
||||
Reference in New Issue
Block a user