React-window: 与 ScrollSync 的兼容性

创建于 2018-11-08  ·  52评论  ·  资料来源: bvaughn/react-window

我有一个用例,我需要创建一个具有固定标题的网格,这些标题保持固定在顶部。 在反应虚拟化中,这可以通过创建两个网格组件(一个用于标题,一个用于网格体)并同步滚动位置来实现,以便标题网格的水平滚动与主网格同步。

我知道您正试图在包大小和概念复杂性方面保持 react-window 更轻的重量,以支持将附加功能构建为单独的包。 这是我同意的方向。 我认为可以从 react-virtualized 中提取或调整 ScrollSync 组件以与 react-window 一起使用。 但是,为此,react-window 需要接受 scrollLeft 和 scrollTop 的 props,以便可以直接管理标题网格的滚动偏移量。

这是您愿意支持的用例吗? 如果没有,您对我应该朝哪个方向实施这个有什么建议吗?

感谢您为这个库工作。 作为使用 react-virtualized 几年的人,我很欣赏 react-window 入门的简单性以及它在没有太多手动干预的情况下对我来说的性能如何。

最有用的评论

这是完全有道理的。 谢谢你的建议! 我让它工作了,它实际上很容易设置。 如此简单,它绝对不保证一个独立的包。 也许文档中的一个例子,但我想这是你的电话。 我将在此处留下指向我的工作代码沙盒示例的链接,以防其他人将来偶然发现此问题。

https://codesandbox.io/s/y3pyp85zm1

TLDR - 将参考放在标题网格上,并将其放在正文网格上。

onScroll={({ scrollLeft }) => this.headerGrid.current.scrollTo({ scrollLeft })}

所有52条评论

首先,感谢您的客气话和积极的反馈。 我很高兴听到 react-window 到目前为止一直在为您工作!

我同意像 ScrollSync 这样的组件可以作为依赖于 react-window 的独立包发布,但我不想将它添加到这个项目中,因为它不是窗口的核心。

关于你关于滚动道具的具体问题,这不是我愿意对项目做出的改变,因为在将它与 react-virtualized 一起使用后,我开始意识到它有一些严重的缺点。 如果你好奇的话,我实际上在 React 博客上写过它:

https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#anti -pattern-erasing-state-when-props-change

您可以使用 react-window 提供的命令式滚动 API 来实现类似的同步行为。 您只需要从提交生命周期调用这些方法,而不是通过传递道具。

希望这是有道理的,但如果没有,请随时提出后续问题!

这是完全有道理的。 谢谢你的建议! 我让它工作了,它实际上很容易设置。 如此简单,它绝对不保证一个独立的包。 也许文档中的一个例子,但我想这是你的电话。 我将在此处留下指向我的工作代码沙盒示例的链接,以防其他人将来偶然发现此问题。

https://codesandbox.io/s/y3pyp85zm1

TLDR - 将参考放在标题网格上,并将其放在正文网格上。

onScroll={({ scrollLeft }) => this.headerGrid.current.scrollTo({ scrollLeft })}

谢谢你的链接! 这是非常周到的。

2018 年 11 月 8 日星期四下午 1:09,Reagan Keeler < [email protected]写道:

这是完全有道理的。 谢谢你的建议! 我得到了它的工作,
它实际上很容易设置。 如此简单,它绝对不会
保证一个独立的包。 也许文档中的一个例子,但那是你的
打电话我想。 我将在此处留下指向我的工作代码沙盒示例的链接
以防其他人将来偶然发现这个问题。

https://codesandbox.io/s/y3pyp85zm1

TLDR - 将参考放在标题网格上,并将其放在正文网格上。

onScroll={({ scrollLeft }) => this.headerGrid.current.scrollTo({ scrollLeft })}


您收到此消息是因为您修改了打开/关闭状态。
直接回复本邮件,在GitHub上查看
https://github.com/bvaughn/react-window/issues/86#issuecomment-437156749
或静音线程
https://github.com/notifications/unsubscribe-auth/AABznTUunzEIs6bVQfVsz7T21L2-Pkkoks5utJ17gaJpZM4YVMd7
.

