フロントエンドのパフォーマンスを最適化するためのブラウザレンダリングの最適化

記事ディレクトリ

導入

今日のインターネットの急速な発展の時代において、ユーザーは Web ページの読み込み速度とパフォーマンスに対する要求がますます高くなっています。フロントエンド開発者として、より良いユーザー エクスペリエンスを提供するために、Web ページの読み込みとレンダリングのパフォーマンスの向上に注意を払い、取り組む必要があります。ブラウザのレンダリングの最適化は、この目標を達成するための鍵です。この記事では、Web ページのレンダリング プロセスを最適化し、ページの読み込み速度とパフォーマンスを向上させることを目的として、ブラウザー レンダリングの最適化に関するヒントと戦略について説明します。フロントエンド開発者として始めたばかりの場合でも、経験豊富な開発者である場合でも、この記事では、ブラウザーでの Web ページのレンダリングを高速化するための貴重なアドバイスと実践的なヒントを提供します。ブラウザーのレンダリングの最適化を通じて Web ページのパフォーマンスを向上させる方法を一緒に検討しましょう。

1. ブラウザのレンダリング処理

ブラウザのレンダリング プロセスとは、ブラウザがサーバーから取得した HTML、CSS、JavaScript などのリソースを解析、レイアウト、描画し、最終的にユーザーに表示するプロセスを指します以下は、ブラウザ レンダリングの簡単なフローチャートです。

  1. HTML の解析: ブラウザはHTMLドキュメントを上から解析し、DOM(ドキュメント オブジェクト モデル) ツリーを構築します。DOMツリーはHTML、タグ、属性、テキスト ノードなどのドキュメントの構造を表します。

  2. CSS の解析: ブラウザは外部CSSファイルを解析し、スタイル ルールをDOMツリー内の対応する要素に適用します。解析プロセスでは(オブジェクト モデル) ツリーCSSが生成されます。これはツリーに似ていますが、スタイル情報を表します。CSSOMCSSDOMCSS

  3. レンダー ツリーの構築 (Render Tree) : ブラウザはDOMツリーとCSSOMツリーをマージしてレンダー ツリーを構築します。レンダー ツリーには、ページに表示する必要がある可視要素のみが含まれます。たとえば、非表示要素 (例display:none) はレンダー ツリーには含まれません。

  4. レイアウト: ブラウザは、ページ上のレンダリング ツリー内の各要素のサイズと位置を計算し、そのボックス モデル ( Box Model) 情報を決定します。このプロセスはレイアウトまたは reflow( reflow) と呼ばれ、いくつかのCSSプロパティ (など) に基づいてwidth、height、position各要素の正確な位置を計算します。

  5. ペイント: ブラウザは、レンダー ツリーとレイアウトに関する情報に基づいて、各要素を画面に描画し始めます。描画プロセスでは、各要素が 1 つ以上のレイヤーに変換され、GPU加速技術を使用してパフォーマンスが向上します。

  6. ラスタライズ: 描画が完了すると、ブラウザはレイヤーを小さなタイルにスライスし、ビットマップに変換します。このプロセスはラスタライズと呼ばれ、グラフィックスを画面に表示できるピクセルに変換します。

  7. 合成: ブラウザはすべてのタイルを正しい順序で合成し、最終的なレンダリング結果を出力します。このプロセスは合成と呼ばれ、ビットマップを画面に描画し、ユーザーの操作に基づいてレンダリングを常に更新します。

プロセス全体をフローチャートの形式で示すと、次のようになります。

开始 -> 解析HTML -> 样式计算 -> 布局计算 -> 绘制 -> 合成 -> 结束

要約すると、ブラウザーのレンダリング プロセスには、解析HTML、解析CSS、レンダリング ツリーの構築、レイアウト、描画、ラスター化、合成などのステップが含まれます。これらの手順により、Web ページが表示可能な Web コンテンツに変換さHTML、CSSJavaScript、ユーザーが通常どおり Web を閲覧できるようになります。

在这里补充一点
ブラウザ レンダリング プロセスの最後のステップでは、Composition は主に次の作業を行います

  1. レンダリング ツリーを合成します。合成エンジンは、レンダリング ツリーの構造とスタイル属性情報に従って、ページ要素を 1 つ以上のレイヤーに結合します。これらのレイヤーは、必要に応じて個別に描画および合成できます。

  2. レイヤー描画: 合成段階では、合成エンジンは各レイヤーをビットマップに個別に描画します。通常、このプロセスは GPU で実行され、ハードウェア アクセラレーション機能を利用して描画を高速化します。

  3. レイヤーの合成: 合成エンジンは、描画されたレイヤーを正しい順序で合成します。合成プロセスでは、さまざまなレイヤーのビットマップに対して透明度の混合やマスキングなどの操作が実行され、最終的に表示されるグローバル ビットマップが生成されます。

  4. ディスプレイとディスプレイ リストの更新: 最後に、合成エンジンは、生成されたグローバル ビットマップをディスプレイ パイプラインを通じてディスプレイに送信して表示します。同時に、合成エンジンはページの表示リストを更新して、最新のレンダリングを反映します。

