blog

パフォーマンスの最適化 - intersectionObserver - それを知る!

オリジン\n会社のトップアプリモデルの1つのクラッシュ率がここ数日で増加していることが判明しました。クライアントサイド開発の同僚が見てみたところ、クラッシュ率の増加のタイムラインとライン上のアクティビ...

Sep 7, 2020 · 5 min. read
シェア

オリジン

ある会社のトップアプリモデルのクラッシュ率がここ数日で増加していることが判明しました。クライアントの開発同僚が見てみると、クラッシュ率の増加のタイムラインとライン上のアクティビティのタイムラインがほぼ同じ!これは大変なことなので、彼らはすぐにどの悪魔がアプリをいじっているのか調査しました。

悪魔の発見

とはいえ、悪魔を捕まえるには、まずガスの匂いを嗅ぎましょう。Androidのデバッグツールに接続し、h5のアクティビティページを開きます。ただ、ページのメモリを開くと、もう少しデータをロードするためにスクロールするには、非常に正常であり、デバッグツールのメモリ曲線も上昇ピークを形成し、下降傾向はなく、さらにはアプリのクラッシュが表示されます。よく見ると、主な上昇メモリはGraphicモジュールにあります。問題の原因は、最初はロックされており、ロードされたイメージが多すぎるか、サイズが大きすぎるのではないでしょうか?そこで、イベントページの表示リストでアバターの表示を無効にしたところ、メモリ使用量は正常値で安定しました。さて、悪魔が現れたので、小仙がこの小悪魔をどう痴漢するか見てみましょう。

呪文の検索

魔物を集めるには、宝物を使って魔物に呪文を唱えなければなりません。この問題に対して、リストのデータロードによると、小仙はまず、アバターのロードがサムネイルリソースを使用しなかったため、メモリ占有率が上昇したと考えました。しかし、サムネイルに切り替えた後、データは低下しませんでした。イメージのサイズが問題なのではなく、おそらくイメージリソースが多すぎてアプリ内のウェブビューのメモリが増加しているようです。もちろん、ウェブビュー自体がイメージリソースの処理に困っていて、メモリの少ないトップモデルのメモリ不足を引き起こし、クラッシュさせているという問題も考えられます。しかし、どちらに問題があるにせよ。小悪魔が現れてこの抜け穴を突いたのですから、まずはこの小悪魔を捕まえて楽しむ方法を考えましょう。イメージが多すぎるので、ユーザーに見えるものだけをレンダリングして、あとはレンダリングしないようにしましょう。

魔法の武器の紹介

intersectionObserver 今回シャオシアンが使った魔法武器はAPIです。

交差点オブザーバー

MDNによると、intersectionObserverインターフェイスは、ターゲット要素とその祖先要素またはトップレベルのドキュメントビューポート(ルート要素と呼ばれます)の間の交差状態を非同期に観察する方法を提供します。人間の言葉で言えば、このインターフェースは2つの要素が交差しているかどうかを判断するために使用することができ、ルート要素はターゲット要素の祖先でなければなりません。

を使って2つの要素の交差を判定するには、ルート要素がターゲット要素の祖先でなければなりません。

  1. まず、IntersectionObserver コンストラクタを使用して、2 つの要素が交差するかどうかを確認するために使用できるオブザーバをインスタンス化します。コンストラクタは2つの引数を取り、可変性が変化したときにトリガーされるコールバック関数です。ひとつはオプションの設定オブジェクト options です。
const observer = new IntersectionObserver((entries, options) => {
 console.log(entries);
}, {
 rootMargin: '200px 0px 60px 0px', // 対象要素がルート要素の外枠から遠い場合に発動する。 この場合、対象要素がルート要素の上枠より200px小さいか、ルート要素の下枠より60px小さいか等しい場合に発動する。
 threshold: 0.1 // ルート要素の領域にターゲット要素が到達するとき、交差イベントがトリガされたどのくらいの割合、数値にすることもできる、次のような数値の配列にすることができる。[0.1, 0.4, 0.75, 1]それぞれルート要素への対象要素が10に達したことを示す%40%75%100%コールバック関数は毎回1回起動する
 root: document.getElementById('#parent') //  
});

コールバック関数は、以下のプロパティを持つ IntersectionObserverEntry オブジェクトの配列を受け取ります:

boundingClientRect: ターゲット要素の矩形領域に関する情報。

intersectionRect: ターゲット要素がルート要素と交差する矩形領域に関する情報。

rootBounds: ルート要素の矩形領域に関する情報。

intersectionRatio: ターゲット要素とルート要素の交差比率、つまり intersectionRect と rootBounds の比率。

isIntersecting: ターゲット要素がルート要素と交差しているかどうか。

time: 可視性が変更された時のタイムスタンプをミリ秒単位で、クロームの小数点以下10桁で指定します。

target: ターゲット要素の dom オブジェクト。
  1. ターゲット要素を観察します:
const target = document.getElementById('target');
observer.observe(target);

この時点で、オブザーバはターゲット要素の監視を開始し、ターゲット要素がルート要素と交差すると、前述のコールバックをトリガします。コールバックは非同期関数であり、その基本原理はこのコールバックを実行するrequestIdlecallbackメソッドに似ていることに注意する必要があります。関数は遅延します。イメージの遅延読み込みでは、スライドの速度が速い場合、イメージが遅く表示されることがあります。この場合、対応するデータを少し早く読み込むために、インスタンス化の際にオプションに渡される rootMargin のマージンを少し増やすことができます。どの値を増やすかは、特定のニーズによって異なります。

  1. 要素の観察を停止
observer.unobserve(target)

一般的に、unobserveはメモリリークを避けるためにページアンロードで必要です。

  1. オブザーバーを閉じるには
observer.disconnect();

呪文の唱え方

簡略化のため、コードの一部を省略しています container.vue

<div class="container">
 <row v-for="item in itemList" :thump-url="item.src"></row>
</div>
export default {
 data() {
 return {
 itemList: [{url: 'xxx'}, ...],
 observer: null
 }
 },
 mounted() {
 this.initObserver();
 },
 beforeDestroy(){
 this.observer.disconnect(); 
 },
 methods: {
 initObserver() {
 this.observer = new IntersectionObserver((entries, options) => {
 entries.forEach(this.isIntersectHandler);
 }, {
 rootMargin: '200px 0px 60px 0px',
 threshold: 0.1
 });
 },
 isIntersectHandler(entry){
 let target = entry.target;
 const isIntersecting = entry.isIntersecting;
 let thumpTarget = target.getElementsByClassName('thump')[0];
 if (isIntersecting) {
 const imgUrl = thumpTarget.dataset['src'];
 imgLoadHandler(thumpTarget, imgUrl); // imgLoadHandler新しいImageの内部使用、urlのリソースを事前にダウンロードし、読み込みが完了すると、そのurlがthumpTargetのsrcに割り当てられる、エラーが発生したり、まだ読み込まれていない場合は、デフォルトのアバターが使用される!
 } else {
 thumpTarget.src = ''; // 非可視領域にレイヤーをレンダリングしないように、対象の src を空にする。
 }
 }
 }
}

row.vue(省略コード)

<div class="row" ref="row">
 <img class="thump" data-src="thumpUrl">
</div>
export default {
 props: {
 thumpUrl: {
 default: '',
 type: String
 }
 },
 mounted(){
 this.$parent.observer.observer(this.$refs.row);
 },
 beforeDescroy(){
 this.$parent.observer.unobserver(this.$refs.row);
 }
}

スペル効果

ちょっとしたおまじないの後、android studioのデバッグで、アプリのメモリが増え続けることがなくなり、すべてのデータをロードした後、アプリ全体の占有メモリが平均280Mから平均220Mに下がったことがわかります。このおまじないは、結果が出始めていると言えます。

互換性

intersection-observer現在のところ、新しいブラウザは基本的に対応していますが、一部の古いブラウザは対応していません。

Read next

アルゴリズムの復習:2つの配列の交点

各要素が出力結果に現れる回数は、その要素が両方の配列に現れる回数と一致しなければなりません。 出力結果の順序は無視してもかまいません。 アイデア:2つのポインタを0にセットし、2つのポインタの要素が等しいかどうかを比較します。ポインタの要素が等しい場合、2つのポインタを一緒に後方に移動し、等しい要素を空白の配列に入れます。 <1> 2つのポインタを0にセットし、2つのポインタの要素を比較...

Sep 7, 2020 · 4 min read