对于偶然发现此问题的任何人,我在react-virtualized中有一个滚动同步的顶部/右侧/底部/左侧冻结超级网格,我刚刚迁移到此库。 我会说,上述解决方案至少与在其他库中使用ScrollSync ,即使不是更好,也至少具有相同的性能,并且大大减少了痛苦。

它还与新的useRef反应钩子(https://reactjs.org/docs/hooks-reference.html#useref)搭配得很好

const topRef = useRef();
const rightRef = useRef();
const bottomRef = useRef();
const leftRef = useRef();
...
<Grid
  onScroll={({ scrollLeft, scrollTop }) => {
    if (leftRef.current) {
      leftRef.current.scrollTo({ scrollTop });
    }
    if (rightRef.current) {
      rightRef.current.scrollTo({ scrollTop });
    }
    if (topRef.current) {
      topRef.current.scrollTo({ scrollLeft });
    }
    if (bottomRef.current) {
      bottomRef.current.scrollTo({ scrollLeft });
    }
  }}
  ...
/>

好的! 感谢分享@ranneyd!

它还与新的useRef反应钩子(https://reactjs.org/docs/hooks-reference.html#useref)很好地配对

谢谢@ranneyd

可以分享一个工作示例吗?

@ranneyd再次感谢,我遵循了您的方法并且滚动同步工作正常。

滚动条上的隐藏溢出导致网格末尾附近未对齐:

gridalignement

对于如何解决这个问题,有任何的建议吗?

代码沙盒示例

提前致谢

@carlosagsmendes这是因为滚动条。 在左边使高度减去滚动条大小的高度。 为了获得跨设备的一致性,我使用 CSS 手动硬编码滚动条的大小。 如果您的内容非常动态,可能有也可能没有滚动条,请执行类似“数量项目 * 大小项目 < 宽度(您应该在那里拥有所有这些值)”之类的操作,然后根据此更改高度。

为了获得跨设备的一致性,我使用 CSS 手动硬编码滚动条的大小。

FWIW dom-helpers包有一个方便的scrollbarSize函数,它告诉你当前设备上的宽度是多少。 可能比硬编码更好。

@bvaughn是的! 我忘了提到这一点。 然而,它对我们不起作用。 我认为问题是我们已经在做硬编码的滚动条,这让它感到困惑

谢谢你。 我会试一下!

这是否适用于水平 VariableSizeList? 我试过了,但我的浏览器死机了。

onScroll={({ scrollLeft }) => this.headerGrid.current.scrollTo({ scrollLeft })}

它适用于VariableSizeGrid,但我的标题在滚动时有点滞后。

@ajaymore所以标题滞后:当您在 Mac 上使用滚轮时,滚动同步会出现问题。 MacOSX 具有这种内置的自然滚动功能,它可以对滚动进行插值以使其“更平滑”。 这方面的刷新率实际上比 Chrome 中的动画帧率要快。 因此,您正在滚动的元素将在 V8/ScrollSync 有时间更新 DOM 之前进行动画处理。 这是我还没有看到解决方案的技术限制。

有趣的事实:如果您手动使用浏览器中的滚动条(例如通过拖动小东西来滚动老式方式),它完全可以正常工作。 插值的东西内置在滚轮/轨迹板滑动中

@ajaymore所以标题滞后:当您在 Mac 上使用滚轮时,滚动同步会出现问题。 MacOSX 具有这种内置的自然滚动功能,它可以对滚动进行插值以使其“更平滑”。 这方面的刷新率实际上比 Chrome 中的动画帧率要快。 因此,您正在滚动的元素将在 V8/ScrollSync 有时间更新 DOM 之前进行动画处理。 这是我还没有看到解决方案的技术限制。

有趣的事实:如果您手动使用浏览器中的滚动条(例如通过拖动小东西来滚动老式方式),它完全可以正常工作。 插值的东西内置在滚轮/轨迹板滑动中

@ranneyd感谢您如此快速的回复。 我同意这是一个限制。 它可以在大多数设备上正常工作,因此不是一个大问题。

@bvaughn你玩过position: sticky吗? 我有一段时间没有看过它,我知道浏览器支持很少,但我想知道是否有办法在支持它的浏览器中利用它......

我和@ajaymore有同样的问题,也就是说,同步两个组件很

@alonrbar我在 Windows PC 上遇到了同样的问题。

我实际上刚刚找到了一种适合我的解决方法。
一般的想法是创建一个隐藏到原始网格并窃取他的 onScroll 事件的“阴影网格”,然后使用它来手动滚动原始网格以及任何其他必要的网格。 它会稍微降低性能,但可以很好地同步所有网格,因此您必须考虑权衡。

代码如下所示;

import styled from '@emotion/styled';
import * as React from 'react';
import { VariableSizeGrid, VariableSizeGridProps } from 'react-window';
import { SizeUtils } from '../utils';

export interface SyncableGridProps extends VariableSizeGridProps {
    mainGridRef: React.Ref<VariableSizeGrid>;
    shadowGridRef: React.Ref<VariableSizeGrid>;
    hideVerticalScrollbar?: boolean;
}

export class SyncableGrid extends React.PureComponent<SyncableGridProps> {
    public render() {
        const { height, width } = this.props;
        const {
            onScroll,
            mainGridRef: mainGridRef1,
            shadowGridRef: shadowGridRef1,
            ...mainProps
        } = this.props;
        const {
            children,
            style,
            overscanRowsCount,
            overscanColumnsCount,
            overscanCount,
            useIsScrolling,
            onItemsRendered,
            mainGridRef: mainGridRef2,
            shadowGridRef: shadowGridRef2,
            innerRef,
            outerRef,
            ...shadowProps
        } = this.props;
        return (
            <SyncWrapper
                style={{
                    height,
                    width
                }}
            >
                <MainGrid
                    {...mainProps}
                    style={Object.assign({}, style, {
                        overflowY: 'scroll'
                    })}
                    ref={mainGridRef1}
                />
                <GridShadow
                    {...shadowProps}
                    style={{
                        position: 'absolute',
                        top: 0,
                        left: 0
                    }}
                    ref={shadowGridRef1}
                >
                    {() => null}
                </GridShadow>
            </SyncWrapper>
        );
    }
}

// ---------------- //
//      styles      //
// ---------------- //

const SyncWrapper = styled.div`
    position: relative;
    overflow: hidden;
`;

export interface MainGridProps extends VariableSizeGridProps {
    hideVerticalScrollbar?: boolean;
}

export const MainGrid = styled(VariableSizeGrid) <MainGridProps>`
    overflow-y: scroll;
    box-sizing: content-box;
    ${props => {
        if (!props.hideVerticalScrollbar)
            return '';
        const paddingDir = (props.theme.dir === 'rtl' ? 'padding-left' : 'padding-right');
        return `${paddingDir}: ${SizeUtils.scrollbarWidth}px;`;
    }}
`;

export const GridShadow = styled(MainGrid)`
    opacity: 0;
`;

然后在另一个文件中:

<SyncableGrid
    mainGridRef={this.firstGridMain}
    shadowGridRef={this.firstGridShadow}
    onScroll={this.handleFirstGridScroll}
    // other props omitted for bravity...
>
   // children omitted for bravity...
</SyncableGrid>
<SyncableGrid
    mainGridRef={this.secondGridMain}
    shadowGridRef={this.secondGridShadow}
    onScroll={this.handleSecondGridScroll}
    // other props omitted for bravity...
>
   // children omitted for bravity...
</SyncableGrid>

private handleFirstGridScroll = (e: GridOnScrollProps) => {
    const { scrollTop, scrollLeft } = e;

    // synchronize self
    if (this.firstGridMain.current) {
        this.firstGridMain.current.scrollTo({ scrollTop, scrollLeft });
    }

    // synchronize other grid
    if (this.secondGridMain.current) {
        this.secondGridMain.current.scrollTo({ scrollTop, scrollLeft });
        this.secondGridShadow.current.scrollTo({ scrollTop, scrollLeft });
    }
}

@alonrbar这很有趣! 我很快就会试试这个。

看起来阴影网格位于真实网格之上,是吗? 如果是这样,“真实网格”上的单击事件将不起作用,对吗? 我想你可以用点击事件 + x/y 坐标做一些有点hacky 的事情,然后以某种方式将它应用到主网格。

还有@barbalex re:在windows中也失败了

它实际上可能是一个镀铬标志。 查看chrome://flags 中是否有任何内容

这个问题似乎与滚动比浏览器动画帧更快地“平滑”有关。 如果您担心这纯粹是性能/滞后问题,请尝试制作一个非常基本的滚动同步实现(在一个 div 的滚动上设置另一个 div 的滚动位置),看看是否遇到同样的问题。 甚至可以用纯香草 JS 来消除 React 作为罪魁祸首

@ranneyd你说得对,它会阻止点击事件......需要对此做更多思考......

我认为chrome://flags 中的线程滚动功能有很大的影响:关闭它会使鼠标滚轮滚动与滚动条滚动一样平滑。

2019-07-15_00h03_42

@alonrbar这个怎么样:

向主网格添加滚动处理程序。 在其中您执行e.preventDefault();或其他操作以防止实际滚动。 然后您查看事件以确定它会滚动多少,这会移动其他同步的内容,然后您使用它来手动滚动相同的元素。 因此,不是滚动 A,然后使用该信息滚动 B,而是拦截 A 上的滚动,取消它,然后使用它滚动 B 和 A 本身。 那行得通吗?

我不是通过电脑来测试的。 很hacky,但我可以工作。 @bvaughn 的想法?

@barbalex你是对的,它也适用于我,但我不能让我的所有用户打开和关闭 chrome 标志😔

@ranneyd我刚刚查看了它,并对源代码(链接)进行了一些修改,但显然您无法禁用滚动事件,唯一能做的就是劫持所有可能导致滚动的鼠标滚轮、触摸和键盘事件阻止他们。 链接(SO)有一个简短的代码片段,但这使它更加hacky,所以我仍在考虑它......

@alonrbar是的,要求用户这样做是行不通的。 如果你_可以_给他们一个direkt链接,如果你_do_想尝试: chrome://flags/#disable -threaded-scrolling

@alonrbar嗯,这不好,但如果我们只是将它应用到我们已经一起黑客攻击的网格,那就不是很

就是滚轮吧? 箭头键也让它发生吗?

这是这里接受的答案的片段: https :

function disableScroll() {
  if (window.addEventListener) // older FF
      window.addEventListener('DOMMouseScroll', preventDefault, false);
  document.addEventListener('wheel', preventDefault, {passive: false}); // Disable scrolling in Chrome
  window.onwheel = preventDefault; // modern standard
  window.onmousewheel = document.onmousewheel = preventDefault; // older browsers, IE
  window.ontouchmove  = preventDefault; // mobile
  document.onkeydown  = preventDefaultForScrollKeys;
}

如您所见,您需要处理多个事件并考虑这样做的可能后果。

目前,由于我现在无法投入更多时间来尝试解决此问题,因此我决定使用非虚拟解决方案(这也不完美,但更适合我的用例)。 您可以在此处此处找到我使用的代码(它是我编写的包含react-window的库的一部分)。

@alonrbar是的,我同意 SO 解决方案非常糟糕。

但是,我发现您的解决方案非常有趣。 我将看看我是否可以自己破解它并使其与虚拟化表一起使用。

@alonrbar所以我做了一个实际使用绝对定位的非虚拟化实现。 唯一滚动的是外部容器。 在滚动时,它会更新用于绝对定位内部元素的顶部/左侧值。 所以没有scrollToscrollTop = ... ,我发现这让我很伤心。 最重要的是,滚动条始终位于整个网格的外部。

我制作的这个东西可以在所有方面动态地具有“冻结标题”。 这是一个非常粗略的例子/poc。

显然它缺乏虚拟化,这是一个严重的问题。 不幸的是,我不知道我是否可以将这个库中的 Grid 包装在其中,因为这个库基本上是在滚动时运行的,这消除了内部网格滚动。 不知道下一步是什么。

https://codesandbox.io/embed/non-virtual-scroll-synced-table-ot467

我认为推动这个库的相同虚拟化逻辑可以应用于此。

@ranneyd非常酷的实现!

我喜欢动态网格组合:)

但最重要的是,使用绝对定位而不是滚动的想法可能是解决这个问题的关键。 通过引入另一个 div,您已将onscroll事件与实际内容滚动断开连接,我认为这开辟了新的可能性。

如果您可以挂钩react-windowrender方法,您可能可以用您的实现替换第 459 行及以后的行。 然后,您将能够挂钩 onscroll 事件并在需要时禁用它的效果(滚动条仍将始终移动,但您可以控制内容以防止其更改)。

@alonrbar所以我实际上重新实现了虚拟化算法。 它不是那么好,但是当这个 Grid 进入实际虚拟化时,查看源代码它基本上是相同的 alg。 不过,性能其实还可以。 我可以在没有任何延迟的情况下达到 200x200 的网格,而只需一点点就可以达到 500x500。 如果我的老板让我再做这件事,我会尝试按照您的建议进行渲染方法的实现,这实际上可能非常简单。

如果你对我制作的网格感兴趣,我可以把它贴在某个地方。 如果你想把它和这个库连接起来,那就做我的客人吧😏

@ranneyd是的,我想看看您制作的虚拟化解决方案。 不知道我是否会使用它,但它肯定会很有趣:)

