Kürzlich stieß ich bei der Entwicklung und Iteration einer bestimmten Plattform auf eine Situation, in der eine extrem lange Liste in einem Antd-Modal verschachtelt war und langsam geladen wurde und es zu Störungen kam. Daher habe ich beschlossen, zur Optimierung des Gesamterlebnisses eine virtuelle Scroll-Liste von Grund auf neu zu implementieren. Vor der Transformation:
Nach der Transformation:
Leistungsvergleichsdemo: codesandbox.io/s/av-list-… 0x0-GrundlagenWas ist also virtuelles Scrollen/Auflisten?
(Aus dem obigen Bild können wir erkennen, dass die tatsächlichen Elemente/Inhalte, die Benutzer jedes Mal sehen können, nur die Elemente 4 bis 13 sind, also 9 Elemente.) 0x1 Implementierung einer virtuellen Liste mit „fester Höhe“Zuerst müssen wir einige Variablen/Namen definieren.
Da wir den Inhalt nur im sichtbaren Bereich rendern, müssen wir die Höhe der ursprünglichen Liste beibehalten, um das Verhalten des gesamten Containers ähnlich einer langen Liste (Scrollen) beizubehalten. Daher entwerfen wir die HTML-Struktur wie folgt <!--ver 1.0 --> <div Klassenname="vListContainer"> <div Klassenname="Phantominhalt"> ... <!-- Artikel-1 --> <!-- Artikel-2 --> <!-- Artikel-3 --> .... </div> </div> In:
Als Nächstes binden wir eine OnScroll-Antwortfunktion an vListContainer und berechnen unseren Startindex und Endindex in der Funktion entsprechend der ScrollTop-Eigenschaft des nativen Scroll-Ereignisses.
Wir brauchen eine feste Listenelementhöhe: rowHeight
Gesamthöhe der Liste: Phantomhöhe = Gesamthöhe * Zeilenhöhe Daher können wir im onScroll-Callback die folgende Berechnung durchführen: beimScrollen(evt: beliebig) { // Bestimmen Sie, ob es sich um ein Scroll-Ereignis handelt, auf das wir reagieren müssen, wenn (evt.target === this.scrollingContainer.current) { const { scrollTop } = evt.target; const { StartIndex, Gesamt, Zeilenhöhe, Limit } = dies; // Den aktuellen StartIndex berechnen const currentStartIndex = Math.floor(scrollTop / rowHeight); // Wenn currentStartIndex sich von startIndex unterscheidet (wir müssen die Daten aktualisieren) wenn (aktuellerStartIndex !== startIndex ) { dieser.startIndex = aktuellerStartIndex; this.endIndex = Math.min(currentStartIndexx + limit, total - 1); this.setState({ scrollTop }); } } } Sobald wir den Startindex und den Endindex haben, können wir die entsprechenden Daten rendern: renderDisplayContent = () => { const { Zeilenhöhe, Startindex, Endindex } = dies; const Inhalt = []; // Beachten Sie, dass wir hier <= verwenden, um x+1 Elemente zu rendern und so das Scrollen kontinuierlich zu machen (immer im Einklang mit der Beurteilung rendern und x+2 rendern) für (lass i = Startindex; i <= Endindex; ++i) { // rowRenderer ist eine benutzerdefinierte Methode zum Rendern von Listenelementen, die einen Index i und // den Stil erhalten muss, der der aktuellen Position entspricht Inhalt.push( rowRenderer({ Index: ich, Stil: { Breite: '100%', Höhe: Zeilenhöhe + 'px', Position: "absolut", links: 0, rechts: 0, oben: i * Zeilenhöhe, Rahmen unten: "1px durchgehend #000", } }) ); } Inhalt zurückgeben; };
Prinzip:Wie wird dieser Scroll-Effekt erzielt? Zuerst rendern wir einen „Phantom“-Container mit der tatsächlichen Listenhöhe in vListContainer, um dem Benutzer das Scrollen zu ermöglichen. Zweitens hören wir auf das Ereignis „onScroll“ und berechnen dynamisch den Startindex, der dem aktuellen Bildlauf-Offset entspricht (wie viel nach dem Hochscrollen verborgen wird), jedes Mal, wenn der Benutzer den Bildlauf auslöst. Wenn wir feststellen, dass sich der neue Boden von dem aktuell angezeigten Index unterscheidet, weisen wir einen Wert zu und „setState“ löst eine Neuzeichnung aus. Wenn der aktuelle Bildlaufversatz des Benutzers keine Indexaktualisierung auslöst, verfügt die virtuelle Liste aufgrund der Länge des Phantoms selbst über die gleiche Bildlauffunktion wie eine normale Liste. Wenn das Neuzeichnen ausgelöst wird, kann der Benutzer das Neuzeichnen der Seite nicht wahrnehmen, da wir den Startindex berechnen (da der nächste Frame des aktuellen Bildlaufs mit dem von uns neu gezeichneten Inhalt übereinstimmt). Optimierung:Bei der virtuellen Liste, die wir oben implementiert haben, lässt sich unschwer feststellen, dass die Liste flackert bzw. nicht rechtzeitig gerendert wird oder leer ist, sobald ein schnelles Wischen durchgeführt wird. Erinnern Sie sich, was wir am Anfang gesagt haben: die maximale Anzahl sichtbarer Zeilen zum Rendern für Benutzer + „BufferSize“? Für den eigentlichen Inhalt, den wir rendern, können wir das Konzept des Puffers hinzufügen (das heißt, mehr Elemente nach oben und unten rendern, um das Problem zu lösen, dass beim schnellen Gleiten nicht genügend Zeit zum Rendern bleibt). Die optimierte onScroll-Funktion lautet wie folgt: beimScrollen(evt: beliebig) { ........ // Den aktuellen StartIndex berechnen const currentStartIndex = Math.floor(scrollTop / rowHeight); // Wenn currentStartIndex sich von startIndex unterscheidet (wir müssen die Daten aktualisieren) wenn (aktuellerStartIndex !== originStartIdx) { // Beachten Sie, dass wir eine neue Variable namens originStartIdx eingeführt haben, die dieselbe Rolle wie startIndex spielt. //Gleicher Effekt, zeichnen Sie den aktuellen realen Startindex auf. this.originStartIdx = aktuellerStartIndex; //Führen Sie eine Header-Pufferberechnung für startIndex durch. this.startIndex = Math.max(this.originStartIdx - bufferSize, 0); // Tail-Buffer-Berechnung für endIndex durchführen this.endIndex = Math.min( dies.originStartIdx + dies.limit + Puffergröße, insgesamt - 1 ); Dies.setState({ scrollTop: scrollTop }); } }
0x2 Listenelement Höhenanpassung
1. Ändern Sie die Eingabedaten und übergeben Sie die Höhe, die jedem Element entspricht dynamicHeight[i] = xx ist die Zeilenhöhe des Elements i
2. Zeichnen Sie das aktuelle Element zuerst außerhalb des Bildschirms und richten Sie die Höhe für die Messung aus, bevor Sie es in den sichtbaren Bereich des Benutzers rendern
3. Übergeben Sie eine estimateHeight-Eigenschaft, um zunächst die Zeilenhöhe zu schätzen und darzustellen. Rufen Sie dann nach Abschluss des Renderings die tatsächliche Zeilenhöhe ab und aktualisieren und zwischenspeichern Sie diese.
<!--ver 1.0 --> <div Klassenname="vListContainer"> <div Klassenname="Phantominhalt"> ... <!-- Artikel-1 --> <!-- Artikel-2 --> <!-- Artikel-3 --> .... </div> </div> <!--ver 1.1 --> <div Klassenname="vListContainer"> <div Klassenname = "Phantominhalt" /> <div Klassenname="tatsächlicher Inhalt"> ... <!-- Artikel-1 --> <!-- Artikel-2 --> <!-- Artikel-3 --> .... </div> </div>
getTransform() { const { scrollTop } = dieser.Zustand; const { Zeilenhöhe, Puffergröße, Ursprungsstart-Idx } = dies; // Aktueller Gleitoffset - Aktuelle gekürzte (nicht vollständig verschwundene) Distanz - Kopfpufferdistanz return `translate3d(0,${ nach oben scrollen - (scrollTop % Zeilenhöhe) – Math.min(originStartIdx, Puffergröße) * Zeilenhöhe }px,0)`; }
(Hinweis: Wenn kein hoher Grad an Anpassungsfähigkeit vorhanden ist und keine Zellwiederverwendung implementiert ist, ist die Leistung beim Rendern von Elementen in Phantom über Absolute besser als über Transform. Dies liegt daran, dass der Inhalt bei jedem Rendern neu angeordnet wird. Wenn jedoch Transform verwendet wird, entspricht dies (Neuanordnen + Transformieren) > Neuanordnen.)
Limit = Math.ceil(Höhe / geschätzte Höhe)
Schnittstelle CachedPosition { Index: Zahl; // Der Index des Elements, das dem aktuellen Post entspricht. Op: Zahl; // Obere Position Bottom: Zahl; // Untere Position Höhe: Zahl; // Elementhöhe Wert: Zahl; // Unterscheidet sich die Höhe von der vorherigen (Schätzung)?} zwischengespeichertePositionen: ZwischengespeichertePosition[] = []; // CachedPositions initialisieren initCachedPositions = () => { const { geschätzteZeilenhöhe } = dies; diese.cachedPositions = []; für (lass i = 0; i < this.total; ++i) { diese.cachedPositions[i] = { Index: ich, Höhe: geschätzteZeilenhöhe, // Benutze geschätzteHöhe, um zuerst zu schätzen. Oben: i * geschätzteZeilenhöhe, // Wie oben. Unten: (i + 1) * geschätzteZeilenhöhe, // Wie oben. dWert: 0, }; } };
this.phantomHeight = this.cachedPositions[cachedPositionsLen - 1].bottom;
KomponenteDidUpdate() { ...... // actualContentRef muss aktuell vorhanden sein (bereits gerendert) + total muss > 0 sein wenn (dieser.aktuelleInhaltsbezug.aktuell && dieses.gesamt > 0) { dies.updateCachedPositions(); } } updateCachedPositions = () => { // zwischengespeicherte Elementhöhe aktualisieren Konstante Knoten: NodeListOf<any> = this.actualContentRef.current.childNodes; const start = Knoten[0]; // Höhenunterschied für jeden sichtbaren Knoten berechnen … nodes.forEach((node: HTMLDivElement) => { wenn (!Knoten) { // zu schnell scrollen?... zurückkehren; } const rect = node.getBoundingClientRect(); const { Höhe } = Rechteck; const index = Zahl(node.id.split('-')[1]); const oldHeight = this.cachedPositions[index].height; const dValue = oldHeight – Höhe; wenn (dWert) { this.cachedPositions[index].bottom -= dValue; this.cachedPositions[index].height = Höhe; this.cachedPositions[index].dValue = dValue; } }); // führe ein einmaliges Höhenupdate durch … Lassen Sie startIdx = 0; wenn (Start) { startIdx = Zahl(start.id.split('-')[1]); } const cachedPositionsLen = this.cachedPositions.length; Lassen Sie cumulativeDiffHeight = this.cachedPositions[startIdx].dValue; this.cachedPositions[startIdx].dValue = 0; für (lass i = startIdx + 1; i < cachedPositionsLen; ++i) { const item = this.cachedPositions[i]; // Höhe aktualisieren diese.cachedPositions[i].oben = diese.cachedPositions[i - 1].unten; this.cachedPositions[i].bottom = this.cachedPositions[i].bottom - kumulativeDifferenzhöhe; wenn (item.dValue !== 0) { kumulativeDifferenzhöhe += item.dValue; item.dWert = 0; } } // Aktualisiere die Höhe unseres Phantom-Divs const Höhe = this.cachedPositions[cachedPositionsLen - 1].bottom; this.phantomHeight = Höhe; this.phantomContentRef.current.style.height = `${height}px`; };
getStartIndex = (scrollTop = 0) => { let idx = binarySearch<CachedPosition, Zahl>(this.cachedPositions, scrollTop, (aktuellerWert: ZwischengespeichertePosition, Zielwert: Zahl) => { const currentCompareValue = aktuellerWert.unten; wenn (aktuellerVergleichswert === Zielwert) { gibt CompareResult.eq zurück; } if (aktuellerVergleichswert < Zielwert) { gibt CompareResult.lt zurück; } gibt CompareResult.gt zurück; } ); const targetItem = this.cachedPositions[idx]; // Geben Sie uns im Falle einer binären Suche nicht sichtbare Daten (ein IDX des aktuell sichtbaren Werts – 1) … wenn (Zielelement.unten < scrollTop) { idx += 1; } idx zurückgeben; }; beimScroll = (evt: beliebig) => { wenn (evt.target === dieser.scrollingContainer.current) { .... const currentStartIndex = this.getStartIndex(scrollTop); .... } };
export enum Vergleichsergebnis { Gleichung = 1, es, gt, } Exportfunktion binäreSuche<T, VT>(Liste: T[], Wert: VT, Vergleichsfunktion: (aktuell: T, Wert: VT) => Vergleichsergebnis) { lass start = 0; let end = Listenlänge - 1; lass tempIndex = null; während (Start <= Ende) { tempIndex = Math.floor((Start + Ende) / 2); const mittlerer Wert = Liste[tempIndex]; const compareRes: CompareResult = Vergleichsfunktion (mittlerer Wert, Wert); wenn (compareRes === CompareResult.eq) { gibt TempIndex zurück; } wenn (compareRes === CompareResult.lt) { Start = Temperaturindex + 1; } sonst wenn (compareRes === CompareResult.gt) { Ende = TempIndex - 1; } } gibt TempIndex zurück; }
getTransform = () => `translate3d(0,${this.startIndex >= 1 ? this.cachedPositions[this.startIndex - 1].bottom : 0}px,0)`;
Oben finden Sie Einzelheiten zur Implementierung einer hochadaptiven virtuellen Liste in React. Weitere Informationen zur adaptiven virtuellen Liste von React finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM! Das könnte Sie auch interessieren:
|
<<: Lösung für langsame Netzwerkanforderungen im Docker-Container
>>: Detailliertes Tutorial zur Installation und Deinstallation von MySql
Inhaltsverzeichnis Vorne geschrieben Vorwort Was ...
1. Festplattenpartition: 2. fdisk-Partition Wenn ...
Docker-Compose-Bereitstellungskonfiguration Jenki...
1. Deinstallation von MySQL 5.7 1.1查看yum是否安裝過mysq...
Verwenden Sie HTML, CSS und JavaScript, um einen ...
mysql-8.0.19-winx64 von der offiziellen Website h...
Dies ist eine erweiterte Version. Die Fragen und ...
Obwohl wir keine professionellen DBAs sind, könne...
Ein cooler JavaScript-Code, um Weibo-Benutzern st...
1. Einige Tipps zu mit class in react deklarierte...
1. Unlink-Funktion Bei Hardlinks wird mit „unlink...
Frage Lassen Sie mich zunächst über das Problem s...
1. Befehlseinführung Der Befehl tac (umgekehrte R...
Inhaltsverzeichnis Vorwort: Detaillierte Einführu...
In diesem Artikelbeispiel wird der spezifische Ja...