つまり、合成 ( Composition) はブラウザ レンダリング プロセスの最後のステップであり、これには、レンダリング ツリーの合成、レイヤの描画と合成、最終的なビットマップの生成、表示リストの更新などのステップが含まれ、最後にレンダリング結果が画面上に表示されます。ユーザーが画面上にいます。これらの取り組みは、レンダリング パフォーマンスとユーザー エクスペリエンスの向上に役立ちます。

2.逆流

1. リフローとは

リフローとは、ブラウザがページ レイアウトの一部または全体を再レンダリングすることを指します。ページの構造、スタイル、レイアウトのプロパティが変更されると、ブラウザはリフローをトリガーします。

リフローは、ブラウザーのレンダリングのレイアウト段階で発生します。レンダリング プロセスには、レイアウトとペイントという 2 つの主なステップがあります。

レイアウト段階とは、ブラウザが要素のボックス モデルに従ってページ上の各要素の位置とサイズを計算し、要素間の関係を確立し、レイアウト ツリー (レンダー ツリー) を生成することを意味します。ブラウザは、レイアウト段階でページ構造またはスタイルが変更されたことを検出すると、リフローをトリガーします。

リフロー プロセス中、ブラウザはレイアウト ツリーを走査し、各要素の位置とサイズを計算し、対応するレンダリング情報を更新してから、レイアウト ツリーと描画ツリーを再生成し、最終的に描画します。

リフローが発生すると、ブラウザはページ レイアウトを再計算し、関連するレンダリング情報を更新する必要があるため、リフローは比較的パフォーマンスを重視する操作であることに注意してください。リフローが頻繁に発生するとページのパフォーマンスが低下するため、ページのパフォーマンスを向上させるには、開発中にリフローの発生を最小限に抑える必要があります。

2. どのような操作が逆流を引き起こす可能性があるか

ブラウザーのレンダリングのリフロー (リフロー) とは、ページ レイアウトと幾何学的プロパティが変更されると、ブラウザーが要素のレイアウトを再計算して再描画することを意味し、これにより多くのパフォーマンスが消費されますブラウザーのレンダリングがリフローする一般的な状況をいくつか示します。

  1. ウィンドウ サイズの変更: ウィンドウ サイズが変更されると、ページ レイアウトが変更され、要素のレイアウトを再計算して再描画する必要があります。
window.addEventListener('resize', function () {
    
    
  // 窗口大小改变的操作
});
  1. 要素のサイズを変更する: 要素の幅、高さ、内側と外側の余白、その他の属性を変更すると、ブラウザーが要素のレイアウトを再計算します。
element.style.width = "200px";
element.style.height = "100px";
  1. 要素の位置を変更する: 要素の位置属性 (上、左、右、下) を変更すると、周囲の要素のレイアウトが変更されます。
element.style.position = "absolute";
element.style.left = "100px";
element.style.top = "50px";
  1. 要素の追加または削除: 要素を追加または削除すると、ページ全体のレイアウトが変更され、すべての要素でレイアウトを再計算する必要があります。
document.body.appendChild(newElement);
document.body.removeChild(elementToRemove);
  1. 要素のコンテンツの変更: 要素のテキスト コンテンツまたは HTML 構造が変更されると、要素とその周囲の要素のレイアウトが変更されます。
element.textContent = "New Content";
element.innerHTML = "<p>New HTML</p>";
  1. 一部のプロパティを計算する: 計算されたプロパティ (offsetWidth、offsetHeight、clientWidth、clientHeight など) を取得するとき、ブラウザは要素のレイアウトを再計算する必要があります。
var width = element.offsetWidth;
var height = element.offsetHeight;

3. 逆流を最適化する方法

リフローは、ブラウザが Web ページのレイアウトを再計算して再描画するプロセスであり、非常にパフォーマンスを消費する操作です。リフローを最適化すると、Web ページのレンダリング パフォーマンスが向上します。一般的に使用されるいくつかの方法を次に示します。