此外,我今天试图使用您的代码来实现我建议的渲染方法的更改,但在重新阅读您的代码后,我注意到您实际上并没有像我认为的那样断开滚动。 我的意思是滚动条和onscroll事件仍然与实际内容滚动分不开。 我将你的想法更进一步,并实现了一个真正断开连接的滚动:

https://codesandbox.io/embed/absolute-position-scrolling-1u7vj

如果您查看代码,您会发现如果我们从Content组件中删除topleft属性,即使滚动条也不会滚动内容做。 我还验证了这次鼠标事件没有被阻止。 😂
我相信现在可以使用此实现来忽略onscroll事件并手动同步多个网格。 但这当然需要更多的工作,所以它必须等待下一次......

这个讨论让我很好奇,所以我设置了一个项目来比较两个滚动同步的 react-window Grid s 和一个 react-virtualized MultiGrid ,这样我就可以了解 perf 的比较:
https://github.com/bvaughn/react-window-vs-react-virtualized-synced-grids

我试图使每个用法尽可能相似。 一些初步观察:

  • react-window 在 DEV 模式下看起来明显更慢,但在生产版本中感觉更快/响应更快。 (这里很难排除我的偏见。产量差异较小。)
  • react-window 也做了更多的提交,因为滚动事件首先更新主动网格,然后通过级联更新被动网格。 我对Grid做了一个补丁,以支持传递“原生”(React)滚动事件处理程序(在 React 的批量更新中调用)而不是当前的onScroll (在提交期间调用)阶段)。 当我在本地测试时,这似乎是一个非常有希望的更改,因为它避免了单独的级联渲染,所以也许我会更改默认的onScroll时间。

