React-window: التوافق مع ScrollSync

تم إنشاؤها على ٨ نوفمبر ٢٠١٨  ·  52تعليقات  ·  مصدر: bvaughn/react-window

لدي حالة استخدام حيث أحتاج إلى إنشاء شبكة بها رؤوس ثابتة تظل مثبتة في الأعلى. في التفاعل الافتراضي ، يمكن تحقيق ذلك عن طريق إنشاء مكونين للشبكة (أحدهما للرؤوس ، والآخر لجسم الشبكة) ومزامنة موضع التمرير بحيث تتم مزامنة التمرير الأفقي لشبكة الرأس مع الشبكة الرئيسية.

أعلم أنك تحاول إبقاء نافذة التفاعل أخف وزنًا ، سواء من حيث حجم الحزمة أو التعقيد المفاهيمي ، لصالح بناء وظائف إضافية كحزم منفصلة. هذا هو الاتجاه الذي أتفق معه. أعتقد أنه يمكن استخراج مكون ScrollSync من رد الفعل الافتراضي أو تكييفه للعمل مع نافذة التفاعل. ومع ذلك ، من أجل القيام بذلك ، ستحتاج نافذة رد الفعل إلى قبول الدعائم لـ scrollLeft و scrollTop بحيث يمكن إدارة إزاحة التمرير لشبكة الرأس مباشرةً.

هل هذه حالة استخدام ستكون على استعداد لدعمها؟ إذا لم يكن الأمر كذلك ، فهل لديك أي نصيحة بشأن الاتجاه الذي يجب أن أتبعه لتنفيذ ذلك بنفسي؟

شكرا لك على عملك في هذه المكتبة. باعتباري شخصًا استخدم خاصية التفاعل الافتراضي لبضع سنوات ، فأنا أقدر بساطة البدء في نافذة التفاعل ومدى أدائها بالنسبة لي دون الكثير من التدخل اليدوي.

التعليق الأكثر فائدة

هذا منطقي تماما. شكرا على اقتراحك! لقد نجحت ، ومن السهل جدًا إعدادها. من السهل جدًا أن لا تضمن بالتأكيد حزمة قائمة بذاتها. ربما مثال في المستندات ، لكن هذا هو مكالمتك على ما أظن. سأترك رابطًا إلى مثال Code Sandbox للعمل الخاص بي هنا في حالة تعثر أي شخص آخر في هذه المشكلة في المستقبل.

https://codesandbox.io/s/y3pyp85zm1

TLDR - ضع المرجع على شبكة الرأس ، ثم ضع هذا على شبكة الجسم.

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

ال 52 كومينتر

أولاً ، شكرًا على الكلمات الرقيقة وردود الفعل الإيجابية. يسعدني أن أسمع أن نافذة التفاعل تعمل بشكل جيد بالنسبة لك حتى الآن!

أوافق على أنه يمكن إصدار مكون مثل ScrollSync كحزمة قائمة بذاتها تعتمد على نافذة التفاعل ، لكنني لا أريد إضافته إلى هذا المشروع لأنه ليس أساسيًا للنافذة.

فيما يتعلق بسؤالك المحدد حول دعائم التمرير ، فهذا ليس تغييرًا سأكون على استعداد لإجرائه على المشروع لأنني بعد استخدامه مع التفاعل الافتراضي ، أدركت أنه يحتوي على بعض العيوب الخطيرة. لقد كتبت بالفعل عن ذلك في مدونة React إذا كنت فضوليًا:

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

يمكنك استخدام واجهة برمجة تطبيقات التمرير الإلزامي التي توفرها نافذة التفاعل لتحقيق سلوك مزامنة مشابه. سيكون عليك فقط استدعاء هذه الأساليب من دورات الحياة بدلاً من تمرير الدعائم.

نأمل أن يكون هذا منطقيًا ولكن لا تتردد في طرح أسئلة المتابعة إذا لم يكن كذلك!

هذا منطقي تماما. شكرا على اقتراحك! لقد نجحت ، ومن السهل جدًا إعدادها. من السهل جدًا أن لا تضمن بالتأكيد حزمة قائمة بذاتها. ربما مثال في المستندات ، لكن هذا هو مكالمتك على ما أظن. سأترك رابطًا إلى مثال Code Sandbox للعمل الخاص بي هنا في حالة تعثر أي شخص آخر في هذه المشكلة في المستقبل.