1. アニメーション効果を実現するには、上、左、幅などのプロパティの代わりに、変換と不透明度を使用します。変換と不透明度ではリフローが発生しないためです。

ボタンがあり、それをクリックしたときにアニメーション効果を表示したいとします。 、 、およびその他の属性を使用する代わりにtransformと を使用してそれを実現できます。実際のケースの例を次に示します。opacitytopleftwidth

HTML 構造:

<button id="myButton">点击我</button>

CSS スタイル:

#myButton {
    
    
  position: relative;
  padding: 10px 20px;
  background-color: blue;
  color: white;
  transform: scale(1);
  opacity: 1;
  transition: transform 0.3s, opacity 0.3s;
}

#myButton.clicked {
    
    
  transform: scale(0.8);
  opacity: 0;
}

JavaScript コード:

var button = document.getElementById("myButton");
button.addEventListener("click", function() {
    
    
  button.classList.add("clicked");
});

上の例では、ボタン要素にはデフォルトでイニシャルtransformopacity属性があります。ボタンをクリックすると、clickedクラスを追加することによってアニメーション効果がトリガーされます。このクラスは、ボタンtransformopacityプロパティを変更して、スケーリングとフェード効果を実現します。

transformプロパティopacityの変更によってリフローが発生しないため、このアニメーション方法では、特に複雑なアニメーション効果を扱う場合に、よりスムーズなユーザー エクスペリエンスを提供できます

2. 要素のレイアウト プロパティを変更するのではなく、絶対配置 (position:Absolute) を使用して要素を移動してみてください。絶対配置はドキュメント フローから切り離されるため、他の要素が再レイアウトされることはありません。

絶対配置 ( position: absolute) を使用して要素を通常のドキュメント フローから移動し、リフローを回避して正確に配置します。

実際のケースの例を次に示します。

HTML コードは次のとおりです。

<div class="container">
  <div class="box"></div>
</div>

CSS スタイルは次のとおりです。

.container {
    
    
  position: relative;
  width: 300px;
  height: 200px;
  border: 1px solid #000;
}

.box {
    
    
  position: absolute;
  top: 50px;
  left: 50px;
  width: 100px;
  height: 100px;
  background-color: red;
}

この場合、.container相対的に配置された親コンテナ.boxと絶対的に配置された子要素です。の値と の.boxを設定することで、 の指定位置に正確に配置することができますtopleft.container

これにより、.box要素の位置を変更しても、.container親コンテナのサイズや他の要素のレイアウト プロパティが変更されず、リフローの発生を回避できます。

要約:

  • 親コンテナ ( .container)positionの を設定してrelative、相対位置決めの基準点を作成します。
  • 子要素 ( .box)positionabsolute位置を通常のドキュメント フローから外すように設定します。
  • topおよび属性を使用してleft、子要素を正確に配置します。

このように、絶対配置を使用すると、レイアウト プロパティを変更せずに要素を移動できるため、リフローが回避されます。

3. テーブルの各セルの内容を変更するとリフローが発生するため、テーブル レイアウトの使用は避けてください。CSS の「display: table」と「display: table-cell」を使用すると、同様の効果を実現できます。

レイアウトの一般的な使用法tableは、各メニュー項目の幅がコンテンツに基づいて動的に調整される、水平方向の中央に配置されたナビゲーション メニューを作成することです。

tableレイアウトを使用するコードは次のようになります。

<table class="nav">
  <tr>
    <td>菜单项1</td>
    <td>菜单项2</td>
    <td>菜单项3</td>
    <td>菜单项4</td>
  </tr>
</table>

ただし、このレイアウト方法ではコンテンツに応じて各セルの幅を動的に調整する必要があるため、リフローが発生します。

レイアウトの使用を回避する1 つのtable方法は、CSSdisplay: tabledisplay: table-cellプロパティを使用して、リフローを回避しながら同様の効果を実現することです。コードは次のようになります。

<div class="nav">
  <div class="nav-item">菜单项1</div>
  <div class="nav-item">菜单项2</div>
  <div class="nav-item">菜单项3</div>
  <div class="nav-item">菜单项4</div>
</div>
.nav {
    
    
  display: table;
  width: 100%;
}

.nav-item {
    
    
  display: table-cell;
  text-align: center;
}

.nav-item:hover {
    
    
  background-color: gray;
}

この例では、テーブル レイアウトの効果をシミュレートするために<div>設定されたナビゲーション メニューのコンテナとして要素が使用され、テーブル内のセルをシミュレートするために各メニュー項目が使用されます。display: tabledisplay: table-cell

