STUDY

シームレスな画面遷移の実装

こんにちは、エンジニアの中島です。
すっかり温かくなって気持ちが良いですね。
花粉さえなければ春は大好きなのですが…。

今回はPjax(非同期画面遷移)を導入できるBarba.js(v2)について書いていこうと思います。
※v1とは仕様が異なりますのでご注意ください。

Barba.jsとは

Barba.jsは、Pjaxを用いてWebサイトのページ間でシームレスな遷移を実装するためのプログレッシブ拡張ライブラリです。

ページ間の遅延を減らし、ブラウザのHTTPリクエストを最小限に抑え、ユーザーのUXを向上させます。

Pjaxについて

javascriptからブラウザの履歴に直接追加させるメソッド「pushState」とjavascriptを用いて非同期通信を行う「Ajax」を組み合わせた技術です。

Ajaxとの違い

Ajaxでは表示内容を書き換えた場合、URL自体は書き変わらずそのままで、ブラウザの履歴にも追加されないため、ブラウザの戻るボタンを押しても内容を戻すことができません。つまりページの遷移がなくURLがそのままということになります。
そこにpushStateを組み合わせることでURLを変更すると共に履歴も残せるようになり過去のページに戻ることが可能になります。

実際に導入してみる

以下のようなページ遷移を実装していきます。
ヘッダーは固定されており、リンクをクリックするとコンテンツ部分のみが切り替わり、合わせてURLも変更されています。

Barba.jsのインストール

npm$ npm i @barba/core 

prefetchの導入

npm$ npm i @barba/prefetch

jsファイルにインポート

script.jsimport barba from '@barba/core';
import barbaPrefetch from '@barba/prefetch';

barba.use(barbaPrefetch);

基本構造(HTML)

index.html<div data-barba="wrapper">
<div data-barba="container" data-barba-namespace="page">
// 更新部分
</div>
</div>

//遷移時アニメーション用の要素
<div class="mask"></div>

アニメーション追加

遷移時のアニメーションは追加モジュールでも実装できますが、
今回は独自のアニメーションを設定します。

画面遷移時のトリガーについて

before- 最初
beforeLeave- 現在のページを離れる直前
leave- 現在のページを離れる時
afterLeave- 現在のページを離れた直後
beforeEnter- 次のページを表示する直前
enter- 次のページを表示する時
afterEnter- 次のページが表示された直後
after- 最後

ページ遷移前後でアニメーション用のclassを付け外しします。

script.jslet mask = document.querySelector('.mask');
barba.init({
transitions: [
{
async leave() {
mask.classList.add('active');
await new Promise(resolve => {
return setTimeout(resolve, 1000);
});
},
afterEnter() {
mask.classList.remove('active');
}
}
]
});
style.scss$ease-cubic-bezier: cubic-bezier(.165, .84, .44, 1);
$color-lightgray: #f5f8fa;

.mask {
position: fixed;
z-index: 9999;
top: 0;
left: 0;
display: block;
visibility: hidden;
width: 100%;
height: 100%;
transition: transform 1s $ease-cubic-bezier;
transition-property: transform, visibility;
transform: rotateY(90deg);
transform-origin: right;
pointer-events: none;
background-color: $color-lightgray;

&.active{
visibility: visible;
transform: rotateY(0deg);
transform-origin: left;
pointer-events: auto;
}
}

画面遷移時に固有class書き換え

ページ固有のスタイルをあてたいので、bodyのclass書き換えます。

script.jsbarba.hooks.afterLeave((data) => {
var nextHtml = data.next.html;
var response = nextHtml.replace(/(<\/?)body( .+?)?>/gi, '$1notbody$2>', nextHtml)
var bodyClasses = $(response).filter('notbody').attr('class')
$('body').attr('class', bodyClasses);
});

同じurlの場合、ページ遷移させない

script.jsconst eventDelete = e => {
if (e.currentTarget.href === window.location.href) {
e.preventDefault()
e.stopPropagation()
return
}
}

const links = [...document.querySelectorAll('a[href]')]
links.forEach(link => {
link.addEventListener('click', e => {
eventDelete(e)
}, false)
})

遷移後スクロール値を0にする

非同期読み込みのためスクロール値が保存されているので、遷移後にリセットします。

script.jslet mask = document.querySelector('.mask');
barba.init({
transitions: [
{
beforeEnter() {
     //追記
const scrollElem = document.scrollingElement || document.documentElement
scrollElem.scrollTop = 0;

},
async leave() {
mask.classList.add('active');
await new Promise(resolve => {
return setTimeout(resolve, 1000);
});
},
afterEnter() {
mask.classList.remove('active');
}
}
]
});

最終的なコードはこちらです。

script.jsimport barba from '@barba/core';
import barbaPrefetch from '@barba/prefetch';

barba.use(barbaPrefetch);

// 同じurlの場合、ページ遷移をさせない
const eventDelete = e => {
if (e.currentTarget.href === window.location.href) {
e.preventDefault()
e.stopPropagation()
return
}
}
const links = [...document.querySelectorAll("a[href]")];
links.forEach(link => {
link.addEventListener(
"click",
e => {
eventDelete(e);
},
false
);
});

// 遷移時固有Class書き換え
barba.hooks.afterLeave((data) => {
var nextHtml = data.next.html;
var response = nextHtml.replace(/(<\/?)body( .+?)?>/gi, '$1notbody$2>', nextHtml)
var bodyClasses = $(response).filter('notbody').attr('class')
$('body').attr('class', bodyClasses);
});

// 画面遷移処理
let mask = document.querySelector(".mask");
barba.init({
transitions: [
{
beforeEnter() {
const scrollElem = document.scrollingElement || document.documentElement
scrollElem.scrollTop = 0;
//遷移後の処理追記
},
async leave() {
mask.classList.add('active');
await new Promise(resolve => {
return setTimeout(resolve, 1000);
});
},
afterEnter() {
mask.classList.remove('active');
}
}
]
});

まとめ

Pjaxは古い技術と思われがちですが、軽量のうえ導入コストも低くまだまだ使える技術だと思っています。

実際にBarba.jsを導入しているサイトのリンク集です。
https://barba.js.org/showcase/
参考にされてみてはいかがでしょうか。

それではまた!