https://codesandbox.io/s/y3pyp85zm1

TLDR - ضع المرجع على شبكة الرأس ، ثم ضع هذا على شبكة الجسم.

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

شكرا على الرابط! هذا مدروس للغاية.

في الخميس ، 8 تشرين الثاني (نوفمبر) 2018 ، الساعة 1:09 مساءً ، كتب ريغان كيلر < [email protected] :

هذا منطقي تماما. شكرا على اقتراحك! لقد نجحت ،
ومن السهل جدًا إعداده. من السهل جدًا أن لا يحدث ذلك بالتأكيد
تضمن حزمة قائمة بذاتها. ربما مثال في المستندات ، ولكن هذا هو ملكك
اتصل أفترض. سأترك رابطًا إلى مثال Code Sandbox الخاص بي للعمل هنا
في حالة تعثر أي شخص آخر في هذه المشكلة في المستقبل.

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 التي هاجرتها للتو إلى هذا lib. سأقول أن الحل أعلاه يكون على الأقل بنفس الأداء إن لم يكن أفضل ، وأقل إيلامًا من استخدام 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

أي اقتراحات حول كيفية إصلاح هذا؟

مثال CodeSandbox

شكرا لك مقدما

carlosagsmendes بسبب شريط التمرير. على اليسار ، اجعل الارتفاع هو الارتفاع مطروحًا منه حجم شريط التمرير. للحصول على تناسق عبر الأجهزة ، أقوم يدويًا برمز حجم شريط التمرير باستخدام CSS. إذا كان المحتوى الخاص بك ديناميكيًا لدرجة أنه قد يكون هناك شريط تمرير أو لا ، فافعل شيئًا مثل "عدد العناصر * عناصر الحجم <العرض (يجب أن يكون لديك كل هذه القيم هناك)" ثم قم بتغيير الارتفاع بناءً على ذلك.

للحصول على تناسق عبر الأجهزة ، أقوم يدويًا برمز حجم شريط التمرير باستخدام CSS.

FWIW تحتوي حزمة dom-helpers على وظيفة سهلة الاستخدام scrollbarSize تخبرك بالعرض على الجهاز الحالي. قد يكون أجمل من الترميز الثابت.

bvaughn نعم! نسيت أن أذكر ذلك. ومع ذلك ، لم ينجح الأمر بالنسبة لنا. أعتقد أن المشكلة كانت أننا كنا نقوم بالفعل بعمل أشرطة تمرير مشفرة وهذا أربكها

شكرا لك. وسوف محاولة إعطائها!

هل يعمل هذا مع قائمة VariableSizeList الأفقية؟ حاولت ولكن متصفحي يتجمد.

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

إنه يعمل بشكل جيد مع VariableSizeGrid لكن رأسي يتخلف قليلاً عند التمرير.

ajaymore لذا

حقيقة ممتعة: إذا كنت تستخدم شريط التمرير يدويًا في المتصفح (مثل التمرير بالطريقة القديمة عن طريق سحب الشيء الصغير) ، فسيكون ذلك جيدًا تمامًا. تم دمج شيء الاستيفاء في تمرير عجلة التمرير / لوحة المسار

ajaymore لذا

حقيقة ممتعة: إذا كنت تستخدم شريط التمرير يدويًا في المتصفح (مثل التمرير بالطريقة القديمة عن طريق سحب الشيء الصغير) ، فسيكون ذلك جيدًا تمامًا. تم دمج شيء الاستيفاء في تمرير عجلة التمرير / لوحة المسار

ranneyd شكرا لك على هذه الاستجابة السريعة. أوافق على أنه قيد. ستعمل بشكل جيد على غالبية الأجهزة ، لذا لن يكون مصدر قلق كبير في ذلك الوقت.

@ bvaughn هل لعبت من قبل بـ position: sticky ؟ لم ألق نظرة عليه منذ فترة وأعلم أن دعم المتصفح ضئيل ، لكنني أتساءل عما إذا كانت هناك طريقة للاستفادة منه في المتصفحات التي يدعمها ...

لدي نفس المشكلة مثل ajaymore ، أي أن مزامنة مكونين بطيئة (كما هو مذكور ، تعمل بشكل جيد في التمرير اليدوي). أنا أختبر Chrome على جهاز كمبيوتر ، لذا يبدو أن هذه ليست مشكلة Mac فقط ... هل نجح شخص ما في حل هذه المشكلة بطريقة ما؟

