ion-virtual-scroll
Virtual Scrollは、Virtualの "infinite" リストを表示します。 レコードの配列が、テンプレートを作成するデータを含むVirtual Scrollに渡されます。 各レコードに対して作成されるセルと呼ばれるテンプレートは、 アイテム、ヘッダー、およびフッターで構成できます。 パフォーマンス上の理由から、リスト内のすべてのレコードが一度にレンダリングされるわけではありません。 その代わりに、レコードの小さなサブセット(ビューポートをいっぱいにするのに十分な数)がレンダリングされ、ユーザーのスクロールに再利用されます。
Approximate Widths and Heights
仮想スクロールのアイテムの高さがデフォルトサイズの 40px
に近くない場合は、
approxItemHeight
プロパティの値を指定することが非常に重要です。
ピクセル単位の正確なサイズは必要ありませんが、見積もりなしでは仮想スクロールは正しくレンダリングされません。
各テンプレートのおおよその幅と高さを使用して、 作成するセルの数を決定したり、 スクロール可能領域の高さを計算したりします。 各セルの実際の描画サイズはアプリケーションのCSSから得られるのに対し、 この近似値は初期次元の計算にのみ使用されることに注意してください。
また、Ionicのデフォルトのアイテムのサイズは、 プラットフォームによって高さが若干異なりますが、これは問題ありません。
Images Within Virtual Scroll
HTTPリクエスト、イメージのデコード、およびイメージのレンダリングによって、
スクロール中にjankが発生することがあります。
Ionicはイメージをより良く制御するために、
HTTPリクエストとイメージレンダリングを管理する
<ion-img>
を提供している。
アイテムをすばやくスクロールすると、<ion-img>
はいつ要求を行わないか、
いつ画像をレンダリングしないかを認識し、スクロール後に表示可能な画像のみをロードします。
詳細については、
ion-img
.
また、アプリ開発者にとって重要なのは、画像サイズがロックされていることを確認することであり、 画像が完全にロードされた後、サイズが変更されたり、 他の要素サイズに影響を与えたりすることはないことに注意ください。 簡単に言えば、レンダリングバグが発生しないようにするには、 仮想アイテム内の要素が動的に変化しないことが重要です。
Virtual Scrollでは、<img>
のNatural Effectsは望ましくありません。
DOMに要素を追加すると、<img>
要素は直ち画像ファイルに対するHTTP要求を作成するため、
ネイティブの <img>
要素よりも <ion-img>
コンポーネントを使用することをお薦めします。
さらに、<img>
は、ユーザーがスクロールしている間であれば常にレンダリングされてしまいます。
<ion-img>
は、含有する
ion-content
によって制御され、
高速にスクロールしてもイメージをレンダリングしません。
Virtual Scroll Performance Tips
iOS Cordova WKWebView
Cordovaを使ってiOSにデプロイする場合は、 WKWebView plugin プラグインを使ってiOSの高性能なウェブビューを利用することを強くお勧めします。 さらに、WKWebViewは、以前のUIWebViewよりも スクロールの効率が優れています。
要素のサイズと位置を固定する
仮想スクロールですべてのアイテムのサイズと位置を効率的に変更するには、 各仮想アイテム内のすべての要素がそのサイズや位置を 動的に変更しないことが非常に重要です。 サイズと位置が変わらないようにする最善の方法は、 各仮想アイテムがCSSを使ってそのサイズにロックされていることです。
画像に
ion-img
を使う
仮想スクロールにイメージを含める場合は、標準のHTML要素ではなく、
必ず
ion-img
ion-img
では、画像は遅延ロードされるため、表示可能な画像のみがレンダリングされ、
HTTPリクエストはスクロール中に効率的に制御されます。
概算の幅と高さを設定する
前述のように、すべての要素の寸法がロックされます。
ただし、virtual scrollは、レンダリングされるまで寸法を認識しません。
最初のレンダリングでは、
virtual scrollで構築する項目数を設定する必要があります。
approxItemHeight
などの"approx"プロパティを使用すると、
virtual scrollにおおよそのサイズを指定できるため、
virtual scrollで作成する項目数を決定できます。
データセットの変更にはtrackByを使用する
データは変更されていなくても、イテレータ内の要素のIDは変更されます。 たとえば、RPCからサーバーに対してイテレーターが生成され、 そのRPCが再実行された場合などです。 「データ」が変更されていなくても、2回目の応答では異なるIDを持つオブジェクトが生成され、 IonicはDOM全体を分解して再構築します。 これはハイコストな操作であり、可能であれば回避する必要があります。
効率的なヘッダーおよびフッター機能
各仮想アイテムは非常に効率的でなければなりませんが、実際にそのパフォーマンスを低下させる1つの方法は、 セクション・ヘッダーおよびフッター関数内でDOM操作を実行することです。 これらの関数はデータセット内のすべてのレコードに対して呼び出されるため、 パフォーマンスコストが高いことを確認してください。
React
The Virtual Scroll component is not supported in React.
Vue
ion-virtual-scroll
is not supported in Vue. We plan on integrating with existing community-driven solutions for virtual scroll in the near future. Follow our
GitHub issue thread for the latest updates.
利用方法
<ion-content>
<ion-virtual-scroll [items]="items" approxItemHeight="320px">
<ion-card *virtualItem="let item; let itemBounds = bounds;">
<div>
<ion-img [src]="item.imgSrc" [height]="item.imgHeight" [alt]="item.name"></ion-img>
</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;
Basic
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
<ion-item-divider>
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;
}
Custom Components
If a custom component is going to be used within Virtual Scroll, it's best
to wrap it with a
<div>
to ensure the component is rendered correctly. Since
each custom component's implementation and internals can be quite different, wrapping
within a
<div>
is a safe way to make sure dimensions are measured correctly.
<ion-virtual-scroll [items]="items">
<div *virtualItem="let item">
<my-custom-item [item]="item">
{{ item }}
</my-custom-item>
</div>
</ion-virtual-scroll>
プロパティ
approxFooterHeight | |
---|---|
Description | 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
|
Attribute | approx-footer-height |
Type | number |
Default | 30 |
approxHeaderHeight | |
Description | 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
|
Attribute | approx-header-height |
Type | number |
Default | 30 |
approxItemHeight | |
Description | 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
|
Attribute | approx-item-height |
Type | number |
Default | 45 |
footerFn | |
Description | Section footers and the data used within its given
template can be dynamically created by passing a function to
|
Type | ((item: any, index: number, items: any[]) => string | null | undefined) | undefined |
footerHeight | |
Description | An optional function that maps each item footer within their height. |
Type | ((item: any, index: number) => number) | undefined |
headerFn | |
Description | Section headers and the data used within its given
template can be dynamically created by passing a function to
|
Type | ((item: any, index: number, items: any[]) => string | null | undefined) | undefined |
headerHeight | |
Description | An optional function that maps each item header within their height. |
Type | ((item: any, index: number) => number) | undefined |
itemHeight | |
Description | 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 |
Type | ((item: any, index: number) => number) | undefined |
items | |
Description | 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. |
Type | any[] | undefined |
nodeRender | |
Description | NOTE: only Vanilla JS API. |
Type | ((el: HTMLElement | null, cell: Cell, domIndex: number) => HTMLElement) | undefined |
renderFooter | |
Description | NOTE: only JSX API for stencil. Provide a render function for the footer to be rendered. Returns a JSX virtual-dom. |
Type | ((item: any, index: number) => any) | undefined |
renderHeader | |
Description | NOTE: only JSX API for stencil. Provide a render function for the header to be rendered. Returns a JSX virtual-dom. |
Type | ((item: any, index: number) => any) | undefined |
renderItem | |
Description | NOTE: only JSX API for stencil. Provide a render function for the items to be rendered. Returns a JSX virtual-dom. |
Type | ((item: any, index: number) => any) | undefined |
メソッド
checkEnd | |
---|---|
Description | This method marks the tail the items array as dirty, so they can be re-rendered. It's equivalent to calling: CopyCopied |
Signature | checkEnd() => Promise<void> |
checkRange | |
Description | This method marks a subset of 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 can are specifing by an offset and a length. |
Signature | checkRange(offset: number, len?: number) => Promise<void> |
positionForItem | |
Description | Returns the position of the virtual item at the given index. |
Signature | positionForItem(index: number) => Promise<number> |