barba.js v2で簡単に非同期画面遷移を取り入れてページ高速化!

barba.js v2で簡単に非同期画面遷移を取り入れてページ高速化!

barba.js v2って?

端的に言うと非同期画面遷移が簡単にできるライブラリ「Barba.js」のバージョン2がbarba.js v2です。このサイトでも使っていますが、デモも作ってみました。

barba.js DEMO

最近はREST API + フレームワークなどで簡単にサクサク見れるSPA(Single Page Application)を作れるようになりました。しかし通常のコーポレートサイトやブログメディアサイトなどにSEOを考えた上でサクッとSPAを取り入れることは難易度が高く、SSRなど余計なことを考え出すと工数と合わないので僕はなかなか手が出ません。(Vue.jsなどはSSRしなくてもGoogleがちゃんとレンダリングしてくれる説もありますが)

それでも快適にかっこよくシュバっとページ遷移したい!という思いで方法を探していると「Barba.js」にたどり着きました。弊社HPやこのサイトでも使っていますが、SPAを作成するより非常に少ない工数で簡単にアニメーション付きの非同期遷移ができます。もちろんSEOにも対応できます。

では使い方を見ていきます。

barba.js v2の使い方

公式のドキュメントはこちらです。
バージョン1.Xと2.Xがありますが、「Barba.js 使い方」などでググって出てくるものが大体1.Xです。今回は公式以外にあまり情報がない2.Xでの実装にチャレンジしてみようと思います。

barba.js v2のインストール

何はともあれまずはyarnですね。

yarn add -D @barba/core

Yarn

https://yarnpkg.com/ja/

もっとサクッといきたいって方はcdnもあるみたいです。

公式に掲載されているCDN
<script src="https://unpkg.com/@barba/core"></script>

JSDELIVRのCDN
  <script src="https://cdn.jsdelivr.net/npm/@barba/core@2.9.6/dist/barba.umd.min.js" crossOrigin></script>

どちらか1つ好きな方を読み込めばOK

どちらの方法で読み込んでも問題無く動作します。 ただunpkg.comはけっこうアクセス過多で落ちてる印象です。 CDNはそういうこともあるのでおすすめはちゃんとwebpackでバンドルして使う方法ですね。

barba.js v2の基本設定

ほとんど公式ドキュメントのまんまなのですが、英語で手順がわかりにくかったので僕のやった手順で進めていきます。

html

まずはhtmlの準備です。
DEMO1として以下のhtmlファイルを作ります。(htmlやbodyなど基本的な部分は省略)

<div data-barba="wrapper">
    <div data-barba="container" data-barba-namespace="demo-1">
     <a class="button" id="js-button" href="DEMO2へのリンク">GO TO DEMO2</a>
      <div class="bg" id="js-bg">
        <img src="背景画像">
      </div>
    </div>
</div>

ボタンと背景のみのシンプルなページを用意します。(CSSは省略)
ここでbarba.js v2の設定として必要な部分は以下です。

  • data-barba="wrapper" :非同期遷移する箇所のラッパーを指定
  • data-barba="container":実際に遷移して入れ替わるソース部分
  • data-barba-namespace="demo-1":遷移する箇所に名前空間(ここではdemo-1)を指定。

data-barba-namespaceについては今回は使いませんでした。アニメーションを遷移する先ごとに毎回変えたいといった場合にこちらを指定しておけば細かく管理できそうです。

htmlはこれだけでOKです。

あとはこのDEMO1をコピーし、DEMO2ファイルを作ります。ファイルはそれぞれDEMO1からはDEMO2、DEMO2からはDEMO1へaタグをリンクさせておきます。

JS

次にJSを書いて、barba.js v2を使えるようにします。
webpackなどを利用している場合はJS内でimport。

import barba from '@barba/core'

cdnの場合は先ほどのタグを読み込みます。

<script src="https://unpkg.com/@barba/core"></script>

これでBarba.jsを使えるようになりました。

次に設定を書いていきます。