alonrbar لدي نفس المشكلة على جهاز كمبيوتر يعمل بنظام Windows.

لقد وجدت للتو حلاً مناسبًا لي.
الفكرة العامة هي إنشاء "شبكة ظل" تخفي إلى الشبكة الأصلية وتسرق حدث 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 coords وتطبيق ذلك بطريقة ما على الشبكة الرئيسية.

أيضا barbalex re: فشل في الويندوز أيضا

قد يكون علم كروم في الواقع. تحقق مما إذا كان هناك أي شيء في chrome: // flags. ربما نفذت Microsoft أيضًا شيئًا مشابهًا.

يبدو أن المشكلة تتعلق بالتأكيد بالتمرير الذي يتم "تجانسه" بشكل أسرع من إطار الرسوم المتحركة للمتصفح. إذا كنت قلقًا من أن الأمر يتعلق فقط بالأداء / التأخر ، فحاول إجراء تنفيذ بسيط للغاية لمزامنة التمرير (حيث يقوم التمرير لواحد من div بتعيين موضع التمرير للآخر) ومعرفة ما إذا كنت تواجه نفس المشكلة. ربما يمكنك القيام بذلك باستخدام Vanilla JS النقي للتخلص من React باعتبارها الجاني

Ahhhranneyd ، أنت محق في حظر أحداث النقر ... بحاجة إلى مزيد من التفكير في ذلك ...

أعتقد أن ميزة التمرير المترابطة في chrome: // flags لها تأثير كبير: إيقاف تشغيلها يجعل التمرير باستخدام عجلة الماوس سلسًا مثل التمرير باستخدام شريط التمرير بالنسبة لي.

2019-07-15_00h03_42

alonrbar ماذا عن هذا:

أضف معالج التمرير إلى الشبكة الرئيسية. في ذلك تقوم بعمل e.preventDefault(); أو شيء ما لمنع التمرير الفعلي. ثم تنظر إلى الحدث لمعرفة مقدار التمرير الذي سيتم تمريره ، وهو ما يفعله لنقل العناصر الأخرى المتزامنة ، ولكن بعد ذلك يمكنك استخدام ذلك للتمرير يدويًا لهذا العنصر نفسه. لذا بدلاً من التمرير A ، ثم استخدام هذه المعلومات للتمرير B ، فإنك تعترض التمرير على A ، وتلغيه ، ثم تستخدمه للتمرير B و A نفسه. هل سينجح هذا؟

أنا لست بجهاز كمبيوتر للاختبار. رائع ولكن يمكنني العمل. bvaughn الأفكار؟

barbalex أنت على حق ، إنه يعمل معي أيضًا ولكن لا يمكنني جعل جميع المستخدمين يقومون بتشغيل وإيقاف تشغيل أعلام الكروم 😔

ranneyd لقد نظرت للتو في الأمر وتلاعبت قليلاً بالشفرة المصدر ( الرابط ) ولكن يبدو أنه لا يمكنك تعطيل أحداث التمرير ، الشيء الوحيد الذي يمكنك فعله هو اختطاف كل عجلة الماوس وأحداث اللمس ولوحة المفاتيح التي قد تتسبب في التمرير و منعهم. يحتوي الرابط (SO) على مقتطف رمز قصير لذلك ولكن هذا يجعله أكثر اختراقًا لذلك ما زلت أفكر في الأمر ...

alonrbar نعم ، لن تنجح مطالبة المستخدمين بالقيام بذلك. يمكنك _يمكنك _ منحهم رابطًا صعبًا إذا كنت تريد _do_ أن تجرب: chrome: // flags / # disable -threaded-scrolling

alonrbar eh ، هذا ليس جيدًا ولكنه ليس رهيباً إذا قمنا بتطبيقه على الشبكة ، سنقوم بالفعل بالاختراق معًا.

انها مجرد عجلة التمرير ، أليس كذلك؟ هل مفاتيح الأسهم تجعل ذلك يحدث أيضًا؟

