Barba.js v2とGSAPでページ遷移アニメーション

Blog

はじめまして。エンジニアのotaniです。
弊社ブログでも以前ご紹介した、シームレスなページ遷移を導入できるJSライブラリのBarba.jsですが
バージョンがv2になって記述の仕方が変わっていたので導入方法の紹介と簡単なデモを作成してみました。

Barba.js v2とは

WebサイトにSPAのような高速でシームレスなページ遷移を導入できるJSライブラリです。しかも非常に軽量で、7KBしかありません。
もともとBarba.jsがありましたがv2にアップデートされ公式サイトも別ドメインになりました。

注意点

最新のモダンブラウザ(Chrome、Firefox、Edge、Safari、Opera)では問題なく動きますがIE対応するにはポリフィルが必要なようです。詳しくは公式ドキュメントをご覧ください。

DEMO

こちらが今回作成したDEMOです。
遷移時のアニメーションにはGSAPを利用しました。

インストール

  • npm | yarn
# npm
npm install @barba/core

# yarn
yarn add @barba/core

インストールしたらモジュールをインポートし、barba.init() で初期化します。※適宜webpack等でコンパイルしてください

import barba from '@barba/core';

barba.init({
  // ...
});
  • CDN
<!-- unpkg -->
<script src="https://unpkg.com/@barba/core"></script>
<!-- or -->
<!-- jsdelivr -->
<script src="https://cdn.jsdelivr.net/npm/@barba/core"></script>

同様に初期化します。

<script>
  barba.init({
    // ...
  })
</script>

マークアップ

v1同様ラッパーとコンテナを設置します。v2ではdata属性になりました。
index.htmlに下記のように記述します。

<body data-barba="wrapper">
    <div class="common-transition">
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
    </div>
    <div id="common-wrapper" class="common-wrapper">
        <div class="container" data-barba="container" data-barba-namespace="home">
            ...
        </div>
    </div>
...
</body>

ラッパー

大枠にdata-barba="wrapper"を設置します。bodyでなくともdivでもsectionでも大丈夫ですが、必ずdata-barba="container"をラップする必要があります。

コンテナ

data-barba="container"が遷移時にbarbaによって更新されます。したがって、コンテナの外側にあるものはページを遷移しても更新されません。

名前空間

data-barba-namespace="home"が名前空間です。この属性の値で各ページに固有の名前を定義することができます。

上記のラッパーとコンテナを各ページに設置しbarba.init()を実行すれば、これだけでページを遷移するたびにコンテナの内部が更新されるようになります。

<div class="common-transition"> は、今回アニメーションさせている目隠しの帯です。これは共通で使うのでコンテナの外に出しておきます。

遷移後のHTML

これでindex.htmlからabout.htmlにページ遷移すると、HTMLが下記のように更新されます。
about.htmldata-barba-namespace="about"と記述しておくことで、コンテナの内容とともにdata-barba-namespaceの値も更新されます。

<body data-barba="wrapper">
    <div class="common-transition">
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
    </div>
    <div id="common-wrapper" class="common-wrapper">
        <div class="container" data-barba="container" data-barba-namespace="about">
            ...
        </div>
    </div>
...
</body>

遷移アニメーション

アニメーションには公式でもおすすめしているGSAPを利用しました。
先に動きの部分を作っておきます。

// 遅延用 引数の分だけ処理を遅らせる
function delay (n) {
  n = n || 2000;
  return new Promise((done) => {
    setTimeout(() => {
      done();
    }, n);
  });
}

// 目隠しの帯のアニメーション
function pageTransition () {
  const transitionItem = document.querySelectorAll('.common-transition .item');
  const transitionContainer = document.querySelector('.common-transition');
  const tl = gsap.timeline();
  tl.to(transitionItem, {
    duration: 0.4,
    scaleY: 1,
    transformOrigin: 'bottom left',
    stagger: 0.1,
    ease: 'Expo.easeInOut'
  });
  tl.to(transitionContainer, {
    duration: 1,
    y: '-100%',
    ease: 'Expo.easeInOut'
  });
  tl.to(transitionContainer, {
    duration: 0,
    y: 0
  });
  tl.to(transitionItem, {
    duration: 0,
    scaleY: 0,
    scaleX: 1
  });
}

// ページを離れる時の上に消える動作
function leaveAnimation () {
  const tl = gsap.timeline();
  tl.to('.container', {
    duration: 1,
    y: -50,
    opacity: 0,
    ease: 'Quart.easeOut'
  });
}

// ページに入った時の下から出てくる動作
function enterAnimation () {
  const tl = gsap.timeline();
  tl.from('.container', {
    duration: 1,
    y: 50,
    opacity: 0,
    ease: 'Quart.easeOut'
  });
}

アニメーションができたらbarba.init() の中にbarbaの設定をしていきます。

transitions

遷移時の動作はtransitionsプロパティに記述します。

// barba設定
barba.init({
  transitions: [
    {
      async leave (data) {
        const done = this.async();
        leaveAnimation();
        pageTransition();
        await delay(1000);
        done();
      },
      async enter (data) {
        await delay(600);
        enterAnimation();
      }
    }
  ],
  // ...
});

ページを離れる時に実行されるleave()と、ページに入った時に実行されるenter()が基本的なトランジションのフックになっています。
今回は使っていませんが、他にもbeforeLeaveやafterEnterなど細かいタイミングが用意されています。

transitionsとviewsで使えるフックの一覧

また、アニメーションの動きを待ってから遷移を実行したいので async / await を使って完了を待ち、delay()で遅延時間を調整しています。