この利点は、リフローを発生させることなく、各メニュー項目の幅がコンテンツに適応することです。さらに、CSS を使用して、マウスを置いたときに背景色を変更するなど、メニュー項目にスタイルを追加できます。

display: table結論として、と属性を使用してテーブル レイアウトをシミュレートすることでdisplay: table-cell、リフローを回避し、レイアウトとスタイルをより柔軟に制御できます。

4. ループ内で DOM 要素のスタイルを複数回変更することを避けるために、まず変更するスタイルを変数に保存し、その後 DOM 要素のスタイルを一度に更新します。

例として、リスト内のすべての要素のスタイルを変更するとします。<ul> 要素があり、その中のすべての <li> 要素の背景色を赤に設定するとします。DOM 要素のスタイルがループ内で複数回変更されると、複数回のリフローが発生し、パフォーマンスに影響します。

以下はリフローを回避する方法です。まず、変更する必要があるスタイルをオブジェクトに保存してから、DOM 要素のスタイルを一度に更新します。

// 获取<ul>元素
const list = document.querySelector('ul');

// 创建一个对象,存储需要修改的样式
const styles = {
    
    
  backgroundColor: 'red'
};

// 循环遍历所有的<li>元素
const items = list.getElementsByTagName('li');
for(let i = 0; i < items.length; i++) {
    
    
  // 将需要修改的样式应用到每个<li>元素
  Object.assign(items[i].style, styles);
}

上記のコードからわかるように、まず querySelector を使用して操作対象の <ul> 要素を取得し、変更する必要があるスタイルを格納するスタイル オブジェクトを作成します。ここでは背景色のみを赤に設定しています。 。次に、getElementsByTagName を使用してすべての <li> 要素を取得し、ループ トラバーサルを通じて保存されたスタイルを各 <li> 要素に適用し、Object.assign メソッド style を通じて、styles オブジェクトの style 属性を li 要素に一度に割り当てます。属性。

このようにして、すべての <li> 要素の背景色を赤に設定する効果が得られ、ループ内で DOM 要素のスタイルを複数回変更することが回避され、リフローの回数が減り、パフォーマンスが向上します。

5. レイアウト プロパティ ( など) を頻繁に読み取ることを避けるためにoffsetWidthoffsetHeightこれらのプロパティの値をキャッシュして使用できます。

実際の開発では、レイアウト属性( など)を頻繁に読み込むと、offsetWidthブラウザoffsetHeightが頻繁にリフロー(リフロー)を起こし、ページのパフォーマンスに影響を与えます。この状況を回避するために、これらのプロパティの値をキャッシュして使用できます。

実際の使用例は、スクロール中に要素の高さを取得することです。シナリオによっては、処理のために要素の高さを取得する必要がありますが、各スクロール イベントで高さを取得するために直接呼び出しを行うと、offsetHeight頻繁にリフローが発生します。

この状況を回避するには、スクロール イベントごとに要素の高さを読み取るのではなく、ページが読み込まれるか要素がレンダリングされた後に要素の高さをキャッシュします。

サンプルコードは次のとおりです。

// 获取元素
const element = document.getElementById('scrollElement');

// 缓存元素的高度
let cachedHeight = element.offsetHeight;

// 监听滚动事件
window.addEventListener('scroll', () => {
    
    
  // 使用缓存的高度进行处理
  // ...
});

cachedHeight上記のコードでは、ページが読み込まれるか要素がレンダリングされた後に、要素の高さを変数にキャッシュします。次に、スクロール イベントが発生すると、offsetHeight毎回 get height を呼び出すのではなく、キャッシュされた高さを処理に直接使用します。

そうすることで、レイアウト プロパティの頻繁な読み取りを回避し、リフローの回数を減らし、ページのパフォーマンスを向上させることができます。要素の高さが動的に変化する場合など、いくつかの特殊なケースでは、キャッシュの高さを適時に更新する必要があることに注意してください。

6. React や Vue.js などのフレームワークなどの仮想 DOM テクノロジーを使用して、コンポーネントが更新されたときに DOM ツリー全体ではなく、変更された部分のみを更新します。

React や Vue.js などの仮想 DOM テクノロジーを使用するフレームワークは、仮想 DOM ツリーへの変更を比較することで実際の DOM ツリーの一部を更新することでリフローを回避できます。

実際のケースを説明するために、複数のユーザー情報コンポーネントを含むユーザー リストがあると仮定します。ユーザーがキーワードを入力すると、一致するユーザー情報がリストに表示される簡単なユーザー検索機能を実装したいと考えています。

まず、React で、ユーザーのリストをレンダリングする UserList コンポーネントを定義できます。