هذا مقتطف من الإجابة المقبولة هنا: https://stackoverflow.com/questions/4770025/how-to-disable-scrolling-temporarily

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 لذلك قمت بتنفيذ تطبيق غير افتراضي يستخدم في الواقع تحديد المواقع المطلق. الشيء الوحيد الذي يتم تمريره هو الحاوية الخارجية. عند التمرير ، يقوم بتحديث القيم العلوية / اليسرى التي تُستخدم لوضع العناصر الداخلية بشكل مطلق. لذلك لا يوجد scrollTo أو scrollTop = ... ، وهو ما وجدته يسبب لي الكثير من الحزن. علاوة على ذلك ، توجد أشرطة التمرير دائمًا على الجزء الخارجي من الشبكة الكاملة.

يمكن أن يحتوي هذا الشيء الذي صنعته على "رؤوس مجمدة" بشكل ديناميكي من جميع الجوانب. إنه مثال تقريبي للغاية / poc.

من الواضح أنها تفتقر إلى الظاهرية وهي مشكلة خطيرة. لسوء الحظ ، لا أعرف ما إذا كان بإمكاني التفاف الشبكة من هذه المكتبة فيها ، نظرًا لأن هذا lib يعمل بشكل أساسي على التمرير وهذا يلغي التمرير الداخلي للشبكة. لست متأكدا ما هي الخطوة التالية.

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

أعتقد أنه يمكن تطبيق نفس منطق المحاكاة الافتراضية الذي يغذي هذا lib على هذا.

ranneyd تنفيذ رائع جدا!

أحب تكوين الشبكة الديناميكية :)

ولكن الأهم من ذلك ، قد تكون فكرة استخدام تحديد المواقع المطلق بدلاً من التمرير هي المفتاح لحل هذه المشكلة. من خلال تقديم div آخر ، قمت بفصل الحدث onscroll من تمرير المحتوى الفعلي والذي أعتقد أنه يفتح إمكانيات جديدة.

إذا تمكنت من ربط طريقة react-window 's render فقد تتمكن من استبدال الأسطر 459 والتشغيل. ستتمكن بعد ذلك من ربط حدث onscroll وتعطيل تأثيره عند الحاجة (ستظل أشرطة التمرير تتحرك دائمًا ولكن يمكنك التحكم في المحتوى لمنعه من التغيير).

alonrbar لذلك قمت بالفعل بإعادة تطبيق خوارزمية المحاكاة الافتراضية. إنه ليس جيدًا ولكن بالنظر إلى الكود المصدري عندما تصل هذه الشبكة إلى الظاهرية الفعلية ، فهي في الأساس نفس alg. ومع ذلك ، فإن الأداء لائق بالفعل. يمكنني الوصول إلى شبكة مقاس 200 × 200 دون أي تأخير ، و 500 × 500 بقليل فقط. إذا سمح لي مديري بالعمل على هذا بعد الآن ، فسأحاول تنفيذ طريقة التقديم كما اقترحت ، والتي ربما تكون في الواقع سهلة للغاية.

إذا كنت مهتمًا بالشبكة التي أنشأتها ، يمكنني نشرها في مكان ما. إذا كنت تريد أن تأخذه وربطه بهذا lib ، فكن ضيفي 😏

ranneyd نعم أود أن أرى حل الظاهرية الذي قمت

أيضًا ، كنت أحاول استخدام الكود الخاص بك اليوم لتنفيذ التغييرات على طريقة العرض كما اقترحت ولكن بعد إعادة قراءة الكود الخاص بك ، لاحظت أنك لم تقم بالفعل بفصل التمرير بالطريقة التي اعتقدت أنك قمت بها. ما أعنيه بذلك هو أن أشرطة التمرير والحدث onscroll لا يزالان غير منفصلين عن تمرير المحتوى الفعلي. لقد تقدمت بفكرتك إلى الأمام ونفذت تمريرًا غير متصل حقًا:

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

إذا نظرت إلى الرمز ، فسترى أنه إذا أزلنا الخصائص top و left من المكوّن Content ، فلن يتم تمرير المحتوى حتى إذا كانت أشرطة التمرير تفعل . لقد تحققت أيضًا من عدم حظر أحداث الماوس هذه المرة. 😂
أعتقد أنه من الممكن الآن استخدام هذا التطبيق لتجاهل حدث onscroll ومزامنة شبكات متعددة يدويًا. لكن هذا بالطبع يتطلب المزيد من العمل ، لذا سيتعين عليه الانتظار في المرة القادمة ...