barba.init({
  transitions: [
  {
    async leave({ current, next, trigger }) {
      //ページを離れる時のアニメーション
      const leave = await leaveAnimation(current);
      return leave;
    },
    afterLeave({ current, next, trigger }){
      //enter時の初期表示状態を設定
      $(next.container).find('#js-button').css('opacity', 0);
    },
    beforeEnter({ current, next, trigger }) {
      //headタグの中身を入れ替え
      replaceHeadTags(next);
    },
    enter({ current, next, trigger }) {
      //ページを表示する時のアニメーション
      enterAnimation(next);
    }
  }
  ]
});

内容を見ていきましょう。

barba.initでBarba.jsを呼び出します。
その際にoptionを指定できるのですが、今回はアニメーションを使った非同期遷移が目的ですので、使うのはtransitionオプションだけです。transitionオプションはページ遷移の際にいろんなタイミングで関数を実行できる便利なオプションです。

transitionオプションの種類は以下です。

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

今回使ったのはleaveafterLeavebeforeEnterenterの4つです。

leave

まずページを離れる時のフックです。

async leave({ current }) {
   //ページを離れる時のアニメーション
   const leave = await leaveAnimation(current);
   return leave;
},

引数のcurrentに遷移前のページ情報が入っていますのでそれをアニメーションに利用します。
遷移をシームレスにしたいので、アニメーションが完全に終了してから次のtransitionを実行するためにasync/awaitを使います。(公式ドキュメントの方にPromiseでもできる方法が掲載してあります)

アニメーションの内容は以下です。anime.jsを使っています。

/*
* ページ離脱アニメーション
* @param {Object} current 離脱するページ(前のページ)の情報
* @return Promise
*/
function leaveAnimation(current) {
  const animation = anime.timeline()
  .add({
    easing           : 'easeOutSine',
    targets          : current.container.querySelector('#js-button'),
    duration         : 300,
    opacity          : [1, 0]
  })
  .add({
    easing           : 'easeInOutExpo',
    targets          : current.container.querySelector('#js-bg'),
    duration         : 600,
    opacity          : [1, 0],
    scale            : [1, 1.1]
  }, '-=300');
  return animation.finished;
}

ボタンをフェードアウトした後に背景をフェードアウトするアニメーションです。ポイントは戻り値をPromiseにすることです。これはanime.jsで簡単にできます。

anime.jsの基本的な使い方はこちら。バージョンは3.xを使っています。

anime.js

https://animejs.com/documentation/
enter

順番が前後しますが、アニメーションを先に設定しておきたいので次のページを表示する時のフックを見ていきます。

enter({ current, next, trigger }) {
  //ページを表示する時のアニメーション
  enterAnimation(next);
}

こちらはasync/awaitは付けません。
引数nextに遷移後のページ情報が入っているのでそちらをアニメーションします。アニメーションは先ほどのleaveAnimationの逆を設定します。

/*
* ページ表示アニメーション
* @param {Object} next 表示するページ(次のページ)の要素
* @return Promise
*/
function enterAnimation(next) {
  const animation = anime.timeline()
  .add({
    easing           : 'easeInSine',
    targets          : next.container.querySelector('#js-button'),
    duration         : 300,
    opacity          : [0, 1],
    translateY       : ['15px', 0]
  })
  .add({
    easing           : 'easeInOutExpo',
    targets          : next.container.querySelector('#js-bg'),
    duration         : 1000,
    opacity          : [0, 1],
    scale            : [1.1, 1]
  }, '-=300');
  return animation.finished;
}
afterLeave

次に現在のページを離れた直後のフックを設定します。こちらもasync/awaitは不要です。

afterLeave({ current, next, trigger }){
  //enter時の初期表示状態を設定
  $(next.container).find('#js-button').css('opacity', 0);
},

先ほどのleaveAnimationenterAnimationだけでは遷移が重なるタイミングで若干のチラつきが出てしまいます。そのため、チラつきの起きるボタンの部分に初期表示状態として事前にCSSを設定しておきます。

ここまででシームレスなアニメーションで遷移できるようになりました。

beforeEnter

こちらはアニメーションとは関係ありませんが、SEO対策です。
ページ遷移の際にdata-barba="container"を指定した要素の中身が変わりますが、SEOに必要なmetaタグなどはそのままでは変わりませんので、このフックで変更処理をはさみます。