views

namespaceを使ったページごとに固有の設定は、viewsプロパティに記述します。

views: [
    {
      namespace: 'about',
      beforeEnter (data) {
        document.querySelector('#common-wrapper').classList.add('-about');
      },
      afterLeave (data) {
        document.querySelector('#common-wrapper').classList.remove('-about');
      }
    }
  ]

ここではnamespace: 'about' のコンテナに更新された時、コンテナの外側にある要素にクラスを付ける処理をしてみました。
このviewsですが、transitionsとは使えるフックが異なるので注意が必要です。

head内を書き換え、Googleアナリティクスに情報を送る

v1同様デフォルトではtitle以外のheadタグ内のmeta情報の更新や、GA用に別途設定が必要になります。

// titleタグ以外のmetaタグの情報の書き換えを行う
const replaceHeadTags = target => {
  const head = document.head;
  const targetHead = target.html.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0];
  const newPageHead = document.createElement('head');
  newPageHead.innerHTML = targetHead;
  const removeHeadTags = [
    "meta[name='keywords']",
    "meta[name='description']",
    "meta[property^='fb']",
    "meta[property^='og']",
    "meta[name^='twitter']",
    "meta[name='robots']",
    'meta[itemprop]',
    'link[itemprop]',
    "link[rel='prev']",
    "link[rel='next']",
    "link[rel='canonical']"
  ].join(',');
  const headTags = [...head.querySelectorAll(removeHeadTags)];
  headTags.forEach(item => {
    head.removeChild(item);
  });
  const newHeadTags = [...newPageHead.querySelectorAll(removeHeadTags)];
  newHeadTags.forEach(item => {
    head.appendChild(item);
  });
};

// Googleアナリティクスに情報を送る
barba.hooks.after(() => {
  ga('set', 'page', window.location.pathname);
  ga('send', 'pageview');
});

head更新用の関数replaceHeadTagsは先に定義しておき、のちほどtransitionsで呼び出します。

最終的なJS

最後に、本記事のJSをまとめると下記のようになります。

import barba from '@barba/core';
import gsap from 'gsap';

// titleタグ以外のmetaタグの情報の書き換えを行う
const replaceHeadTags = target => {
  const head = document.head;
  const targetHead = target.html.match(/<head[^>]*>([\s\S.]*)<\/head>/i)[0];
  const newPageHead = document.createElement('head');
  newPageHead.innerHTML = targetHead;
  const removeHeadTags = [
    "meta[name='keywords']",
    "meta[name='description']",
    "meta[property^='fb']",
    "meta[property^='og']",
    "meta[name^='twitter']",
    "meta[name='robots']",
    'meta[itemprop]',
    'link[itemprop]',
    "link[rel='prev']",
    "link[rel='next']",
    "link[rel='canonical']"
  ].join(',');
  const headTags = [...head.querySelectorAll(removeHeadTags)];
  headTags.forEach(item => {
    head.removeChild(item);
  });
  const newHeadTags = [...newPageHead.querySelectorAll(removeHeadTags)];
  newHeadTags.forEach(item => {
    head.appendChild(item);
  });
};

// Googleアナリティクスに情報を送る
barba.hooks.after(() => {
  ga('set', 'page', window.location.pathname);
  ga('send', 'pageview');
});

// アニメーション
function delay (n) {
n = n || 2000;
return new Promise((done) => {
    setTimeout(() => {
      done();
    }, n);
  });
}

function pageTransition () {
  const transitionItem = document.querySelectorAll('.common-transition .item');
  const transitionContainer = document.querySelector('.common-transition');
  const tl = gsap.timeline();
  tl.to(transitionItem, {
    duration: 0.4,
    scaleY: 1,
    transformOrigin: 'bottom left',
    stagger: 0.1,
    ease: 'Expo.easeInOut'
  });
  tl.to(transitionContainer, {
    duration: 1,
    y: '-100%',
    ease: 'Expo.easeInOut'
  });
  tl.to(transitionContainer, {
    duration: 0,
    y: 0
  });
  tl.to(transitionItem, {
    duration: 0,
    scaleY: 0,
    scaleX: 1
  });
}

function leaveAnimation () {
  const tl = gsap.timeline();
  tl.to('.container', {
    duration: 1,
    y: -50,
    opacity: 0,
    ease: 'Quart.easeOut'
  });
}

function enterAnimation () {
  const tl = gsap.timeline();
  tl.from('.container', {
    duration: 1,
    y: 50,
    opacity: 0,
    ease: 'Quart.easeOut'
  });
}

// barba設定
barba.init({
  sync: true,
  transitions: [
    {
      async leave (data) {
        const done = this.async();
        leaveAnimation();
        pageTransition();
        await delay(1000);
        done();
      },
      beforeEnter ({ next }) {
        replaceHeadTags(next);
      },
      async enter (data) {
        await delay(600);
        enterAnimation();
      }
    }
  ],
  views: [
    {
      namespace: 'about',
      beforeEnter (data) {
        document.querySelector('#common-wrapper').classList.add('-about');
      },
      afterLeave (data) {
        document.querySelector('#common-wrapper').classList.remove('-about');
      }
    }
  ]
});

まとめ

以上、簡単にではありますがBarba.js v2とGSAPを使ったページ遷移アニメーションの紹介でした。
v1同様コンテナ外の情報の更新などもろもろ設定は必要になりますが、うまく使えれば表現の幅が大きく広がるのではないでしょうか。

参考サイト