أثارت هذه المناقشة فضولًا ، لذلك قمت بإعداد مشروع لمقارنة نافذة تفاعل متزامنة مع التمرير Grid s مع تفاعل افتراضي افتراضي MultiGrid ، حتى أتمكن من التعرف على كيفية مقارنة الأداء :
https://github.com/bvaughn/react-window-vs-react-virtualized-synced-grids

حاولت أن أجعل كل استخدام متشابهًا قدر الإمكان. بعض الملاحظات الأولية:

  • تبدو نافذة رد الفعل أبطأ بشكل ملحوظ في وضع DEV ولكنها تشعر بأنها أسرع قليلاً / أكثر استجابة في بنية الإنتاج. (من الصعب استبعاد التحيز من جانبي هنا. الفرق في الإنتاج أصغر.)
  • تقوم نافذة رد الفعل أيضًا بالكثير من الالتزامات ، نظرًا لأن حدث التمرير يقوم أولاً بتحديث الشبكة النشطة ، ثم الشبكة السلبية عبر تحديث متتالي. لقد قمت بعمل تصحيح على Grid لدعم تمرير معالج حدث تمرير "أصلي" (React) (ليتم استدعاؤه ضمن التحديث المجمع لـ React) بدلاً من onScroll (والذي يتم استدعاؤه أثناء الالتزام مرحلة). يبدو هذا وكأنه تغيير واعد جدًا عندما اختبرته محليًا ، لأنه يتجنب عمليات العرض المتتالية المنفصلة ، لذلك ربما سأغير التوقيت الافتراضي onScroll .

bvaughn لقد حاولت إنشاء رمز تأثير التمرير الفانيليا الأساسي ، باستخدام divA -> onScroll -> setState -> ref.scrollTop ولا يزال يتعذر عليه الالتفاف على هذا الشيء ذي التمرير الملولب بالكروم. من المسلم به أنني لم أتحايل على حالة رد الفعل وقمت بتعيين المرجع. إذا لم تتمكن من الحصول على onScroll -> scrollTop بسرعة كافية في أقل عدد ممكن من الخطوات ، كيف يمكنك إصلاحه؟ هل أفتقد شيئًا تمامًا؟ يبدو أن الشبكات لا تحتاج إلى التحرك بناءً على التمرير (أو إعداد scrollTop).

الهدف دائمًا هو أن تكون JavaScript في أسرع وقت ممكن بحيث تواكب التمرير الذي يدير سلسلة الرسائل.

من المسلم به أنني لم أتحايل على حالة التفاعل وقمت بتعيين المرجع التمرير أعلى داخل معالج onScroll

فقط لأكون واضحًا ، هذا ليس ما يفعله الريبو أيضًا. أنا فقط أقوم بتعيين إزاحة التمرير في الشبكة المنفعلة في نفس معالج الأحداث (React-wraped) مثل الشبكة النشطة ، لذلك تقوم React بتجميع تحديثاتها في عملية تصيير + تنفيذ واحدة. ربما لا يحدث هذا فرقًا كبيرًا حتى نكون صادقين.

ranneyd و @ bvaughn يشاركونك ملاحظاتي حتى الآن:

  1. تعمل العديد من التطبيقات بشكل جيد بما فيه الكفاية على متصفح سطح المكتب ، ولكن حالة الاستخدام الأساسية الخاصة بي هي في الواقع على الأجهزة المحمولة وهناك أرى اختلافات هائلة في الأداء.

  2. تعمل مزامنة شبكتين غير افتراضيتين بسيطتين بشكل جيد (ليست مثالية بنسبة 100٪ ولكنها قريبة بدرجة كافية ، حتى على الهاتف المحمول). هذا هو عملي الساذج ، ولكنه عملي ، والتنفيذ وحالة اختبار لرؤيتها في العمل ( yarn storybook ).

  3. يؤدي استخدام التمرير "المتحكم فيه" (كما اقترحت في هذا التعليق ) إلى الحفاظ على تزامن الشبكتين بنسبة 100٪ ولكنه بطيء جدًا على سطح المكتب وبطيء جدًا على الهاتف المحمول. هذا هو صدقي فيه (خشن جدًا ...): https://github.com/alonrbar/react-window/commit/c39ce7006dbd590d9c640e37f8a1e78826e4688e

    ومع ذلك ، فإنني متحمس لمعرفة ما إذا كانت الإستراتيجية "الخاضعة للرقابة" يمكن أن تعمل بطريقة ما لحل هذه المشكلة في قائمة افتراضية واحدة.

  4. كنت أفكر ، كما اقترحت أنتما الاثنان بالفعل ، أن نقل _callPropsCallbacks إلى المعالج _onScroll مباشرةً قد يساعد في تقليل تأخر المزامنة إلى الحد الأدنى. EDIT - لقد جربته الآن ، ولم يساعد ذلك حقًا 😞

  5. بأي طريقة تتعامل معها ، فإن فكرة جيدة IMHO هي فصل منطق المحاكاة الافتراضية عن بقية المكون (ربما حتى خطاف؟) بحيث يمكن التعامل مع عناصر التقديم والتمرير بشكل منفصل ، ومقارنتها بسهولة وحتى التبديل في وقت التشغيل بناءً على الدعائم المكون. سيسمح أيضًا بتصدير هذا المنطق ويسمح للمستخدمين بتنفيذ مكونات مخصصة (مثل حل ranneyd في هذا التعليق ) بناءً على نفس المنطق.