class UserList extends React.Component {
    
    
  render() {
    
    
    return (
      <div>
        {
    
    this.props.users.map(user => (
          <UserItem key={
    
    user.id} user={
    
    user} />
        ))}
      </div>
    );
  }
}

Vue.js でも同様に:

Vue.component('user-list', {
    
    
  props: ['users'],
  template: `
    <div>
      <user-item v-for="user in users" :key="user.id" :user="user" />
    </div>
  `,
});

UserItem コンポーネントは、単一のユーザーに関する情報を表示するために使用されるコンポーネントです。このコンポーネントに入力ボックスを追加して、検索キーワードを入力できます。

class UserItem extends React.Component {
    
    
  constructor(props) {
    
    
    super(props);
    this.state = {
    
    
      search: '',
    };
  }

  handleSearchChange = (event) => {
    
    
    this.setState({
    
     search: event.target.value });
  }

  render() {
    
    
    const {
    
     user } = this.props;
    const {
    
     search } = this.state;

    return (
      <div>
        <h3>{
    
    user.name}</h3>
        <p>{
    
    user.email}</p>
        <input type="text" value={
    
    search} onChange={
    
    this.handleSearchChange} />
      </div>
    );
  }
}

Vue.js でも同様のロジックを実装できます。

Vue.component('user-item', {
    
    
  props: ['user'],
  data() {
    
    
    return {
    
    
      search: '',
    };
  },
  methods: {
    
    
    handleSearchChange(event) {
    
    
      this.search = event.target.value;
    },
  },
  template: `
    <div>
      <h3>{
     
     { user.name }}</h3>
      <p>{
     
     { user.email }}</p>
      <input type="text" v-model="search" />
    </div>
  `,
});

これで、ユーザーが入力ボックスにキーワードを入力するたびに、対応するコンポーネントの検索状態が更新されます。React や Vue.js は仮想 DOM 技術を使用しているため、前後の 2 つの仮想 DOM ツリーの差分を比較し、変更された部分のみを実際の DOM ツリーに更新します。

ここでは、検索キーワードに応じて UserItem コンポーネントにユーザー情報を動的に表示するだけです。たとえば、React では次のようになります。

class UserItem extends React.Component {
    
    
  // ...

  render() {
    
    
    const {
    
     user } = this.props;
    const {
    
     search } = this.state;

    if (search && !user.name.toLowerCase().includes(search.toLowerCase())) {
    
    
      // 不匹配搜索关键字时,返回null,不渲染该用户信息
      return null;
    }

    return (
      <div>
        <h3>{
    
    user.name}</h3>
        <p>{
    
    user.email}</p>
        <input type="text" value={
    
    search} onChange={
    
    this.handleSearchChange} />
      </div>
    );
  }
}

Vue.js では、v-if ディレクティブを使用して同様のロジックを実現できます。

Vue.component('user-item', {
    
    
  // ...

  template: `
    <div v-if="!search || user.name.toLowerCase().includes(search.toLowerCase())">
      <h3>{
     
     { user.name }}</h3>
      <p>{
     
     { user.email }}</p>
      <input type="text" v-model="search" />
    </div>
  `,
});

このようにして、ユーザーがキーワード検索を入力するたびに、DOM ツリー全体ではなく、一致したユーザー情報の一部のみが更新されます。これにより、不必要なリフローが回避され、パフォーマンスが向上します。

7. CSS の will-change プロパティを使用して、要素が変更されようとしていることを事前にブラウザーに通知します。これにより、ブラウザーは要素に対していくつかの最適化を実行できます。

要素の属性will-changeを属性に設定すると、要素が変更されようとしていること、およびブラウザがリフローを回避するために要素に対して何らかの最適化を実行できることがブラウザに通知されます。これによりパフォーマンスが向上し、不必要なレイアウト計算が回避されます。

属性を使用する実際の例を次に示しますwill-change

HTML:

<div class="box">这是一个 Box</div>

CSS:

.box {
    
    
  width: 100px;
  height: 100px;
  background-color: red;
  transition: transform 1s ease-in-out;
}

.box:hover {
    
    
  transform: scale(1.2);
  will-change: transform;
}

この場合、マウスをボックス要素の上に置くと、要素が拡大縮小されます。.box:hoverセレクターにwill-change属性を設定して、それが要素が変更されようとしている属性transformであることをブラウザーに伝えますtransform

このようにして、ブラウザーは、不必要なリフローを回避するためにボックス要素を拡大縮小する前にいくつかの最適化を行う必要があることを認識します。たとえば、ブラウザはレイアウトの再計算を防ぐために、変更transform前にレンダー レイヤを準備できます。