beforeEnter({ current, next, trigger }) {
  //headタグの中身を入れ替え
  replaceHeadTags(next);
},

metaタグを入れ替える際に$.parseHTMLを使いたいのでjQueryを読み込んでおきます。

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

jQueryが嫌いな人はがんばってネイティブで書きましょう。(今回はjQueryを使ったのでネイティブの記述は省略します)

/*
* <head>内のタグを入れ替える
* @param {Object} target 表示するページ(次のページ)の要素
*
*/
function replaceHeadTags(target) {
  const $newPageHead = $('<head />').html(
    $.parseHTML(
      target.html.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0],
      document,
      true
      )
    );
  const headTags = [
  "meta[name='robots']",
  "meta[name='keywords']",
  "meta[name='description']",
  "meta[property^='og']",
  "meta[name^='twitter']",
  "meta[itemprop]",
  "link[rel='next']",
  "link[rel='prev']",
  "link[rel='alternate']",
  "link[rel='canonical']",
  "script[type='application/ld+json']"
  ].join(',');
  // タグを削除
  $('head').find(headTags).remove();
  // タグを追加
  $newPageHead.find(headTags).appendTo('head');

  //Googleアナリティクスに送信
  if (typeof ga === 'function') {
    ga('send', 'pageview', location.pathname);
  }
}

metaタグを入れ替えるついでにGoogleアナリティクスへ情報を送っておけばPVも計測できます。

非同期遷移の場合は最初の1ページ目以外はアナリティクスへ送信する処理をJS内に書いておかないと正確な計測ができませんので注意です。

補足

サンプルで説明するためにあえてafterLeavebeforeEnterを使いましたが、基本的には下記のようにleaveenterだけで事足りるかと思います。

barba.init({
  transitions: [
  {
    async leave({ current }) {
      const leave = await leaveAnimation(current);
      return leave;
    },
    enter({ current, next, trigger }) {
      $(next.container).find('#js-button').css('opacity', 0);
      replaceHeadTags(next);
      enterAnimation(next);
    }
  }
  ]
});

async/awaitに関してはleaveの時だけアニメーションの終了を待ってあげないと、アニメーション途中かどうか関係なく通信が終わったタイミングでページ遷移してしまい、enterが発火してしまうので使っています。

以上!ここまでで完成です!

barba.js v2のDEMO

barba.js v2 DEMO

画像とボタンだけの質素なページですが、簡単なアニメーションをかけて遷移するだけでちょっとリッチになりました。

追記

コンソールに出る文字列を消す方法

ライブラリをそのまま使うとコンソールに使っているプラグインのバージョンが勝手にログとして出てしまいます。

[@barba/prefetch]  2.1.5
[@barba/core]  2.3.12

これを消す方法はbarba.jsのLogger ClassからlogLevelを設定しなければならないようです。

import barba from '@barba/core'
import barbaPrefetch from '@barba/prefetch'

//prefetchのログを消すには使う前にログレベルを0に設定する
barba.Logger.setLevel(0);
barba.use(barbaPrefetch);

//coreのログを消すには初期化の際にオプションでログレベルを0に設定する
barba.init({
  logLevel : 0,
  ...
});

これで必要のないログは消せます。
ログについては以下を参考にしました。

公式ドキュメントのlogLevelの部分

https://barba.js.org/docs/advanced/recipes/#logLevel

APIドキュメント Logger Class について

https://barba.js.org/api/modules/core_modules_logger.html

特定のリンクで非同期画面遷移したくない時

このブログもそうですが、目次などでページ内リンクを設定している場合にそのリンクもbarba.jsが反応してしまいます。

公式ドキュメントによると非同期遷移させない方法こちら。

import barba from '@barba/core';

barba.init({
  //ここに無効にする条件を書く
  prevent: ({ el }) => el.getAttribute('href').slice(0,1) === '#'
});

オプションのpreventがtrueの場合にbarba.jsを無効化します。
ここではページ内リンクを無効化したいので#が先頭にあるリンクである時と指定しました。これでhref="#target"のような指定がしてあるリンクは非同期遷移せずにページ内リンクとして動いてくれます。

もっと手軽な方法として以下も紹介されています。