أفكار؟

alonrbar ، الحل الخاص بي ، الذي يقوم 300 × 300 حيث يصبح بطيئًا (لاحظ أنه يظل متزامنًا بنسبة 100٪ من الوقت ، والتمرير فقط يتأخر قليلاً). في الأحجام الكبيرة أعتقد أنها مجرد معالجة / رسم خرائط على مجموعة كبيرة. أعتقد أن هناك العديد من التحسينات
ومع ذلك ، يمكنني القيام بذلك ، ولست مقتنعًا تمامًا بأن له علاقة بمزامنة التمرير بقدر ما يتعلق بتطبيق المحاكاة الافتراضية البسيط إلى حد ما. يمكنني القيام بمزيد من التخزين المؤقت ، على سبيل المثال ، ويمكنني حساب ما إذا كان يجب تحويل الخلية إلى افتراضية أم لا قبل ذلك لتجنب استدعاء وظيفة التقديم على الإطلاق (ومن ثم تخزينها مؤقتًا بشكل أفضل).

لم أختبر أي شيء على الهاتف المحمول. سأقدم لك بعض التعليمات البرمجية لتجربتها قليلاً.

أريد حقًا إلقاء نظرة على اختبار bvaughn . يقول إن التثبيت مباشرة في اللفافة الأصلية يصلحها ، وأنت تقول أنها لا تفعل ذلك. اريد ان ارى بنفسي.

بقدر ما يتم سحب منطق المحاكاة الافتراضية إلى خطاف أو وظيفة مستقلة ، فإنه يصبح صعبًا جدًا لأن المنطق مرتبط بشكل جوهري بالمنظر. يبدو أيضًا أن الكثير من تعديلات الأداء تتضمن التخزين المؤقت وعناصر الذاكرة التي قد يكون من الصعب تغليفها في خطاف أو وظيفة واحدة ، أو على الأقل إلى الحد الذي تحصل فيه على نفس الأداء. لكنني سألقي نظرة على ما لدي وأرى كم من المنطق يمكنني استخلاصه.

ملاحظة:
أحد الأشياء التي فكرت فيها للتو والتي من المحتمل ألا تنجح هي القيام بشيء يشبه التنديد + انتقالات css. إذا قمنا بتنفيذ حدث تمرير واحد فقط كل 100 مللي ثانية أو شيء ما وقمنا بتحريك الحركة ، فقد تبدو أفضل. قد يبدو أيضًا أقل استجابة إلى حد كبير. يشبه الأمر نوعًا ما الطريقة التي فعلوا بها لعبة World of Warcraft (إذا كان هناك تأخر أو زمن انتقال عالٍ ، فسيقومون بتحريك شخصية في خط مستقيم ثم تصحيحها بمجرد حصولهم على معلومات حول المكان الذي ذهبوا إليه بالفعل).

أريد حقًا إلقاء نظرة على اختبار bvaughn . يقول إن التثبيت مباشرة في اللفافة الأصلية يصلحها ، وأنت تقول أنها لا تفعل ذلك. اريد ان ارى بنفسي.