プロパティを使用するとwill-change、特に変更が必要な大きな要素や複雑なアニメーションを扱う場合に、パフォーマンスが向上します。ただし、will-change属性を誤って使用するとパフォーマンスの問題が発生する可能性があるため、必要な要素と属性にのみ適用するようにしてください。

8. DOM ツリーの構造を頻繁に変更することを避けるために、バッチ挿入および削除操作にドキュメント フラグメント (DocumentFragment) を使用したり、HTML コードを生成するために文字列スプライシングを使用したりするなど、いくつかの最適化戦略を採用できます。

documentFragment
実際のケースでは、ユーザーがタスクを追加、削除、完了できる To-Do リストがあるとします。ユーザーがこれらの操作を実行するたびに、DOM ツリーの構造が変更されるため、リフローや再描画が頻繁に発生し、パフォーマンスとユーザー エクスペリエンスが低下する可能性があります。

DOM ツリーの構造を頻繁に変更することを避けるために、ドキュメントのフラグメントを使用して一括挿入および削除操作を行うことができます。ドキュメント フラグメントは、一連のノードを含めることができる軽量の DOM ノード コンテナです。ただし、ドキュメント内に実際のノードは作成されません。

以下は、ドキュメントフラグメントを使用したバッチ挿入の実際のケースです。

// 创建一个文档片段
var fragment = document.createDocumentFragment();

// 获取待办事项列表的容器元素
var container = document.getElementById('todoList');

// 待添加的任务数据
var tasks = ['任务1', '任务2', '任务3'];

// 循环遍历任务列表
tasks.forEach(function(task) {
    
    
  // 创建新的任务元素
  var item = document.createElement('li');
  item.textContent = task;
  
  // 将任务元素添加到文档片段中
  fragment.appendChild(item);
});

// 将文档片段一次性插入到容器中
container.appendChild(fragment);

この場合、最初にドキュメント フラグメントを作成しfragment、次にタスク リストをループし、新しいタスク要素を作成してドキュメント フラグメントに追加します。最後に、ドキュメントのフラグメントを一度にコンテナに挿入することで、DOM ツリー構造の頻繁な変更が回避され、リフローの回数が減ります。

同様に、ドキュメントのフラグメントを一括削除操作に使用できます。以下は、文書フラグメントを使用した一括削除の実際のケースです。

// 创建一个文档片段
var fragment = document.createDocumentFragment();

// 获取待删除的任务元素列表
var deleteList = document.querySelectorAll('.delete');

// 遍历待删除的任务元素列表
Array.prototype.forEach.call(deleteList, function(item) {
    
    
  // 将任务元素从文档中移除并添加到文档片段中
  fragment.appendChild(item);
});

// 从容器中一次性删除文档片段中的任务元素
container.removeChild(fragment);

この場合、最初にドキュメント フラグメント を作成しfragment、次に querySelectorAll を通じて削除するタスク要素のリストを取得します。次に、リストを反復処理して、ドキュメントから各タスク要素を削除し、ドキュメントのフラグメントに追加します。最後に、ドキュメントのフラグメントをコンテナから一度に削除することで、DOM ツリー構造の頻繁な変更が回避され、リフローの回数が削減されます。

バッチ挿入および削除操作にドキュメント フラグメントを使用することで、DOM 操作を最適化し、リフローの回数を減らし、パフォーマンスとユーザー エクスペリエンスを向上させることができます。

文字列の連結:
完了するタスクのリストがあるとします。ユーザーは入力ボックスから新しいタスクを追加し、ボタンをクリックして保存できます。ユーザーが新しいタスクを追加するたびに、その新しいタスクを DOM ツリー内のタスクのリストに動的に追加する必要があります。DOM ツリー構造を頻繁に変更することによって発生するパフォーマンスの問題を回避するために、文字列の連結を使用して HTML コードを生成し、生成された HTML を DOM ツリーに一度に挿入できます。

<!DOCTYPE html>
<html>
<head>
  <title>待完成任务列表</title>
</head>
<body>
  <div id="taskList"></div>
  <input type="text" id="taskInput">
  <button id="addButton">Add Task</button>

  <script>
    var taskList = document.getElementById('taskList');
    var taskInput = document.getElementById('taskInput');
    var addButton = document.getElementById('addButton');
    var tasks = [];

    // 监听按钮点击事件
    addButton.addEventListener('click', function() {
      
      
      var taskName = taskInput.value;
      if (taskName) {
      
      
        tasks.push(taskName);

        // 使用字符串拼接生成HTML代码
        var html = '';
        for (var i = 0; i < tasks.length; i++) {
      
      
          html += '<div>' + tasks[i] + '</div>';
        }

        // 批量插入任务列表
        var fragment = document.createElement('div');
        fragment.innerHTML = html;
        taskList.appendChild(fragment);

        // 清空输入框
        taskInput.value = '';
      }
    });
  </script>
