コンテンツにスキップ

レンダリング

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. レンダリングの仕組み

構成する処理

以下の8つの処理からなる。

クライアントの操作のたびにイベントが発火し、Scriptingプロセスが繰り返し実行される。

  • Downloading
  • Parse
  • Scripting
  • Rendering
  • CalculateStyle
  • Paint
  • Rasterize
  • Composite

browser-rendering


01-02. マークアップ言語

▼ マークアップ言語とは

ハードウェアが読み込むファイルには、バイナリファイルとテキストファイルがある。

このうち、テキストファイルをタグとデータによって構造的に表現し、ハードウェアが読み込める状態する言語のこと。

▼ マークアップ言語の歴史

Webページをテキストによって構成するための言語をマークアップ言語という。

1970年、IBMが、タグによって、テキスト文章に構造や意味を持たせるGML言語を発表した。

markup-language-history


xml 形式:Extensible Markup Language

xml 形式とは

テキストファイルのうち、何らかのデータの構造を表すことに特化している。

▼ スキーマ言語とは

マークアップ言語の特に xml 形式で、タグの付け方は自由である。

しかし、利用者間で共通のルールを設けたほうが良い。

ルールを定義するための言語をスキーマ言語という。

スキーマ言語に、DTD:Document Type Definition (文書型定義) がある。

*実装例*