<!-- htmlでリンクに直接属性を付けてリンクの非同期遷移を無効化 -->
<a href="#target" data-barba-prevent>ページ内リンク</a>

<!-- 親要素に属性をつけて子要素すべてのリンクの非同期遷移を無効化 -->
<div data-barba-prevent="all">
  <a href="#target1">ページ内リンク1</a>
  <a href="#target2">ページ内リンク2</a>
  <a href="#target3">ページ内リンク3</a>
</div>

data-barba-preventという属性をhtmlで直接要素に追記することで非同期遷移を無効化できます。条件ではなく特定のリンクを個別に無効化したいならこちらを利用するのが簡単そうです。また、data-barba-prevent="all"と親属性に付けることで全部まとめて無効化できるので、こちらは特定のエリアをまとめて無効化する場合に便利です。

公式ドキュメントのpreventの部分を参考にしました。

https://barba.js.org/docs/advanced/strategies/#prevent

ページ内リンクを適用する方法

前提知識としてhtmlのテクニックをざっと説明します。(初歩的なものなので知っている人は読み飛ばしてください。)

まずは1つ目。htmlの特定のコンテンツにid属性を振っておいて、aタグのhref属性にそのidを書いてリンクを踏むと同一ページ内でそのコンテンツまでジャンプできます。

<a href="#target">コンテンツまでジャンプ</a>

<div id="target">
着地!
</div>

そして2つ目。ページ内だけでなく、URLの後ろにhttps://notes.sharesl.net/#targetのような形でidを指定すると遷移先のページでもそのidが付いたコンテンツまでジャンプした状態で表示されます。

これがbarba.jsを使うと機能しなくなり、自分で設定する必要があります。
その方法を簡単にメモ。

まず特定のリンクで非同期画面遷移したくない時でハッシュが先頭にあるリンクの場合はbarba.jsを発火させなくできるので、1つ目はこれで解決します。ですが2つ目の場合は、barba.jsで遷移した先で画面をスクロールしておく必要があるので、このままでは使えません。

