Webフォントが読み込まれてからページを表示するローディングアニメーションの実装

電脳備忘録

本記事のソースコードの利用によって生じた損害について、当方は一切の責任を負いません。ご自身の判断と責任のもとで参照・ご利用ください。

WebサイトにWebフォントを使っていると、ページが表示された後にフォントが切り替わる「Flash Of Unstyled Text (FOUT)」が起こることがあります。これを防ぐために、Webフォントの読み込みが完了するまでページ全体をローディングアニメーションで隠し、読み込み完了後に一気に表示する「Flash Of Invisible Text (FOIT)」方式のローディングを実装しました。

今回の実装は、Adobe Fonts(Typekit)を使用しており、WordPress 環境を想定しています。

実装のポイント

  1. 初期状態でのコンテンツ隠蔽: Webフォントの読み込みが始まる前に、CSSでページコンテンツ全体を確実に非表示にしておきます。これにより、フォントが適用されていない状態での表示(FOUT)を防ぎます。
  2. document.fonts.ready の活用: JavaScript の document.fonts.ready プロミスを使用して、Webフォントの読み込みが完全に完了したことを検出します。
  3. ローディングアニメーションの制御: フォント読み込み完了後、ローディングアニメーションをフェードアウトさせると同時に、隠していたページコンテンツをフェードインさせます。
  4. パフォーマンス考慮: dns-prefetch や preconnect を使用し、Webフォントの読み込みを早める工夫をしています。

実装コード

HTML

<html>
  <head>
    <link rel="dns-prefetch" href="https://use.typekit.net">
    <link rel="preconnect" href="https://use.typekit.net" crossorigin>
    
    <script>
      (function(d) {
        var config = {
          kitId: 'qke6hdn',
          scriptTimeout: 6000, // フォント読み込みのタイムアウト時間(ms)。長めに設定。
          async: true
        },
        h=d.documentElement,t=setTimeout(function(){h.className=h.className.replace(/\bwf-loading\b/g,"")+" wf-inactive";},config.scriptTimeout),tk=d.createElement("script"),f=false,s=d.getElementsByTagName("script")[0],a;h.className+=" wf-loading";tk.src='https://use.typekit.net/'+config.kitId+'.js';tk.async=true;tk.onload=tk.onreadystatechange=function(){a=this.readyState;if(f||a&&a!="complete"&&a!="loaded")return;f=true;clearTimeout(t);try{Typekit.load(config)}catch(e){}};s.parentNode.insertBefore(tk,s)
      })(document);

      // Webフォントの読み込み完了を待つ JavaScript
      document.fonts.ready.then(function() {
        var loader = document.getElementById("loader");
        var pageContent = document.getElementById("page-content");
        var bodyElement = document.body; 
        
        // ローディングアニメーションをフェードアウト
        loader.classList.add("loaded"); 
        
        // ページコンテンツをフェードイン表示
        pageContent.classList.add("visible");
        
        // bodyの初期非表示クラスを削除(コンテンツが表示可能になる)
        bodyElement.classList.remove("initial-hidden"); 
        
        // オプション: CSSアニメーション完了後にローダーをDOMから完全に削除
        // #loader.loaded の animation-duration (今回は 2s) に合わせる
        setTimeout(function() {
          if (loader) {
            loader.remove();
          }
        }, 2000); 
      });
    </script>
  </head>
  <body <?php body_class(); ?>>
    <div id="page-content">
      <div class="wrapper">
        <header id="header">
          </header>
        <main>
          </main>
        <footer>
          </footer>
      </div></div><div id="loader">
      <div class="sk-chase">
        <div class="sk-chase-dot"></div>
        <div class="sk-chase-dot"></div>
        <div class="sk-chase-dot"></div>
        <div class="sk-chase-dot"></div>
        <div class="sk-chase-dot"></div>
        <div class="sk-chase-dot"></div>
      </div>
    </div>
  </body>
</html>

CSS

/* ページコンテンツ全体を隠すための初期クラス */
/* bodyに initial-hidden が付いている間、コンテンツとローダーの表示を制御 */
body.initial-hidden #page-content {
  visibility: hidden !important; /* 強制的に隠す */
  opacity: 0 !important;
}
body.initial-hidden #loader {
  display: flex !important; /* ローダーは必ず表示 */
  opacity: 1 !important;
  pointer-events: auto !important; /* ローダー表示中はページの操作をブロック */
}

/* ページコンテンツの初期状態とフェードインアニメーション */
#page-content {
  visibility: hidden; /* コンテンツを完全に隠す */
  opacity: 0;        /* フェードインアニメーション用 */
  transition: opacity 0.5s ease-in; /* フェードインアニメーションの時間 */
}

/* ページコンテンツが表示されるスタイル */
#page-content.visible {
  visibility: visible;
  opacity: 1;
}

/* ローディングオーバーレイのスタイル */
#loader {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 999; /* ページコンテンツより手前に表示 */
  width: 100%;
  height: 100vh;
  background-color: #000014; /* ローダーの背景色 */
  display: flex; /* コンテンツを中央に配置 */
  justify-content: center;
  align-items: center;
}

/* ローディング完了時のフェードアウトアニメーション */
#loader.loaded {
  animation: loadingFadeOut 2s forwards; /* 2秒かけてフェードアウト */
}

/* ローダーのフェードアウトアニメーションの定義 */
@keyframes loadingFadeOut {
  0% {
    opacity: 1;
    pointer-events: auto;
  }
  100% {
    opacity: 0;
    pointer-events: none; /* フェードアウト後はページの操作を許可 */
  }
}

/* 以下は既存のローディングスピナーのアニメーションスタイル */
.sk-chase {
  width: 40px;
  height: 40px;
  position: relative;
  animation: sk-chase 2.5s infinite linear both;
}

.sk-chase-dot {
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;
  animation: sk-chase-dot 2s infinite ease-in-out both;
}

.sk-chase-dot:before {
  content: "";
  display: block;
  width: 25%;
  height: 25%;
  background-color: #fff;
  border-radius: 100%;
  animation: sk-chase-dot-before 2s infinite ease-in-out both;
}

.sk-chase-dot:nth-child(1) { animation-delay: -1.1s; }
.sk-chase-dot:nth-child(2) { animation-delay: -1s; }
.sk-chase-dot:nth-child(3) { animation-delay: -0.9s; }
.sk-chase-dot:nth-child(4) { animation-delay: -0.8s; }
.sk-chase-dot:nth-child(5) { animation-delay: -0.7s; }
.sk-chase-dot:nth-child(6) { animation-delay: -0.6s; }

.sk-chase-dot:nth-child(1):before { animation-delay: -1.1s; }
.sk-chase-dot:nth-child(2):before { animation-delay: -1s; }
.sk-chase-dot:nth-child(3):before { animation-delay: -0.9s; }
.sk-chase-dot:nth-child(4):before { animation-delay: -0.8s; }
.sk-chase-dot:nth-child(5):before { animation-delay: -0.7s; }
.sk-chase-dot:nth-child(6):before { animation-delay: -0.6s; }

functions.php

<body> タグに initial-hidden クラスを動的に追加します。

function add_initial_hidden_to_body_class( $classes ) {
    // ページロード時にのみこのクラスを追加します
    $classes[] = 'initial-hidden';
    return $classes;
}
add_filter( 'body_class', 'add_initial_hidden_to_body_class' );

意図した通りに動いたのでこれでよしとします。

0%