<!DOCTYPE Employee[
    <!ELEMENT Name (First, Last)>
    <!ELEMENT First (#PCDATA)>
    <!ELEMENT Last (#PCDATA)>
    <!ELEMENT Email (#PCDATA)>
    <!ELEMENT Organization (Name, Address, Country)>
    <!ELEMENT Name (#PCDATA)>
    <!ELEMENT Address (#PCDATA)>
    <!ELEMENT Country (#PCDATA)>
    ]>


html 形式:HyperText Markup Language

html 形式とは

テキストファイルのうち、Webページの構造を表すことに特化している。


01-03. JavaScript

マークアップ言語へのJavaScriptの組み込み

▼ インラインスクリプト

js ファイルを直接的に組み込む。

<script>
  document.write("JavaScriptを直接的に組み込んでいます。");
</script>

▼ 外部スクリプト

外部 js ファイルを組み込む。

<script src="sample.js"></script>

CDN (グローバルなキャッシュサーバー) の仕組みを使用して、Web上からWebページを取得もできる。

<script
  src="https://cdn.jsdelivr.net/npm/lazyload@2.0.0-rc.2/lazyload.min.js"
  integrity="sha256-WzuqEKxV9O7ODH5mbq3dUYcrjOknNnFia8zOyPhurXg="
  crossorigin="anonymous"
></script>

▼ scriptタグが複数ある場合

1 個のWebページの html ファイル内で、scriptタグが複数に分散していても、Scriptingプロセスでは、1つにまとめて実行される。

そのため、より上部のscriptタグの処理は、より下部のscriptに引き継がれる。

1。

例えば、以下のコードがある。

localNum
<p>見出し1</p>

<script>
  var globalNum = 10;
</script>

<p>見出し2</p>

<script>
  globalNum = globalNum * 10;
</script>

<p>見出し3</p>

<script>
  document.write("<p>結果は" + globalNum + "です</p>");
  var foo = true;
</script>

<script src="sample.js"></script>
// sample.js
// 無名関数の即時実行。定義と呼び出しを同時に実行する。
(function () {
  // 外側の変数 (foo) を参照できる。
  if (foo) {
    console.log("外部ファイルを読み出しました");
  }

  var localNum = 20;

  function localMethod() {
    // 外側の変数 (localNum) を参照できる。
    console.log("localNum");
  }

  // 定義した関数を実行
  localMethod();
})();
  1. 実行時には以下の様に、まとめて実行される。

ここでは、html ファイルで定義した関数の外にある変数は、グローバル変数になっている。

1 個のページを構成する html ファイルを別ファイルとして分割していても、同じである。

<script>
  var globalNum = 10;

  localNum = localNum * 10;

  document.write("<p>結果は" + num + "です</p>");
  var foo = true;

  // 無名関数の即時実行。定義と呼び出しを同時に実行する。
  (function () {
    // 外側の変数 (foo) を参照できる。
    if (foo) {
      console.log("外部ファイルを読み出しました");
    }

    var localNum = 20;

    function localMethod() {
      // 外側の変数 (localNum) を参照できる。
      console.log("localNum");
    }

    // 定義した関数を実行
    localMethod();
  })();
</script>


01-04. ブラウザのバージョン

Polyfill

▼ Polyfillとは

JavaScriptやHTMLの更新にブラウザが追いついていない場合、それを補完するように実装されたパッケージのこと。

『Polyfilla』に由来している。


02. Downloading処理

Downloading処理とは

▼ 非同期的な読み出し

まず、サーバーサイドからWebページ (html ファイル、.css ファイル、js ファイル、画像ファイル) は、分割されながら、バイト形式でレスポンスされる。

これは、メッセージボディに含まれている。

これを優先度を基に読み込む処理。

分割でレスポンスされたWebページを、随時読み込んでいく。

そのため、各Webページの読み出しは非同期的に行われる。

Downloading処理が終了したWebページから、次のParse処理に進んでいく。

▼ Webページの要素の優先順位

(1)

HTML

(2)

CSS

(3)

JS

(4)

画像


Pre-Loading

▼ Pre-Loadingとは

Downloading処理の優先順位を上げるように宣言する。

優先度の高い分割Webページは、次のParse処理、Scripting処理も行われる。

そのため、JSファイルのScripting処理が、以降のimageファイルのDownloading処理よりも早くに行われることがある。

<head>
  <meta charset="utf-8" />
  <title>Title</title>
  <!-- preloadしたいものを宣言 -->
  <link rel="preload" href="style.css" as="style" />
  <link rel="preload" href="main.js" as="script" />
  <link rel="stylesheet" href="style.css" />
</head>

<body>
  <h1>Hello World</h1>
  <script src="main.js" defer></script>
</body>


Lazy Loading (遅延読み出し)

▼ Lazy Loadingとは

条件に合致した要素を随時読み込む。

条件の指定方法には、scroll/resize イベントに基づく方法と、Intersection Observerによる要素の交差率に基づく方法がある。

画像ファイルの遅延読み出しでは、読み出し前にダミー画像を表示させておき、遅延読み出し時にダミー画像パスを本来の画像パスに上書きする。

▼ scrollイベントとresizeイベントに基づく遅延読み出し

scrollイベントとresizeイベントを監視し、これらのイベントの発火をトリガーにして、画面内に新しく追加された要素を随時読み込む。

▼ Intersection Observerの仕組みに基づく遅延読み出し

Intersection Observerの仕組みでは、特定のHTML要素を指定し、これがほかの要素とどのくらい交差しているかを非同期に検知できる。

指定の交差率を超えて、初めてその要素を読み込む。

例えば、交差率の閾値を『0.5』と設定すると、ターゲットエレメントの交差率が『0.5』を超えた要素を随時読み込む。

intersection-observer

*実装例*

import {useRef, useState, useEffect, type ReactNode} from "react";

type
LazyRenderWithIntersectionObserverProps = {

  /**
   * レンダリングを遅延させたい要素
   */
  children: ReactNode;

  /**
   * レンダリング遅延中のプレースホルダーの高さ
   *
   * レンダリング遅延中は本来あるべき要素がなくなるため、仮の空欄を挿入する
   */
  minHeight: number;

  /**
   * レンダリング開始の検知に使用する領域
   *
   * スクロールによって ref.current 要素が rootMargin 内に入ると、遅延させていたレンダリングが始まる
   */
  rootMargin? : string;
};

/**
 * Intersection Observerの仕組みを使用して、渡した要素のレンダリングを遅延させる
 * @see https://developer.mozilla.org/ja/docs/Web/API/IntersectionObserver
 */
export const LazyRenderWithIntersectionObserver = ({
  children,
  minHeight,
  rootMargin = "200px",
}: LazyRenderWithIntersectionObserverProps)
:
JSX.Element
=>
{

  const ref = useRef < HTMLDivElement > (null);
  const [shouldRender, setShouldRender] = useState(false);

  useEffect(() => {
    // 一度レンダリングを開始した後の場合
    if (shouldRender) {
      return;
    }

    // ref.current 要素を取得できない場合
    const targetElement = ref.current;
    if (!targetElement) {
      return;
    }

    const intersectionObserver = new IntersectionObserver(
      ([entry]) => {
        // ref.current 要素が rootMargin 内に入った場合
        if (entry.isIntersecting) {
          setShouldRender(true);
          intersectionObserver.disconnect();
        }
      },
      {rootMargin}
    );

    intersectionObserver.observe(targetElement);
    return () => intersectionObserver.disconnect();
  }, [rootMargin, shouldRender]);

  return (
    <div ref={ref}>
      {shouldRender ? children : <div style={{minHeight}}/>}
    </div>
  );
}
;
<LazyRenderWithIntersectionObserver
  minHeight={120}
  children={
    // ここにレンダリングを遅延させたいの要素を設定する
  }
/>


Eager Loading

▼ Eager Loadingとは


02-02. Parse処理

Parse処理とは

Downloading処理によって読み込まれたWebページを翻訳するプロセス


html 形式テキストファイルの構造解析

▼ 構造解析の流れ

Downloading処理で読みこまれたバイト形式ファイルは、文字コードを基に、一連の文字列に変換される。

ここでは、以下の html ファイルと .css ファイル (style.css) に変換されたとする。

<!doctype html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <div style="width: 50%">
      <div style="width: 50%">Hello world!</div>
    </div>
  </body>
</html>
/* style.css */
body {
  font-size: 16px;
}
p {
  font-weight: bold;
}
span {
  color: red;
}
p span {
  display: none;
}
img {
  float: right;
}

Webページの文字列からHTMLタグが認識され、トークンに変換される。

各トークンは、1 個のオブジェクトに変換される。

dom-tree_process

HTMLパーサーは、オブジェクトをノードとして、DOMツリーを作成する。

DOMツリーを作成する途中でscriptタグに到達すると、いったん、JSファイルを読み込んでScripting処理を終えてから、DOMツリーの作成を再開する。

DOMのインターフェースについては、以下のリンクを参考にせよ。

dom-tree

同時に、CSSパーサーは、head タグにある link タグを基にサーバーへリクエストを送信する。

レスポンスされた .css ファイルに対してDownloading処理を行った後、オブジェクトをノードとして、CSSOMツリーを作成する。

cssom-tree


xml 形式テキストファイルの構造解析

▼ 構造解析の流れ

レンダリングエンジンは、最初に出現するルート要素を根 (ルート) として扱う。さらに、すべての要素や属性を、そこから延びる枝葉として意味づける。これにより、レンダリングツリーを作成する。

*例*

DOMによるツリー構造化


03. Scripting処理

Scripting処理とは

サーバーサイドからWebページ (html ファイル、.css ファイル、js ファイル、画像ファイル) を取得した後、レンダリングエンジンのHTMLの解析が script タグに到達する。

レンダリングエンジンは、JavaScriptエンジンに script タグの内容を渡す。

JavaScriptエンジンは、JavaScriptコードを機械語に翻訳し、実行する。

この処理は、初回アクセス時のみでなく、イベントが発火したときにも実行される。


JavaScriptエンジン

▼ JavaScriptエンジンとは

JavaScriptのインタプリタのこと。

JavaScriptエンジンは、レンダリングエンジンから html ファイルに組み込まれたJavaScriptのコードを受け取る。

JavaScriptエンジンは、これを機械語に翻訳し、ハードウェアに対して命令する。

JavaScriptEngine

▼ 機械語翻訳

JavaScriptエンジンは、コードを、字句解析、構造解析、意味解釈、命令の実行をコード1行ずつに対し、繰り返し実行する。

03-02. イベント

イベント

▼ イベントとは

ブラウザの各操作はイベントとして .js ファイルまたは html ファイルに紐付けられている。

▼ イベントハンドラ関数とは

イベントの発火に伴ってコールされる関数のこと。

イベントハンドラ関数が実行されるたびにScripting処理が繰り返される。


html 形式におけるイベントハンドラ関数のコール

onload

『Webページのローディング』というイベントが発火すると、イベントハンドラ関数をコールする。

onclick

『要素のクリック』というイベントが発火すると、イベントハンドラ関数をコールする。

<input type="button" value="ボタン1" onclick="methodA()" />

<script>
  function methodA() {
    console.log("イベントが発火しました");
  }
</script>


JS形式におけるイベントハンドラ関数のコール

document.getElementById.onclick 関数

指定したIDに対して、1 個のイベントと 1 個のイベントハンドラ関数を紐付ける。

*実装例*

// 指定したIDで、クリックイベントが発火した時に、処理を実行する。
document.getElementById("btn").onclick = () => {
  console.log("イベントが発火しました");
};

document.addEventListener 関数

1 個のイベントに対して、1つ以上のイベントハンドラ関数を紐付ける。

第一引数で、click などのイベントを設定し、第二引数で関数 (無名関数でも可) を渡す。

false を設定することにより、イベントバブリングを行わせない。

*実装例*

<button id="btn">表示</button>

<script>
  const btn = document.getElementById("btn");
  btn.addEventListener(
    "click",
    () => {
      console.log("クリックされました!");
    },
    false,
  );
</script>
// DOMContentLoadedイベントが発火した時に、処理を実行する。
document.addEventListener("DOMContentLoaded", () => {
  console.log("イベントが発火しました");
});
// 1つ目
document.getElementById("btn").addEventListener(
  "click",
  () => {
    console.log("イベントが発火しました`(1)`");
  },
  false,
);

// 2つ目
document.getElementById("btn").addEventListener(
  "click",
  () => {
    console.log("イベントが発火しました`(2)`");
  },
  false,
);


04. Rendering処理

Rendering処理とは

レンダリングツリーが作成され、ブラウザ上のどこに何を描画するのかを計算する。

CalculateStyle処理とLayout処理に分けられる。


04-02. CalculateStyle処理

CalculateStyle処理とは

レンダリングエンジンは、DOMツリーのルートのノードから順にCSSOSツリーを適用し、Renderツリーを作成する。

Renderツリー


04-03. Layout処理

Layout処理とは

上記で読み込まれた html 形式テキストファイルには、ネストされた 2 つの div がある。

1 つ目 (親) の div より、ノードの表示サイズをビューポートの幅の 50% に設定する。

この親に含まれている 2 つ目 (子) の div より、その幅を親の50%、つまりビューポートの幅の25%になるようにレイアウトされる。

Layout処理


05. Paint処理

Paint処理とは

DOMツリーの各ノードを、ブラウザ上に描画する。


05-02. Rasterize処理

Rasterize処理とは

記入中...


05-03. CompositeLayers処理

CompositeLaysers処理とは

記入中...