</body>
</html>

上記の場合、ユーザーが「追加」ボタンをクリックすると、文字列連結を利用してHTMLコードを生成し、生成したHTMLコードをDOMツリーのタスクリストに一括挿入します。1 回の挿入により、DOM ツリー構造の頻繁な変更を回避できるため、リフローの回数が減り、ページのパフォーマンスが向上します。

9. デバウンスまたはスロットルを使用して、頻繁なリフロー呼び出しの数を減らします。たとえば、lodash ライブラリのデバウンス メソッドとスロットル メソッドを使用します。

アンチシェイク ( debounce) とスロットリング ( throttle) は、フロントエンド開発で一般的に使用される最適化手法であり、頻繁にトリガーされる特定のイベントの実行時間を制限し、Web ページのパフォーマンスとユーザー エクスペリエンスを向上させるために使用されます。

アンチシェイク: イベントがトリガーされた後、指定された時間内にイベントが再度トリガーされると、タイミングが再開されます。指定された時間内にイベントが再度トリガーされなかった場合にのみ、イベント ハンドラーが実行されます。通常、入力ボックスでのリアルタイム検索など、ユーザーによって頻繁にトリガーされるイベントを最適化するために使用されます。アンチシェイクを使用すると、ユーザーが入力を続けているときに頻繁にリクエストが送信されるのを防ぐことができますが、ユーザーが入力を止めるのを待ってからリクエストを行うため、リクエストの数とサーバーの負荷が軽減されます。

スロットル: イベントがトリガーされた後、指定された時間内に、イベントが何回トリガーされても、イベント処理関数は 1 回だけ実行されます。通常は、ページ スクロール イベントなどの一部の高頻度イベントを制限するために使用されます。スロットリングにより、イベント処理関数の実行頻度を制限し、頻繁なトリガーによって引き起こされるパフォーマンスの問題を回避し、ページ応答のスムーズさを確保できます。

2 つの違いは、実行のタイミングにありますアンチシェイクは、イベントがトリガーされた後、一定期間待機してからイベント処理関数を実行することですが、スロットリングは、イベントがトリガーされた直後にイベント処理関数を実行し、指定された時間内に 1 回だけ実行することですアンチシェイクはユーザーによって頻繁にトリガーされるイベントに適しており、スロットルは高頻度によってトリガーされるイベントに適しています。

以下は、JavaScript で手ぶれ補正機能とスロットリング機能を実装するサンプルコードです。

手ぶれ補正機能の実装:

function debounce(func, delay) {
    
    
    let timerId;
  
    return function() {
    
    
        clearTimeout(timerId);
        timerId = setTimeout(func, delay);
    }
}

// 使用示例
function handleDebounce() {
    
    
    console.log('Debounced function is called');
}

const debouncedFunc = debounce(handleDebounce, 300);
debouncedFunc(); // 只有在 300ms 内没有再次调用时才会执行 handleDebounce 函数

スロットリング機能の実装:

function throttle(func, delay) {
    
    
    let timerId;
    let lastExecTime = 0;
  
    return function() {
    
    
        const currentTime = Date.now();
        const elapsed = currentTime - lastExecTime;
        const context = this;
        const args = arguments;
        
        if (elapsed < delay) {
    
    
            clearTimeout(timerId);
            timerId = setTimeout(function() {
    
    
                lastExecTime = currentTime;
                func.apply(context, args);
            }, delay - elapsed);
        } else {
    
    
            lastExecTime = currentTime;
            func.apply(context, args);
        }
    }
}

// 使用示例
function handleThrottle() {
    
    
    console.log('Throttled function is called');
}

const throttledFunc = throttle(handleThrottle, 300);
throttledFunc(); // 间隔小于 300ms 时不执行 handleThrottle 函数,超过 300ms 才执行

デバウンスとスロットルは、関数の実行頻度を制限するために使用される 2 つの方法であり、頻繁な呼び出しによって引き起こされるリフロー問題を回避するのに役立ちます。以下は、デバウンスとスロットルを使用してリフローの数を減らす方法を示す実践的なケースです。

