perf(feed): smoother scrolling for long feeds

Three low-risk wins for large filtered feeds:
- content-visibility:auto on cards (.cv-card/.cv-row) so the browser skips
  layout/paint for off-screen cards; contain-intrinsic-size keeps the scrollbar
  stable and is remembered per card after first render.
- memo(VideoCard) + stable onState/onChannelFilter callbacks (onState reads the
  loaded list via a ref) so appending a page only renders the ~60 new cards
  instead of reconciling every card already on screen.
- Prefetch the next page earlier (sentinel rootMargin 800px → 1500px) so the
  'Loading more…' flash is far less likely during fast scrolling.
This commit is contained in:
npeter83 2026-06-12 18:17:03 +02:00
parent 0b5f7f5769
commit 9e8c890893
3 changed files with 70 additions and 39 deletions

View file

@ -1,3 +1,4 @@
import { memo } from "react";
import { Bookmark, Check, CheckCheck, Eye, EyeOff, ListFilter } from "lucide-react";
import Avatar from "./Avatar";
import clsx from "clsx";
@ -136,7 +137,7 @@ function Thumb({
);
}
export default function VideoCard({
function VideoCard({
video,
view,
onState,
@ -172,7 +173,7 @@ export default function VideoCard({
return (
<div
className={clsx(
"group flex gap-3 p-2 rounded-xl hover:bg-card hover:shadow-lg transition",
"cv-row group flex gap-3 p-2 rounded-xl hover:bg-card hover:shadow-lg transition",
watched && "opacity-55"
)}
>
@ -198,7 +199,7 @@ export default function VideoCard({
return (
<div
className={clsx(
"group glass-card glass-hover rounded-2xl p-2.5 transition-all duration-150 hover:-translate-y-1",
"cv-card group glass-card glass-hover rounded-2xl p-2.5 transition-all duration-150 hover:-translate-y-1",
watched && "opacity-55"
)}
>
@ -227,3 +228,8 @@ export default function VideoCard({
</div>
);
}
// Memoized so appending a feed page only renders the new cards, not every existing
// one (the callbacks from Feed are stable; non-overridden video objects keep their
// identity across renders).
export default memo(VideoCard);