ion-virtual-scroll
Ionic4项目中我们可以使用Ionic4列表组件ion-virtual-scroll对项目进行布局。 ion-virtual-scroll官方文档地址:https://ionicframework.com/docs/api/virtual-scrollVirtual Scroll displays a virtual, "infinite" list. An array of records is passed to the virtual scroll containing the data to create templates for. The template created for each record, referred to as a cell, can consist of items, headers, and footers. For performance reasons, not every record in the list is rendered at once; instead a small subset of records (enough to fill the viewport) are rendered and reused as the user scrolls.
The Basics
The array of records should be passed to the items
property on the ion-virtual-scroll
element.
The data given to the items
property must be an array. An item
template with the *virtualItem
property is required in the ion-virtual-scroll
.
The *virtualItem
property can be added to any element.
<ion-virtual-scroll [items]="items">
<ion-item *virtualItem="let item">
{{ item }}
ion-item>
ion-virtual-scroll>
Section Headers and Footers
Section headers and footers are optional. They can be dynamically created
from developer-defined functions. For example, a large list of contacts
usually has a divider for each letter in the alphabet. Developers provide
their own custom function to be called on each record. The logic in the
custom function should determine whether to create the section template
and what data to provide to the template. The custom function should
return null
if a template shouldn't be created.
<ion-virtual-scroll [items]="items" [headerFn]="myHeaderFn">
<ion-item-divider *virtualHeader="let header">
{{ header }}
ion-item-divider>
<ion-item *virtualItem="let item">
Item: {{ item }}
ion-item>
ion-virtual-scroll>
Below is an example of a custom function called on every record. It
gets passed the individual record, the record's index number,
and the entire array of records. In this example, after every 20
records a header will be inserted. So between the 19th and 20th records,
between the 39th and 40th, and so on, a
will
be created and the template's data will come from the function's
returned data.
myHeaderFn(record, recordIndex, records) {
if (recordIndex % 20 === 0) {
return 'Header ' + recordIndex;
}
return null;
}
Approximate Widths and Heights
If the height of items in the virtual scroll are not close to the default size of 40px, it is extremely important to provide a value for approxItemHeight height. An exact pixel-perfect size is not necessary, but without an estimate the virtual scroll will not render correctly.
The approximate width and height of each template is used to help determine how many cells should be created, and to help calculate the height of the scrollable area. Note that the actual rendered size of each cell comes from the app's CSS, whereas this approximation is only used to help calculate initial dimensions.
It's also important to know that Ionic's default item sizes have slightly different heights between platforms, which is perfectly fine.
Images Within Virtual Scroll
HTTP requests, image decoding, and image rendering can cause jank while
scrolling. In order to better control images, Ionic provides
to manage HTTP requests and image rendering. While scrolling through items
quickly,
knows when and when not to make requests, when and
when not to render images, and only loads the images that are viewable
after scrolling. ion-img
.
It's also important for app developers to ensure image sizes are locked in, and after images have fully loaded they do not change size and affect any other element sizes. Simply put, to ensure rendering bugs are not introduced, it's vital that elements within a virtual item does not dynamically change.
For virtual scrolling, the natural effects of the are not desirable
features. We recommend using the
component over the native
element because when an
element is added to the DOM, it
immediately makes a HTTP request for the image file. Additionally,
renders whenever it wants which could be while the user is scrolling. However,
is governed by the containing ion-content
and does not render
images while scrolling quickly.
<ion-virtual-scroll [items]="items">
<ion-item *virtualItem="let item">
<ion-avatar item-start>
<ion-img [src]="item.avatarUrl">ion-img>
ion-avatar>
{{ item.firstName }} {{ item.lastName }}
ion-item>
ion-virtual-scroll>
Custom Components
If a custom component is going to be used within Virtual Scroll, it's best
to wrap it with a good old When deploying to iOS with Cordova, it's highly recommended to use the
WKWebView plugin
in order to take advantage of iOS's higher performing webview. Additionally,
WKWebView is superior at scrolling efficiently in comparison to the older
UIWebView. In order for virtual scroll to efficiently size and locate every item, it's
very important every element within each virtual item does not dynamically
change its dimensions or location. The best way to ensure size and location
does not change, it's recommended each virtual item has locked in its size
via CSS. When including images within Virtual Scroll, be sure to use
As mentioned above, all elements should lock in their dimensions. However,
virtual scroll isn't aware of the dimensions until after they have been
rendered. For the initial render, virtual scroll still needs to set
how many items should be built. With "approx" property inputs, such as
It is possible for the identities of elements in the iterator to change
while the data does not. This can happen, for example, if the iterator
produced from an RPC to the server, and that RPC is re-run. Even if the
"data" hasn't changed, the second response will produce objects with
different identities, and Ionic will tear down the entire DOM and rebuild
it. This is an expensive operation and should be avoided if possible. Each virtual item must stay extremely efficient, but one way to really
kill its performance is to perform any DOM operations within section header
and footer functions. These functions are called for every record in the
dataset, so please make sure they're performant. The approximate width of each footer template's cell.
This dimension is used to help determine how many cells should
be created when initialized, and to help calculate the height of
the scrollable area. This height value can only use The approximate height of each header template's cell.
This dimension is used to help determine how many cells should
be created when initialized, and to help calculate the height of
the scrollable area. This height value can only use It is important to provide this
if virtual item height will be significantly larger than the default
The approximate height of each virtual item template's cell.
This dimension is used to help determine how many cells should
be created when initialized, and to help calculate the height of
the scrollable area. This height value can only use Section footers and the data used within its given
template can be dynamically created by passing a function to Section headers and the data used within its given
template can be dynamically created by passing a function to An optional function that maps each item within their height.
When this function is provides, heavy optimizations and fast path can be taked by
This function allows to skip all DOM reads, which can be Doing so leads
to massive performance The data that builds the templates within the virtual scroll.
It's important to note that when this data has changed, then the
entire virtual scroll is reset, which is an expensive operation and
should be avoided if possible. NOTE: only Vanilla JS API. NOTE: only JSX API for stencil. Provide a render function for the footer to be rendered. Returns a JSX virtual-dom. NOTE: only JSX API for stencil. Provide a render function for the header to be rendered. Returns a JSX virtual-dom. NOTE: only JSX API for stencil. Provide a render function for the items to be rendered. Returns a JSX virtual-dom. Marks the tail of the items array as dirty, so they can be re-rendered.
It's equivalent to calling Marks a subset of the items as dirty so they can be re-rendered.
Items should be marked as dirty any time the content or their style changes. The subset of items to be updated are specified by an offset and a length.
If a length is not provided it will check all of the items beginning at
the offset. Returns the position of the virtual item at the given index.<ion-virtual-scroll [items]="items">
<div *virtualItem="let item">
<my-custom-item [item]="item">
{{ item }}
my-custom-item>
div>
ion-virtual-scroll>
Virtual Scroll Performance Tips
iOS Cordova WKWebView
Lock in element dimensions and locations
Use
ion-img
for images
ion-img
HTML element.
With
ion-img
, images are lazy loaded so only the viewable ones are
rendered, and HTTP requests are efficiently controlled while scrolling.
Set Approximate Widths and Heights
approxItemHeight
, we're able to give virtual scroll an approximate size,
therefore allowing virtual scroll to decide how many items should be
created.
Changing dataset should use
virtualTrackBy
Efficient headers and footer functions
ion-virtual-scroll 用法(Usage)
<ion-content>
<ion-virtual-scroll [items]="items" approxItemHeight="320px">
<ion-card *virtualItem="let item; let itemBounds = bounds;">
<div>
<img [src]="item.imgSrc" [height]="item.imgHeight" [alt]="item.name">
div>
<ion-card-header>
<ion-card-title>{{ item.name }}ion-card-title>
ion-card-header>
<ion-card-content>{{ item.content }}ion-card-content>
ion-card>
ion-virtual-scroll>
ion-content>
export class VirtualScrollPageComponent {
items: any[] = [];
constructor() {
for (let i = 0; i < 1000; i++) {
this.items.push({
name: i + ' - ' + images[rotateImg],
imgSrc: getImgSrc(),
avatarSrc: getImgSrc(),
imgHeight: Math.floor(Math.random() * 50 + 150),
content: lorem.substring(0, Math.random() * (lorem.length - 100) + 100)
});
rotateImg++;
if (rotateImg === images.length) {
rotateImg = 0;
}
}
}
}
const lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, seddo eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
const images = [
'bandit',
'batmobile',
'blues-brothers',
'bueller',
'delorean',
'eleanor',
'general-lee',
'ghostbusters',
'knight-rider',
'mirth-mobile'
];
function getImgSrc() {
const src = 'https://dummyimage.com/600x400/${Math.round( Math.random() * 99999)}/fff.png';
rotateImg++;
if (rotateImg === images.length) {
rotateImg = 0;
}
return src;
}
let rotateImg = 0;
import React from 'react';
import { IonContent, IonCard, IonCardHeader, IonCardTitle, IonVirtualScroll } from '@ionic/react';
let rotateImg = 0;
const lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, seddo eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
const images = [
'bandit',
'batmobile',
'blues-brothers',
'bueller',
'delorean',
'eleanor',
'general-lee',
'ghostbusters',
'knight-rider',
'mirth-mobile'
];
function getImgSrc() {
const src = 'https://dummyimage.com/600x400/${Math.round( Math.random() * 99999)}/fff.png';
rotateImg++;
if (rotateImg === images.length) {
rotateImg = 0;
}
return src;
}
const items: any[] = [];
for (let i = 0; i < 1000; i++) {
items.push({
name: i + ' - ' + images[rotateImg],
imgSrc: getImgSrc(),
avatarSrc: getImgSrc(),
imgHeight: Math.floor(Math.random() * 50 + 150),
content: lorem.substring(0, Math.random() * (lorem.length - 100) + 100)
});
rotateImg++;
if (rotateImg === images.length) {
rotateImg = 0;
}
}
const Example: React.SFC<{}> = () => (
<IonContent>
<IonVirtualScroll items="items" approxItemHeight="320px">
<IonCard virtualItem="let item; let itemBounds = bounds;">
<div>
<img src="item.imgSrc" height="item.imgHeight" alt="item.name" />
div>
<IonCardHeader>
<IonCardTitle>{{ name }}IonCardTitle>
IonCardHeader>
<IonCardContent>{{ content }}IonCardContent>
IonCard>
IonVirtualScroll>
IonContent>
);
export default Example;
ion-virtual-scroll 属性(Properties)
approxFooterHeight
Description px
units.
Note that the actual rendered size of each cell comes from the
app's CSS, whereas this approximation is used to help calculate
initial dimensions before the item has been rendered.Attribute approx-footer-height
Type number
Default 30
approxHeaderHeight
Description px
units.
Note that the actual rendered size of each cell comes from the
app's CSS, whereas this approximation is used to help calculate
initial dimensions before the item has been rendered.Attribute approx-header-height
Type number
Default 30
approxItemHeight
Description px
units.
Note that the actual rendered size of each cell comes from the
app's CSS, whereas this approximation is used to help calculate
initial dimensions before the item has been rendered.Attribute approx-item-height
Type number
Default 45
footerFn
Description footerFn
.
The logic within the footer function can decide if the footer template
should be used, and what data to give to the footer template. The function
must return null
if a footer cell shouldn't be created.Type ((item: any, index: number, items: any[]) => string | null | undefined) | undefined
headerFn
Description headerFn
.
For example, a large list of contacts usually has dividers between each
letter in the alphabet. App's can provide their own custom headerFn
which is called with each record within the dataset. The logic within
the header function can decide if the header template should be used,
and what data to give to the header template. The function must return
null
if a header cell shouldn't be created.Type ((item: any, index: number, items: any[]) => string | null | undefined) | undefined
itemHeight
Description ion-virtual-scroll
leading to massive performance improvements.Type ((item: any, index: number) => number) | undefined
items
Description Type any[] | undefined
nodeRender
Description Type ((el: HTMLElement | null, cell: Cell, domIndex: number) => HTMLElement) | undefined
renderFooter
Description Type ((item: any, index: number) => any) | undefined
renderHeader
Description Type ((item: any, index: number) => any) | undefined
renderItem
Description Type ((item: any, index: number) => any) | undefined
ion-virtual-scroll 内置方法(Methods)
checkEnd
Description checkRange(length)
where length
is the
total length of the items.Signature checkEnd() => void
checkRange
Description Signature checkRange(offset: number, length?: number) => void
positionForItem
Description Signature positionForItem(index: number) => Promise