@bvaughn我尝试使用 divA -> onScroll -> setState -> ref.scrollTop 制作最基本的香草滚动效果代码,但它仍然无法解决这个 chrome 线程滚动问题。 我承认我没有绕过反应状态并在 onScroll 处理程序中设置 ref.scrollTop ,但除此之外我想不出更基本的方法来做到这一点。 如果您无法在尽可能少的步骤中足够快地获得 onScroll -> scrollTop,您将如何解决? 我完全错过了什么吗? 似乎网格不需要根据滚动(或设置 scrollTop)移动。

目标始终是让 JavaScript 尽可能快,以便跟上管理滚动的线程。

我承认我没有绕过反应状态并在 onScroll 处理程序中设置 ref.scrollTop

需要明确的是,这也不是我的回购正在做的事情。 我只是在与活动网格相同的(React 包装的)事件处理程序中设置被动网格中的滚动偏移量,因此 React 将它们的更新批处理为单个渲染+提交。 老实说,这可能没有_太大_区别。

@ranneyd@bvaughn与您分享我到目前为止的观察:

  1. 许多实现在桌面浏览器上运行良好,但我的主要用例实际上是在移动设备上,我看到了巨大的性能差异。

  2. 同步两个简单的非虚拟网格效果很好(不是 100% 完美,但足够接近,即使在移动设备上)。 这是我天真但有效的实现和一个测试用例,可以看到它的实际效果( yarn storybook )。

  3. 使用“受控”滚动(正如我在此评论中所建议的https :

    即便如此,我还是想看看“受控”策略是否能以某种方式在单个虚拟列表上解决这个问题

  4. 我在想,正如你们两个已经建议的那样,也许将_callPropsCallbacks直接移动到_onScroll处理程序可能有助于将同步延迟降至最低。 编辑- 现在才尝试,并没有真正帮助 😞

  5. 以任何方式解决它,恕我直言,一个好主意是将虚拟化逻辑与组件的其余部分(甚至可能是一个钩子?)组件道具。 它还将使导出此逻辑成为可能,并允许用户基于相同的逻辑实现自定义组件(例如此评论中的@ranneyd解决方案)。

想法?

@alonrbar我的解决方案,它进行绝对定位,所以没有一个网格是进行滚动的网格,在大约 300x300 之前它会变得有点缓慢(注意它保持同步 100% 的时间,滚动只是变得有点迟钝)。 在较大的尺寸下,我认为它只是在大数组上处理/映射。 我认为有几个优化
不过,我可以做到,而且我并不完全相信它与滚动同步有任何关系,就像我相当简单的虚拟化实现一样。 例如,我可以做更多的缓存,并且我可以计算是否应该更早地虚拟化一个单元,以避免调用渲染函数(然后更好地缓存它)。

我还没有在手机上测试过任何东西。 我将提供一些代码供您稍后试用。

我真的很想看看@bvaughn测试。 他说直接挂钩到本机滚动修复它,你说它没有。 我想亲眼看看。

就将虚拟化逻辑拉入钩子或独立函数而言,它变得非常棘手,因为逻辑与视图具有内在的联系。 似乎很多性能调整都涉及缓存和记忆化的东西,这些东西可能很难封装在一个钩子或函数中,或者至少在获得相同性能的程度上。 但我会看看我有什么,看看我能抽出多少逻辑。

PS:
我刚刚想到的可能行不通的一件事是做一个类似 denounce 的事情 + css 转换。 如果我们每 100 毫秒或类似的时间只执行一个滚动事件并为运动设置动画,它可能看起来会更好。 它也可能看起来响应性大大降低。 这有点像他们做魔兽世界的方式(如果有延迟或高延迟,他们会让角色沿直线移动,然后在获得他们实际去了哪里的信息后进行纠正)。

我真的很想看看@bvaughn测试。 他说直接挂钩到本机滚动修复它,你说它没有。 我想亲眼看看。

我没有这么说 :smile: 我只是说它避免了额外的不必要的渲染和 DOM 突变。 我不清楚这对实际表现有多大影响。 无论如何,这似乎确实是一个整体的积极变化。

@alonrbar @bvaughn我需要实际记录我的代码,但这是最新版本:

https://github.com/ranneyd/synced-table

@alonrbar @bvaughn所以这是我刚刚发现的一件有趣的事情:

我的解决方案在 macbook 屏幕上的 Chrome 75 中不起作用。 当我的同事没有更新 chrome 时,它​​就起作用了。 如果他们使用外接显示器,它就可以工作。 在他们的笔记本电脑屏幕上它滞后。
😩

嗯......与外部显示器存在一些差异,刷新率或缩放比例。 当您说它“不起作用”时,具体是什么意思?

我的错。 我的意思是滚动不再同步。 如果你克隆我的 repo 并运行它,你可以比较外接显示器和笔记本电脑的屏幕。 在监视器上,它们完全同步。 在笔记本电脑屏幕上,标题闪烁(不要以与滚动元素相同的速度更新)。

我实际上已经放弃了,我正在尝试使用position: sticky 。 它实际上在起作用。 浏览器支持不是 100%,但实际上比一年前好多了。

有这个 lib 是一个非 polyfill 的 polyfill,它可能可以工作,但我们的目标浏览器恰好支持该功能。
https://github.com/dollarshaveclub/stickybits

@alonrbar @bvaughn这里是最新版本。 它使用position: sticky 。 我在自述文件中解释了一点。 代码仍然需要记录。

https://github.com/ranneyd/sticky-table

我的解决方案在 macbook 屏幕上的 Chrome 75 中不起作用。 当我的同事没有更新 chrome 时,它​​就起作用了。 如果他们使用外接显示器,它就可以工作。 在他们的笔记本电脑屏幕上它滞后。
😩

@ranneyd今天我

@bvaughn我认为必须是这样。 操作系统说它正在发送 60hz,我认为显示器的规格是 60hz,但如果有人在撒谎,我不会感到惊讶😂

@ranneyd我很抱歉地说我现在不是很可用所以我无法正确查看您的解决方案但是从我瞥了一眼我注意到它看起来很整洁并且您创建了一个useVirtual挂钩并优雅地将逻辑与渲染分开。
我认为,如果您能以某种方式为react-window创建一个拉取请求,在那里使用它,并且可能公开某种renderTable方法,您可以在其中插入渲染逻辑,那将会很棒。 这样你的库就可以包装 react-window 而不是替换它。 我相信如果我们可以利用这种方法并解决react-window的问题,这将是首选,这已经经过了大量的实战测试和广泛传播。

在另一件事上,我认为如果scrollTo将使用直接 DOM 操作(可能除了设置状态,但无论如何在这样做之前),那么同步两个表的结果将变得更加流畅。 如果我没记错的话, @bvaughn也在这个方向提出了一些建议。

@ranneyd您的粘性表解决方案看起来非常棒,我很高兴能够使用它。 你会考虑在npm上发布一个可用的 api 吗?

@bvaughn @ranneyd

所以,在很长一段时间之后,我最近又回到了这个问题。 使用来自您的库以及recyclerlistviewreact-virtual-grid 的代码和想法,我终于能够实现我想要的行为,即具有固定行和列的高性能网格,在桌面和移动设备上都运行良好设备(平滑滚动,没有任何空/白色单元格)。

它的 TLDR 是我将回收与粘性定位一起使用,最有趣的一段代码可以在此方法中找到。

关于编写另一个解决方案的动机的积分和更多信息在这里。 谢谢!

我将在此处留下指向我的工作代码沙盒示例的链接,以防其他人将来偶然发现此问题。
https://codesandbox.io/s/y3pyp85zm1

非常好,直到您完全向右滚动:然后标题与内容未对齐(通过滚动垂直滚动条的宽度)。
您是否有机会找到解决方案?
谢谢

我将在此处留下指向我的工作代码沙盒示例的链接,以防其他人将来偶然发现此问题。
https://codesandbox.io/s/y3pyp85zm1

非常好,直到您完全向右滚动:然后标题与内容未对齐(通过滚动垂直滚动条的宽度)。
您是否有机会找到解决方案?
谢谢

完全披露:我实际上从头开始制作了自己的版本。 它有自己的虚拟化,很大程度上基于这个库。 我这样做的原因是因为 MacBook 和某些 Chrome 标志存在问题,其中滚动动画的发生时间与 JS 不同。 使用 ScrollSync 需要过多的传递函数调用并且速度太慢。 我基本上不得不以粘性标题为核心重新制作库(我没有使用position: sticky就像我在本线程前面所说的那样。我需要在某个时候上传我现在拥有的内容)。

也就是说,我解决这个问题的方法是通过overflow: scroll强制滚动条,然后将该填充添加到最后一个单元格,或者我动态地确定是否有滚动条(及其宽度是多少)使用ref 并在其中放置一个不可见的 div,测量滚动条大小,并删除 DOM 节点。

我使用了overflow-y: overlay使滚动条叠加。 不幸的是,它只适用于 webkit 浏览器。

好的,谢谢你的信息。 似乎粘性标题是如此普遍的需求,只需将粘性第一行选项添加到 react-window 就可以使其在更多情况下替代 react-virtualized。 你不这么认为吗?

@ranneyd再次感谢,我遵循了您的方法并且滚动同步工作正常。

滚动条上的隐藏溢出导致网格末尾附近未对齐:

gridalignement

对于如何解决这个问题,有任何的建议吗?

代码沙盒示例

提前致谢

迟到了,但如果你只是简单地在 leftRef 中添加一行并将其溢出设置为隐藏,你基本上解决了 pb. (我这样做也不必同步 leftRef -> main Grid

  const headerRef = React.useRef();
  const leftRef   = React.useRef();

  return <Box classes={{
            root:classes.tableContainer
          }}>
      <AutoSizer>
        {({ height, width }) => (<>
        {/*---------------- LA TABLE -------------*/}
          <Grid
            columnCount={1000}
            columnWidth={100}
            height={height}
            rowCount={1000}
            rowHeight={35}
            width={width}
            onScroll={({ scrollLeft, scrollTop }) => {
              if (leftRef.current) {
                leftRef.current.scrollTo({ scrollTop });
              }
              if (headerRef.current) {
                headerRef.current.scrollTo({ scrollLeft });
              }
            }}
          >
            {({ columnIndex, rowIndex, style }) => (
              <Box style={style} classes={{root:classes.cell}}>
                Item {rowIndex},{columnIndex}
              </Box>
            )}
          </Grid>
          {/*---------------- HEADER -------------*/}
          <Grid
            ref={headerRef}
            outerElementType={React.forwardRef((props, ref) => (
              <div ref={ref}  {...props} style={{...props.style,position:"absolute",overflow:"hidden",top:0,right:0,left:150}} />
            ))}
            columnCount={1001}  /*columns count +1 for scroll problems*/
            columnWidth={100}
            height={60}
            rowCount={1}
            rowHeight={60}
            width={width}
          >
            {({ columnIndex, rowIndex, style }) => (
              <Box style={style} classes={{root:classes.headerCell}}>
                Header {rowIndex},{columnIndex}
              </Box>
            )}
          </Grid>  
          {/*---------------- LEFT COL -------------*/}
          <Grid
            ref={leftRef}
            outerElementType={React.forwardRef((props, ref) => (
              <div ref={ref}  {...props} style={{...props.style,position:"absolute",overflow:"hidden",top:60,left:0}} />
            ))}
            columnCount={1}
            columnWidth={150}
            height={height}
            rowCount={251} /** add 1 for scroll problems at the end */
            rowHeight={140}
            width={150}
          >
            {({ columnIndex, rowIndex, style }) => (
              <Box style={style} classes={{root:classes.headerCell}}>
                Left {rowIndex},{columnIndex}
              </Box>
            )}
          </Grid>  
        </>)}
      </AutoSizer>
    </Box>
此页面是否有帮助?
0 / 5 - 0 等级