لم أقل ذلك: ابتسم: لقد قلت للتو إنها تتجنب عرضًا إضافيًا غير ضروري وطفرة DOM. مدى تأثير ذلك على الأداء الفعلي غير واضح بالنسبة لي. يبدو أنه تغيير إيجابي شامل ، بغض النظر.

alonrbarbvaughn I ضرورة توثيق الواقع قانون بلدي، ولكن هنا الإصدار الأخير:

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

alonrbarbvaughn حتى هنا شيء المرح اكتشفت للتو:

لا يعمل الحل الخاص بي في Chrome 75 على شاشة macbook. عندما لم يكن زملائي في العمل قد قاموا بتحديث الكروم ، فقد نجح. إذا كانوا يستخدمون شاشة خارجية يعمل. على شاشة الكمبيوتر المحمول الخاص بهم يتأخر.
😩

حسنًا ... هناك بعض الاختلافات مع الشاشات الخارجية ، مع معدل التحديث أو التحجيم. عندما تقول "لا يعمل" ماذا تقصد بالتحديد؟

خطأي. أعني أن التمرير لم يعد متزامنًا. إذا قمت باستنساخ الريبو الخاص بي وتشغيله ، يمكنك مقارنة الشاشة الخارجية مع شاشة الكمبيوتر المحمول. على الشاشة تكون متزامنة تمامًا. تومض الرؤوس على شاشة الكمبيوتر المحمول (لا تقم بالتحديث بنفس سرعة عنصر التمرير).

لقد استسلمت بالفعل وأحاول ذلك باستخدام position: sticky . إنه يعمل بالفعل. دعم المتصفح ليس بنسبة 100٪ ، لكنه في الواقع أفضل بكثير مما كان عليه قبل عام.

يوجد هذا lib وهو عبارة عن polyfill غير متعدد التعبئة قد يعمل ، لكن المتصفحات التي نستهدفها تدعم الميزة.
https://github.com/dollarshaveclub/stickybits

alonrbarbvaughn هنا هو أحدث إصدار. يستخدم position: sticky . أشرح ذلك قليلاً في التمهيدي. لا يزال الرمز بحاجة إلى التوثيق.

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

لا يعمل الحل الخاص بي في Chrome 75 على شاشة macbook. عندما لم يكن زملائي في العمل قد قاموا بتحديث الكروم ، فقد نجح. إذا كانوا يستخدمون شاشة خارجية يعمل. على شاشة الكمبيوتر المحمول الخاص بهم يتأخر.
😩

ranneyd لقد

bvaughn أعتقد أن هذا يجب أن يكون عليه. يقول نظام التشغيل إنه يرسل 60 هرتز وأعتقد أن مواصفات الشاشة تقول 60 هرتز ، لكن لن يفاجئني إذا كان شخص ما يكذب 😂

ranneyd ، يؤسفني أن أقول إنني لست متاحًا جدًا في الوقت الحالي ، لذا لم أتمكن من إلقاء نظرة مناسبة على الحل الذي useVirtual ربط وفصل المنطق بأناقة عن التصيير.
أعتقد أنه سيكون رائعًا إذا كان بإمكانك بطريقة ما إنشاء طلب سحب react-window ، واستخدامه هناك وربما كشف طريقة renderTable من نوع ما حيث يمكنك توصيل منطق العرض الخاص بك. بهذه الطريقة يمكن لمكتبتك فقط التفاف نافذة التفاعل بدلاً من استبدالها. أعتقد أنه سيكون من المفضل إذا استطعنا الاستفادة من هذا النهج وحل المشكلة داخل react-window والتي تم اختبارها بالفعل إلى حد كبير وانتشارها على نطاق واسع.

من ناحية أخرى ، أعتقد أنه إذا كان scrollTo سيستخدم معالجة DOM مباشرة (ربما بالإضافة إلى ضبط الحالة ولكن على أي حال قبل القيام بذلك) ، فستصبح نتيجة مزامنة جدولين أكثر سلاسة. إذا لم أكن مخطئًا ، فقد اقترح bvaughn أيضًا شيئًا ما في هذا الاتجاه.

ranneyd يبدو حل الطاولة اللاصقة رائعًا حقًا وسيسعدني جدًا أن أتمكن من استخدامه. هل تفكر في إطلاق واجهة برمجة تطبيقات قابلة للاستخدام لها على npm ؟

تضمين التغريدة