検索ボックスがあるとします。ユーザーが検索ボックスに入力しているとき、不必要なネットワーク要求を減らすために、ユーザーが入力をやめてから 500 ミリ秒以内に検索操作を実行する必要があります。

まず、デバウンスおよびスロットル メソッドを提供する lodash ライブラリをインポートする必要があります。

import {
    
     debounce, throttle } from 'lodash';

次に、検索関数を作成する必要があります。

function search(query) {
    
    
  // 发起搜索请求的逻辑
  console.log('搜索关键词:', query);
}

次に、debounce メソッドを使用して、ユーザーが入力を停止してから 500 ミリ秒後に検索関数を実行する新しい関数を作成できます。

const debouncedSearch = debounce(search, 500);

最後に、ユーザー入力イベントをリッスンし、debouncedSearch 関数を呼び出す必要があります。

const inputElement = document.querySelector('input');

inputElement.addEventListener('input', (event) => {
    
    
  const query = event.target.value;
  debouncedSearch(query);
});

こうすることで、ユーザーが入力すると、debouncedSearch 関数が 500 ミリ秒に 1 回だけ呼び出されます。ユーザーが 500 ミリ秒以内に入力を続けた場合、debouncedSearch 関数は呼び出されず、頻繁なネットワーク リクエストが回避されます。

同様に、throttle メソッドを使用して関数の実行頻度を制限することもできます。スロットル メソッドとデバウンス メソッドの違いは、スロットルは特定の時間間隔内で関数を複数回実行するのに対し、デバウンスは指定された時間内の最後の呼び出しでのみ有効になることです。状況に応じて、適切な方法を選択することで、リフローの回数を減らすことができます。

10. setInterval の代わりに requestAnimationFrame を使用すると、ブラウザのパフォーマンスを向上させることができます。

setInterval の代わりに requestAnimationFrame を使用して均一なアニメーションを作成する
上記の記事にrequestAnimationFramereplaceの使用方法setIntervalの実践例が記録されているので、興味のある方はご覧ください。

アニメーションの作成に使用される場合setInterval、コードを実行するための一定の時間間隔が存在しますが、この時間間隔はJavaScriptシングルスレッドであるため不正確であり、コードの実行は他のタスクやイベントの影響を受ける可能性があります。これにより、アニメーションのフレーム レートが不安定になったり、途切れたりすることがあります。

使用方法としてはrequestAnimationFrame、ブラウザが自身のリフレッシュレートに応じてコールバック関数の実行タイミングを決定し、アニメーションのフレームレートが安定してスムーズになるようにします。同時に、requestAnimationFrameこのメソッドはブラウザの再描画前に実行されるため、アニメーションとブラウザの描画プロセスの同期が向上し、不必要な再描画が回避されます。

さらに、requestAnimationFrameこの方法では、アニメーションが非表示になったときに自動的に一時停止することもできるため、不必要なコンピューティング リソースが節約されます。

要約すると、requestAnimationFrameオーバーライドを使用するとsetIntervalブラウザのパフォーマンスが向上し、よりスムーズで効率的なアニメーションが可能になります。

11. ページ上の要素の数と複雑さを最小限に抑え、リフローの発生を減らすために表示する必要のない要素を非表示にするか読み込みを遅らせます。

上記の最適化戦略に従うことで、逆流現象を効果的に軽減または最適化し、フロントエンドで開発された Web アプリケーションのパフォーマンスを向上させることができます。

要約する

このブログ投稿では、フロントエンドのパフォーマンスの最適化におけるブラウザーのレンダリングの最適化について詳しく説明します。高性能のフロントエンド アプリケーションを構築する場合、ブラウザーのレンダリング プロセスを最適化することが重要であることがわかりました。ブラウザのレンダリング プロセスを理解し、効果的な最適化戦略を採用することで、アプリケーションのパフォーマンスとユーザー エクスペリエンスを大幅に向上させることができます。

レンダリング階層を最小限に抑え、リフロー再描画操作を減らす方法について詳しく説明しました。適切な HTML と CSS 構造を使用し、頻繁な DOM 操作を回避することで、ブラウザーがレイアウトを再計算して再描画する回数を減らし、レンダリング効率を向上させることができます。

全体として、ブラウザーのレンダリング パフォーマンスを最適化することが、フロントエンド アプリケーションのパフォーマンスを向上させるための鍵の 1 つです。ブラウザのレンダリング プロセスを理解し、いくつかの効果的な最適化戦略を採用することで、アプリケーションの効率と応答性を高め、ユーザーの満足度とエクスペリエンスを向上させることができます。

おすすめ

転載: blog.csdn.net/jieyucx/article/details/132469062