具体的には、transitionオプションでURLのハッシュ(#)以降の値を取得し、遷移するタイミングでスクロールさせます。

enterのタイミングでOKなのですが、注意点が2つ。

  • 完全にDOMが描画されたタイミングで画面をスクロールさせないと位置がずれる
  • 遷移アニメーションと違和感なく連動させる

これを踏まえてDEMOを作りました。

ページ内リンクを適用したDEMO

実際のコードでのポイントを見ていきます。htmlは簡単なものなのでここでは省きます。

/*
* ページ表示アニメーション
* @param {Object} next 表示するページ(次のページ)の要素
* @return Promise
*/
function enterAnimation(next) {
  const animation = anime.timeline()
  .add({
    easing           : 'easeInSine',
    targets          : next.container.querySelector('#js-button'),
    duration         : 300,
    opacity          : [0, 1],
    translateY       : ['15px', 0]
  })
  .add({
    easing           : 'easeInOutExpo',
    targets          : next.container.querySelector('#js-content'),
    duration         : 1000,
    opacity          : [0, 1],
    begin            : () => {
      scrollTo(next);
    }
  }, '-=300');
  return animation.finished;
}

まず完全にDOMが描画されたタイミングで画面をスクロールさせるために、アニメーションが始まったタイミングでスクロールさせます。anime.jsでコンテンツをフェードインするアニメーションにbeginオプションを追加します。

/*
* スクロールアニメーション
* @param {Object} next 表示するページ(次のページ)の要素
* @return Promise
*/
function scrollTo(next) {
  if(!(next)){
    return;
  }
  const $target = $(`#${next.url.hash}`, next.container);
  const targetPosition = $target.offset().top;
  return anime({
    targets     : 'html,body',
    scrollTop   : targetPosition,
    duration    : 600,
    easing      : 'easeInOutExpo',
    elasticity  : 300,
  });
}

実際のスクロール処理はこちら。引数のnextオブジェクトはbarba.jsでenter時に引数として取得できる情報をそのまま渡しています。URL情報が含まれているので、その中のnext.url.hashでURLに付いている#以降の文字列を取得し、それを利用します。

あとは煮るなり焼くなり好きにという感じですが、ここではもともとの遷移アニメーションと違和感なく連動させることが大事なので、anime.jsを使ってスクロールにもアニメーションを付けて、遷移アニメーションと少し重なるようにします。duration : 600の部分を200ぐらいにすると遷移時にスクロールし終わった状態になりますが、DEMO2ではあえてアニメーションを重ねて動きを加えてみました。

CSSでアニメーション遷移する方法

使う機会がなかったので書いていませんでしたが、取り入れるのが簡単なこちらの方法も追記しておきます。

CSSでアニメーション遷移するDEMO

まずJSで追加の指定をします。

unpkg.comのCDN
<script src="https://unpkg.com/@barba/core" crossorigin></script>
<script src="https://unpkg.com/@barba/css@2.1.15/dist/barba-css.umd.js" crossorigin></script>

JSDELIVRのCDN
<script src="https://cdn.jsdelivr.net/npm/@barba/core@2.9.6/dist/barba.umd.min.js" crossOrigin></script>
<script src="https://cdn.jsdelivr.net/npm/@barba/css@2.1.15/dist/barba-css.umd.min.js" crossOrigin></script>

どちらかを読み込んで、以下を追記

<script>
  barba.use(barbaCss);
  barba.init();
</script>

上記はCDNを使った方法です。
公式の@barba/cssの使い方ではimportを使う形式になっているので、その場合はYarnやnpmでインストールして使います。

yarn add -d @barba/css

JS内でインポート。

import barba from '@barba/core';
import barbaCss from '@barba/css';

barba.use(barbaCss);
barba.init();

barba.jsで指定するのはこれだけです。
ここではCSSアニメーションを指定するだけなので、barba.init()のオプションは省略しましたが、headタグ内を書き換える場合は、前述の「head内のタグを入れ替える」処理を追記すればOKです。

次にCSSを書きます。

//コンテナーにイージングを設定
.barba-leave-active,
.barba-enter-active {
  transition: .3s ease-out;
}

//ページを離れる時の初期状態
.barba-leave {
  opacity: 1;
  visibility: visible;
  transform: none;
}

//ページ表示時の初期状態
.barba-enter {
  opacity: 0;
  visibility: hidden;
  transform: translateY(10vh);
}

//ページを離れるアニメーション
.barba-leave-to {
  opacity: 0;
  visibility: hidden;
  transform: translateY(10vh);
}

//ページ表示時のアニメーション
.barba-enter-to {
  opacity: 1;
  visibility: visible;
  transform: none;
}

公式の@barba/cssの使い方のページに、Vue.jsのTransitionに強く影響を受けているというような内容が書いてあるので、Vue.js使いの人は馴染みやすいのかなと思います。

具体的にはdata-barba="container"に、遷移時の各タイミングで自動的にclassが付くのでそこにCSSアニメーションを書きます。

.barba-leaveから.barba-leave-toがページを離れる時のアニメーション、.barba-enter から.barba-enter-toがページを表示する時のアニメーションです。 .barba-leave-active.barba-enter-activeは遷移それぞれの遷移が実行されている間に付くので、CSSトランジションを使う場合はこちらにtransitionを指定します。

今回のDEMOは、ページ離脱時に下にふわっと消えて表示するときに下からふわっと出てくる感じにしてみました。

ブラウザ対応について(IE11)

barba.js v2はChrome、Firefox、Edge、Safari、Operaといったモダンブラウザではpolyfillなしで動きます。
ただし内部的にPromiseなどが使われているため、IE11ではpolyfillが必要です。

<script src="https://polyfill.io/v3/polyfill.min.js?features=default%2CArray.prototype.find%2CIntersectionObserver" crossorigin></script>

IE11対応される場合はこちらを追記しておきましょう。

まとめ

今回はbarba.js v2の使い方を簡単にまとめました。
慣れないとちょっとクセがあるかもしれませんが、それにしても簡単。
状態管理考えたり小難しい設計思想でわざわざSPAを作らなくてもいいですし、サイトにちょっと違いを出したい時にめっちゃ使えるのでとってもオススメです!ぜひ試してみてください!