本記事のソースコードの利用によって生じた損害について、当方は一切の責任を負いません。ご自身の判断と責任のもとで参照・ご利用ください。
WebサイトにWebフォントを使っていると、ページが表示された後にフォントが切り替わる「Flash Of Unstyled Text (FOUT)」が起こることがあります。これを防ぐために、Webフォントの読み込みが完了するまでページ全体をローディングアニメーションで隠し、読み込み完了後に一気に表示する「Flash Of Invisible Text (FOIT)」方式のローディングを実装しました。
今回の実装は、Adobe Fonts(Typekit)を使用しており、WordPress 環境を想定しています。
実装のポイント
- 初期状態でのコンテンツ隠蔽: Webフォントの読み込みが始まる前に、CSSでページコンテンツ全体を確実に非表示にしておきます。これにより、フォントが適用されていない状態での表示(FOUT)を防ぎます。
- document.fonts.ready の活用: JavaScript の document.fonts.ready プロミスを使用して、Webフォントの読み込みが完全に完了したことを検出します。
- ローディングアニメーションの制御: フォント読み込み完了後、ローディングアニメーションをフェードアウトさせると同時に、隠していたページコンテンツをフェードインさせます。
- パフォーマンス考慮: 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' );
意図した通りに動いたのでこれでよしとします。