لذلك ، بعد فترة وجيزة عدت مؤخرًا إلى هذه المشكلة. باستخدام التعليمات البرمجية والأفكار من كل من المكتبات الخاصة بك وكذلك من Recyclerlistview والشبكة الافتراضية للتفاعل ، تمكنت أخيرًا من تحقيق السلوك الذي أردته ، أي شبكة عالية الأداء مع صفوف وأعمدة ثابتة تعمل بشكل جيد على كل من سطح المكتب والجوال الأجهزة (التمرير السلس بدون أي خلايا فارغة / بيضاء).

TLDR منه هو أنني أستخدم إعادة التدوير مع تحديد المواقع اللزجة ويمكن العثور على الجزء الأكثر إثارة للاهتمام من الكود هنا في هذه الطريقة .

الاعتمادات والمزيد حول الدافع لكتابة حل آخر هنا . شكرا!

سأترك رابطًا إلى مثال Code Sandbox للعمل الخاص بي هنا في حالة تعثر أي شخص آخر في هذه المشكلة في المستقبل.
https://codesandbox.io/s/y3pyp85zm1

جميل جدًا ، حتى تقوم بالتمرير تمامًا إلى اليمين: عندئذٍ تكون الرؤوس غير محاذية للمحتوى (حسب عرض شريط التمرير العمودي).
هل ستتمكن بأي حال من إيجاد حل لذلك؟
شكرا

سأترك رابطًا إلى مثال Code Sandbox للعمل الخاص بي هنا في حالة تعثر أي شخص آخر في هذه المشكلة في المستقبل.
https://codesandbox.io/s/y3pyp85zm1

جميل جدًا ، حتى تقوم بالتمرير تمامًا إلى اليمين: عندئذٍ تكون الرؤوس غير محاذية للمحتوى (حسب عرض شريط التمرير العمودي).
هل ستتمكن بأي حال من إيجاد حل لذلك؟
شكرا

الكشف الكامل: لقد صنعت نسختي الخاصة من البداية. لديها افتراضية خاصة بها تعتمد بشكل كبير على هذا lib. السبب في أنني فعلت ذلك هو وجود مشكلة في أجهزة MacBooks ومع بعض أعلام Chrome حيث تحدث الرسوم المتحركة للتمرير بتوقيت مختلف عن JS. يتطلب استخدام ScrollSync عددًا كبيرًا جدًا من استدعاءات الوظائف التي تم تمريرها وكان بطيئًا جدًا. اضطررت بشكل أساسي إلى إعادة إنشاء المكتبة برؤوس ثابتة في جوهرها (لم أستخدم position: sticky كما قلت سابقًا في هذا الموضوع. أحتاج إلى تحميل ما لدي الآن في مرحلة ما).

ومع ذلك ، فإن الطريقة التي يمكنني بها التغلب على هذه المشكلة هي إما عن طريق overflow: scroll لفرض شريط تمرير ثم إضافة تلك المساحة المتروكة إلى الخلية الأخيرة ، أو تحديد ما إذا كان هناك شريط تمرير ديناميكيًا (وما هو عرضه) باستخدام المرجع ووضع div غير مرئي بداخله ، وقياس حجم شريط التمرير ، وحذف عقدة DOM.

لقد استخدمت overflow-y: overlay مما يجعل تراكب شريط التمرير. لسوء الحظ ، إنه يعمل فقط لمتصفحات webkit.

حسنا، شكرا للمعلومات. يبدو حقًا أن الرؤوس اللاصقة هي حاجة شائعة ، وأن مجرد إضافة خيار الصف الأول اللاصق إلى نافذة رد الفعل سيجعلها بديلاً عن رد الفعل الافتراضي في العديد من الحالات. ألا تعتقد ذلك؟

شكرًا لكranneyd مرة أخرى ، لقد اتبعت

يتسبب الفائض المخفي في أشرطة التمرير في حدوث اختلال في المحاذاة بالقرب من نهاية الشبكة:

gridalignement

أي اقتراحات حول كيفية إصلاح هذا؟

مثال CodeSandbox

شكرا لك مقدما

في وقت متأخر من الحفلة ، ولكن إذا أضفت صفًا واحدًا إلى اليسار (لقد فعلت ذلك أيضًا لعدم الاضطرار إلى مزامنة اليسار المرجع -> الشبكة الرئيسية

  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 التقييمات