リンクを押すと、色のパネルが横からスライドして画面を覆い、次のページが表示されるとパネルが抜ける。そんなページ遷移アニメーション。色・速さは数字の変更だけで調整できる。
パネルは、黒いパネルとグレーのパネルの2枚。黒いパネルは画面を通り抜けるだけで、画面を覆うのはグレーのパネル。
パネルの動きは時間で決まっていて、読み込みの進み具合に合わせて速さが変わるものではない。読み込みが終わるまではグレーのパネルで覆ったまま待ち、終わったらパネルが抜ける。
動きは次のとおり。
| 場面 | 動き |
|---|---|
| ページを開いたとき | グレーのパネルが画面を覆った状態で始まる。読み込みが終わると、パネルが左へ抜ける |
| サイト内のリンクを押したとき | 黒いパネルが右から左へ通り抜け、同時にグレーのパネルが右から入って画面を覆う。覆いきったら次のページへ移る |
| ブラウザの「戻る」で戻ったとき | 前のページが読み込み直さずに復元された場合は、パネルを出していない状態に戻す。ページを読み込み直した場合は、ページを開いたときと同じ |
※ パネルが出ている間は、ページをスクロールできない。パネルが抜けきると戻る。 ※ jQuery は不要。WordPress でなくても動く。
※ デモは記事の枠の中で動かしている。ページの移動の代わりに、枠の中身と背景の色を入れ替えている。
HTML
<body> の直後に配置。
<!-- ===== ページ遷移アニメーション:<body> の直後 =====
黒いパネル(A)とグレーのパネル(B)の2枚 -->
<div id="page-loader" class="pl-overlay" role="status" aria-label="読み込み中">
<i class="pl-a"></i><i class="pl-b"></i>
</div>※ #page-loader は画面全体に固定した箱で、箱自体は動かない。動くのは、中の黒いパネル(.pl-a)とグレーのパネル(.pl-b)の2枚。 ※ <body> の直後に置くのは、ページの中身より先に画面を覆うため。
CSS と JavaScript
<head> 内に配置。CSS・<noscript>・JavaScript をまとめて1つにしてある。長いよ。
<!-- ===== ページ遷移アニメーション:<head> 内 ここから =====
流れ(ページを開いたとき): パネルが画面を覆った状態で始まる
→ 読み込みが終わったら、グレーのパネル(B)が左へ抜ける
流れ(リンクを押したとき): 黒いパネル(A)が右から左へ通り抜け、同時にグレーのパネル(B)が右から入って覆う
→ 覆いきったら次のページへ移動 → 次のページは、覆った状態から始まってパネルが抜ける
・パネル本体(#page-loader)は <body> の直後に置く
・クラス名は pl- で始まる(サイトのほかの CSS とぶつからないようにするため) -->
<style>
/* ===== ここから CSS ===== */
/* ---- パネルを入れる箱(画面全体に固定。箱そのものは動かない) ---- */
.pl-overlay {
position: fixed;
inset: 0;
z-index: 99999; /* ★ 重なり順。メニューなど、ほかの要素より必ず前に出す */
--pl-a: #4A4A4A; /* ★ 色A(黒いパネル。止まらずに右から左へ通り抜ける) */
--pl-b: #C6C6C6; /* ★ 色B(画面を覆うグレー) */
--pl-dur: .5s; /* ★ 1枚が動く時間。※ JavaScript の DUR と同じ長さにする */
--pl-ease: cubic-bezier(.76, 0, .24, 1); /* ★ グレーの動き方 */
}
.pl-overlay i {
position: absolute;
inset: 0;
display: block;
}
/* 黒いパネル(A):ふだんは右の外で待機。覆うときだけ、右から左へ通り抜ける。グレーの上を通るので z-index は B より上 */
.pl-overlay .pl-a {
background: var(--pl-a);
z-index: 2;
transform: translateX(100%);
}
/* グレーのパネル(B):ページを開いたときは、画面を覆った位置から始まる */
.pl-overlay .pl-b {
background: var(--pl-b);
z-index: 1;
}
/* ---- 抜ける(読み込みが終わったとき):グレーのパネル(B)が左へ抜ける ---- */
.pl-overlay.is-leave .pl-b {
transform: translateX(-100%);
transition: transform var(--pl-dur) var(--pl-ease);
}
/* ---- 覆う(リンクを押したとき):黒いパネル(A)が右から左へ通り抜け、同時にグレーのパネル(B)が右から入って覆う ---- */
.pl-overlay.is-pre i {
transform: translateX(100%); /* 右の外で待機(アニメなし) */
}
.pl-overlay.is-enter i {
transition: transform var(--pl-dur) var(--pl-ease);
}
.pl-overlay.is-enter .pl-a {
transform: translateX(-100%);
/* ★ 黒いパネルだけ動き方を緩やかにする。端で時間を使わないので、黒いパネルが画面を覆っている時間が長く見える */
transition: transform var(--pl-dur) cubic-bezier(.4, 0, .6, 1);
}
/* ---- 使っていないとき:見えない・クリックも邪魔しない(次のリンクが押されるまで待機) ---- */
.pl-overlay.is-hidden {
visibility: hidden;
pointer-events: none;
}
.pl-overlay.is-hidden i {
transform: translateX(100%);
}
/* 読み込み中・移動中はスクロールさせない(パネルが抜けきったら JavaScript が pl-lock を外す)
scrollbar-gutter:スクロールバーの幅を常に取っておき、ロックの前後で画面の幅が変わらないようにする */
html {
scrollbar-gutter: stable;
}
html.pl-lock {
overflow: hidden;
}
/* OS の設定で「視差効果を減らす」のときは、動かさない(すぐ切り替わる) */
@media (prefers-reduced-motion: reduce) {
.pl-overlay i {
transition: none !important;
}
}
/* ===== ここまで CSS ===== */
</style>
<!-- JavaScript が動かない環境では、パネルを出さない(出しっぱなしでページが見えなくなるのを防ぐ) -->
<noscript>
<style>
.pl-overlay {
display: none;
}
</style>
</noscript>
<script>
/* ===== JavaScript =====
・ページを開いたとき:読み込み(load)と WAIT_IMAGES の画像が全部そろったら、パネルが抜ける。MIN_SHOW より早くは抜けない。MAX_WAIT を過ぎたら必ず抜ける
・リンクを押したとき:サイト内の普通のリンクだけ、パネルで覆ってから次のページへ移動する
・「戻る」で戻ってきたとき:パネルを待機の状態に戻す */
(function () {
/* ---- 設定 ---- */
var MIN_SHOW = 500; /* ★ 最低の表示時間(ミリ秒)。読み込みが速くても、この時間はパネルで覆う。0 で読み込み次第すぐ抜ける */
var MAX_WAIT = 20000; /* ★ 最大の待ち時間(ミリ秒)。読み込みが終わらなくても、これを過ぎたらパネルが抜ける(出しっぱなし防止) */
var DUR = 500; /* ★ 1枚が動く時間(ミリ秒)。CSS の --pl-dur と同じにする */
var WAIT_IMAGES = []; /* ★ パネルが抜ける前に読み込みを待つ画像(load のあとで読み込む、スライダーの背景など)。例:['/img/slide-1.jpg']。空なら load だけを待つ */
var html = document.documentElement;
var startedAt = Date.now();
var opened = false; /* パネルが抜け始めたか */
var leaving = false; /* 次のページへ移動中か */
html.classList.add('pl-lock'); /* スクロールを止める(パネルが抜けきったら外す) */
function reduced() {
return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
function unlock() {
html.classList.remove('pl-lock');
}
function nextFrames(fn) {
requestAnimationFrame(function () {
requestAnimationFrame(fn);
});
}
/* ---- パネルが抜ける(読み込みが終わったとき) ---- */
function open() {
if (opened) {
return;
}
opened = true;
var wait = Math.max(0, MIN_SHOW - (Date.now() - startedAt));
setTimeout(function () {
var el = document.getElementById('page-loader');
if (!el) {
unlock();
return;
}
el.classList.add('is-leave');
setTimeout(function () {
if (leaving) {
return; /* パネルが抜けている途中でリンクを押されたとき:覆う動きを優先する */
}
el.classList.remove('is-leave');
el.classList.add('is-hidden'); /* 見えない状態で待機(次のリンクで、また使う) */
unlock();
}, reduced() ? 0 : DUR + 200); /* 抜けきってから隠す(DUR に少し余裕を足す) */
}, wait);
}
/* ---- 覆う(リンクを押したとき)。覆いきったら callback で次のページへ移動 ---- */
function cover(el, callback) {
if (reduced()) {
callback();
return;
}
html.classList.add('pl-lock');
el.classList.remove('is-hidden', 'is-leave');
el.classList.add('is-pre'); /* 画面の外で待機(アニメなし) */
nextFrames(function () {
el.classList.remove('is-pre');
el.classList.add('is-enter'); /* 黒いパネル(A)が通り抜け、グレーのパネル(B)が入ってくる */
setTimeout(callback, DUR + 370); /* 覆いきってから移動する(DUR に少し余裕を足す) */
});
}
/* ---- リンクのクリックを見張る:サイト内の普通のリンクだけ、パネルを挟む ---- */
document.addEventListener('click', function (e) {
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || leaving) {
return; /* ほかの処理が止めたクリック、別タブで開く操作などは対象外 */
}
var a = e.target && e.target.closest ? e.target.closest('a') : null;
var href = a ? a.getAttribute('href') : null;
if (!a || !href || href.charAt(0) === '#') {
return; /* リンクでない・ページ内リンクは対象外 */
}
if ((a.target && a.target !== '_self') || a.hasAttribute('download')) {
return; /* 別タブ・ダウンロードは対象外 */
}
var u;
try {
u = new URL(a.href, location.href);
} catch (err) {
return;
}
if (u.origin !== location.origin) {
return; /* 外部サイト・mailto・tel などは対象外 */
}
if (u.pathname === location.pathname && u.search === location.search) {
return; /* 同じページは対象外 */
}
if (/\.(pdf|zip|jpe?g|png|gif|webp|svg|mp4)$/i.test(u.pathname)) {
return; /* ファイルへのリンクは対象外 */
}
var el = document.getElementById('page-loader');
if (!el) {
return;
}
e.preventDefault();
leaving = true;
cover(el, function () {
location.href = u.href;
});
});
/* 「戻る」で戻ってきたとき(ブラウザが前のページをそのまま復元したとき):パネルを待機に戻す */
window.addEventListener('pageshow', function (e) {
if (e.persisted) {
var el = document.getElementById('page-loader');
if (el) {
el.className = 'pl-overlay is-hidden';
}
leaving = false;
unlock();
}
});
/* ここで待つ:読み込み(load)と WAIT_IMAGES の画像が全部そろったら、パネルが抜ける。画像が読めなかったときも「終わった」とみなす */
var pending = 1; /* 待っているものの数(まず load の1つ) */
function oneDone() {
pending--;
if (pending <= 0) {
open();
}
}
for (var i = 0; i < WAIT_IMAGES.length; i++) {
pending++;
var im = new Image();
im.onload = oneDone;
im.onerror = oneDone;
im.src = WAIT_IMAGES[i];
}
if (document.readyState === 'complete') {
oneDone();
} else {
window.addEventListener('load', oneDone);
}
setTimeout(open, MAX_WAIT); /* 保険:時間切れでもパネルが抜ける */
})();
</script>
<!-- ===== ページ遷移アニメーション:<head> 内 ここまで ===== -->※ JavaScript は <head> 内に置く。ページの表示が始まる前にスクロールを止め、読み込みの完了(load)を待ち始める。
JavaScript が動かない環境では、<noscript> でパネルを出さないようにしている。リンクは普通に使える。 また、端末で「視差効果を減らす」(動きを減らす)設定にしている場合は、パネルを動かさずにすぐ切り替える。
パネルがスライドするタイミング設定
パネルがスライドするタイミングは、JavaScript の3つの数字で決める。
| 設定 | 役割 |
|---|---|
MIN_SHOW | 最低の表示時間(ミリ秒)。読み込みが速くても、MIN_SHOW の時間はパネルで覆う。0 にすると、読み込みが終わり次第すぐ抜ける |
MAX_WAIT | 最大の待ち時間(ミリ秒)。読み込みが終わらなくても、MAX_WAIT の時間を過ぎたら必ず抜ける |
WAIT_IMAGES | パネルが抜ける前に読み込みを待つ画像。空([])なら、ページの読み込み(load)だけを待つ |
待つ処理は、JavaScript の最後(pending で数えている部分)。ページの読み込み(load)と、WAIT_IMAGES の画像が全部そろったら、パネルがスライドする。
load は、HTML に書いた画像・CSS・JavaScript が全部揃うまで待つ。<img> で書いた画像は WAIT_IMAGES に入れなくていい。WAIT_IMAGES に入れるのは、JavaScript のスライダーがあとから背景に入れる画像のように、load では待てない画像。
var WAIT_IMAGES = ['/img/slide-1.jpg']; /* スライダーの1枚目。複数ある場合は ['/img/slide-1.jpg', '/img/slide-2.jpg'] */※ 画像のパスは / から始まる形で書く。img/slide-1.jpg のように書くと、下の階層のページでは場所がずれて読み込めない。
※ 画像が読み込めなかった(エラー)ときも、待ち続けずに「読み込み終わった」とみなしてパネルが抜ける。 ※ MAX_WAIT は、通信が遅い環境でパネルが出しっぱなしになるのを防ぐための保険。
パネルを表示させない設定
サイト内の普通のリンクだけにパネルを出す。次の場合は、パネルを出させないように制御。
- 外部リンクや別タブのときはパネルを出さない
- 読み込みが終わらなくても20秒(
MAX_WAIT)で必ずスライドする - JavaScript がオフのとき、動きを減らす設定のとき
- パネルが抜けている途中で、またリンクを押されたとき
| リンク | 理由 |
|---|---|
ページ内リンク(#〜)・同じページへのリンク | ページを移らないため |
別タブで開くリンク(target="_blank")・download 付きのリンク | 今のページは残るため |
外部サイト・mailto:・tel: | サイトの外へ出るため |
画像・PDF・zip などのファイル(.pdf .zip .jpg .png など) | ページではないため |
| Ctrl・⌘・Shift・Alt を押しながらのクリック | 別タブ・別ウインドウで開く操作のため |
| ほかのスクリプトが止めたクリック | ほかの動き(モーダルなど)を優先するため |
※ ファイルの種類を増やす場合は、JavaScript の /\.(pdf|zip|jpe?g|png|gif|webp|svg|mp4)$/i に拡張子を足す。
調整できる部分の設定
調整するときの変更箇所は次のとおり。
| 変えたいもの | 変更する場所 |
|---|---|
| 黒いパネルの色 | CSS の --pl-a |
| グレーのパネルの色 | CSS の --pl-b |
| パネルの動く速さ | CSS の --pl-dur と JavaScript の DUR |
| グレーのパネルの動き方 | CSS の --pl-ease |
| 黒いパネルの動き方 | CSS の .pl-overlay.is-enter .pl-a の cubic-bezier(.4, 0, .6, 1) |
| 重なり順 | CSS の .pl-overlay の z-index |
| 最低の表示時間 | JavaScript の MIN_SHOW(ミリ秒) |
| 最大の待ち時間 | JavaScript の MAX_WAIT(ミリ秒) |
パネルの動く速さは、CSS の --pl-dur と JavaScript の DUR をセットで変える必要がある。.5s なら 500、.8s なら 800。
WordPress で設定する場合
HTML は header.php の <body> の直後、CSS と JavaScript は header.php の <head> 内(wp_head() の前)に置く。管理画面・ログイン画面へのリンクにも、パネルを出さないようにする。JavaScript の1行を差し替える。
/* 変更前 */
if (/\.(pdf|zip|jpe?g|png|gif|webp|svg|mp4)$/i.test(u.pathname)) {
/* 変更後 */
if (/\/wp-admin|\/wp-login|\/wp-json|\.(pdf|zip|jpe?g|png|gif|webp|svg|mp4)$/i.test(u.pathname)) {トップページのスライダーの画像を待つ場合は、画像のパスもテーマのフォルダから出す。
var WAIT_IMAGES = <?php echo wp_json_encode( is_front_page() ? array( get_template_directory_uri() . '/img/slide-1.jpg' ) : array() ); ?>; /* トップページだけ、スライダーの1枚目を待つ */※ img/slide-1.jpg はテーマ内の画像の場所に合わせて書き換える。
既存の設定がある場合
すでにローディング画面や、ページ全体をフェードインさせる設定がある場合は、先に外すこと。パネルと二重になって、パネルの動きが見えなくなる。
たとえば body を display: none にして jQuery の fadeIn で出している場合は、body ごとパネルも隠れてしまう。CSS の body { display: none; } と、JavaScript の $('body').fadeIn(…) の両方を消す。
表示環境によって動きの見え方が異なる場合があるため、本番に反映する前に確認してね。
NOTICES
- 記事内容は実装させたものがほとんどですが自己責任で参考にしてください。