親愛なるハブラビテス、私はあなたを歓迎します! 今日は、Web開発におけるレンダリングの問題に焦点を当てたいと思います。 もちろん、このトピックに関する多くの記事が既に書かれていますが、私には思えたように、すべての情報はかなり断片的で断片的です。 少なくとも、頭の中の全体像を収集して理解するためには、多くの情報(主に英語)を分析する必要がありました。 そのため、記事で知識を形式化し、その結果をHabrコミュニティと共有することにしました。 この情報は、初心者のWeb開発者と経験豊富なWeb開発者の両方が知識を更新して構造化するのに役立つと思います。
明らかに、レイアウト、スタイル、およびスクリプトがレンダリングに直接関係するため、この方向はレイアウト/フロントエンド開発の段階で最適化できます。 これを行うには、適切な専門家が微妙な点を知っている必要があります。
この記事は、ブラウザーの仕組みを正確に伝えることを目的としたものではなく、むしろその一般原則を理解することを目的としていることに注意してください。 さらに、ブラウザエンジンごとに作業のアルゴリズムが大きく異なるため、1つの記事ですべてのニュアンスを網羅することはできません。
ブラウザがWebページを処理するプロセス
開始するには、ドキュメントを表示するときにブラウザのシーケンスを考慮してください。
- DOM(ドキュメントオブジェクトモデル)は、サーバーから受信したHTMLドキュメントから形成されます。
- スタイルが読み込まれて認識され、CSSOM(CSSオブジェクトモデル)が形成されます。
- DOMとCSSOMに基づいて、レンダリングツリーが形成されるか、レンダリングツリーは一連のレンダリングオブジェクトです(Webkitは「レンダラー」または「レンダーオブジェクト」という用語を使用し、Geckoは「フレーム」を使用します)。 レンダーツリーはDOM構造を複製しますが、非
display:none;
要素(たとえば、 <head>
、またはスタイルdisplay:none;
要素display:none;
)はここには含まれません。 また、テキストの各行は、レンダリングツリーで個別のレンダラーとして表されます。 各レンダリングオブジェクトには、対応するDOMオブジェクト(またはテキストブロック)、およびこのオブジェクトに対して計算されたスタイルが含まれます。 簡単に言えば、レンダーツリーはDOMの視覚的表現を表します。 - レンダーツリー要素ごとに、ページ上の位置が計算されます-レイアウトが発生します。 ブラウザはフローメソッドを使用します。ほとんどの場合、1つのパスですべての要素を配置できます(パステーブルにはさらに多くの要素が必要です)。
- 最後に、これらすべてがブラウザでレンダリングされます-ペイント。
ページとスクリプトのユーザーインタラクションのプロセスで変更されるため、上記の操作の一部を再実行する必要があります。
塗り直し
ページ上のサイズと位置に影響を与えない要素のスタイルを変更する場合(たとえば、
background-color
、
border-color
、
visibility
)、ブラウザは新しいスタイルを考慮して、それを再びレンダリングします-再描画(または再スタイル)が発生します。
リフロー
変更がドキュメントのコンテンツ、構造、要素の位置に影響する場合、リフロー(または再レイアウト)が発生します。 通常、このような変更の理由は次のとおりです。
- DOMによる操作(要素の追加、削除、変更、再配置);
- コンテンツの変更、含む。 フォームフィールドのテキスト。
- CSSプロパティの計算または変更。
- スタイルシートの追加、削除。
- 属性「
class
」を使用した操作。 - ブラウザウィンドウでの操作-サイズ変更、スクロール。
- 擬似クラスのアクティブ化(
:hover
)。
ブラウザの最適化
ブラウザは、可能な場合、変更された要素内で再ペイントとリフローをローカライズします。 たとえば、絶対位置または固定位置の要素のサイズ変更は、要素自体とその子孫のみに影響し、静的に位置する要素の変更は、それに続くすべての要素のリフローを伴います。
もう1つの機能は、JavaScriptの実行中に、ブラウザーが行った変更をキャッシュし、コードブロックの完了時に1回のパスでそれらを適用することです。 たとえば、このコードの実行中に発生するリフローと再ペイントは1回のみです。
var $body = $('body'); $body.css('padding', '1px');
ただし、上で説明したように、要素のプロパティにアクセスすると強制的なリフローが発生します。 つまり、上記のコードブロックで要素プロパティへの呼び出しを追加すると、余分なリフローが発生します。
var $body = $('body'); $body.css('padding', '1px'); $body.css('padding');
その結果、1つではなく2つのリフローが発生します。 したがって、可能であれば、パフォーマンスを最適化するために、要素のプロパティへのアクセスを1か所にグループ化する必要があります(
JSBinのより詳細な例を参照 )。
しかし、実際には、強制的なリフローなしではできない状況があります。 タスクがあるとします:最初にアニメーションなしで要素に同じプロパティを適用し(“
margin-left
”を取る)(
100px
設定)、次に
50px
への遷移を通してアニメーション化する必要があり
50px
。
JSBinで
この例をすぐに見ることができますが、ここ
でも署名します。
最初に、遷移を持つクラスを作成しましょう。
.has-transition { -webkit-transition: margin-left 1s ease-out; -moz-transition: margin-left 1s ease-out; -o-transition: margin-left 1s ease-out; transition: margin-left 1s ease-out; }
次に、次のように計画を実装してみましょう。
var $targetElem = $('#targetElemId');
このソリューションは、期待どおりに機能しません。 変更はキャッシュされ、コードブロックの最後にのみ適用されます。 強制的なリフローは私たちを助けます。その結果、コードは次の形式を取り、タスクを正確に実行します。
最適化のヒント
この記事とクライアント側の最適化の問題に対処するHarbeに関する他の記事に基づいて、効果的なフロントエンドを作成するときに役立つ次のヒントを導き出すことができます。
問題のより詳細な調査については、次の記事を読むことをお勧めします。
すべての読者がこの記事から何か役に立つことを学んだことを願っています。 いずれにせよ-見てくれてありがとう!
UPD:CSSセレクターの処理効率に関する正しいコメントを寄せてくれた
SelenIT2と
piumossoに感謝します。