Backpax.jsを使ってパララックスユニットを作ってみよう

みなさんは背景画像にパララックス効果をつけたい場合にどのライブラリを選定して実装していますか?意外と背景画像にパララックスエフェクトをつけるのは難しいと思います。なぜなら、背景画像の大きさに応じてパララックスのスピードを調整しないといけないし、スマートフォンサイズの時は別画像にしてパララックスさせたい場合もあります。 一つのJavaScriptを利用するだけではうまくいかず、さらに高度なカスタマイズを求められることがほとんどです。

Backpax.jsとは

Backpax.js とは弊社 アップルップル が開発したパラッラックス用のJavaScriptライブラリーでGithubに公開しています。


Backpax.jsの特徴として以下の点があります。

  • HTMLベースで簡単に背景パララックスが実装できる
  • 一つの背景に対していくつでもブレイクポイントに応じた画像が登録できる
  • パララックススピードの自動調整

HTMLベースで簡単に背景画像が実装できる

実際にBackpax.jsでは以下のようにHTMLを記述します。background-image:url(/path)を記述する代わりに以下のようにdata-imgを背景画像を設定したい要素に記述します。さらにブレイクポイントごとに画像を変えたい場合はdata-img-768などのように768px以下の時の画像を設置したりできます。data-img-以降の数字は任意のもので大丈夫です。data-img-400data-img-1800など、好きに数字を指定することができます。

<div class="js-parallax" 
  data-img="./path/to/default-image"
  data-img-768="./path/to/image-which-size-is-smaller-than-768"
  data-img-1024="./path/to/image-which-size-is-smaller-than-1024"
>
  <p>ここには普通にコンテンツが入ります。</p>
</div>

JavaScriptは以下のように記述します。非常に簡単です。

document.addEventListener('DOMContentLoaded', function() {
  new Backpax('.js-parallax');
});

パララックススピードの自動調整

また、画像の高さによってパララックススピードが自動調整されます。表示されるエリアに対して画像が高い場合はパララックススピードが早くなりますし、逆に表示エリアに対して画像の高さが変わらない場合はパララックススピードが遅くなります。というのも背景画像を上から下まですべて表示させたいという意図があります。

デモ

以下がBackpax.jsの実際のデモになります。スクロールすると背景がスクロールスピードより遅く移動しているのがわかるかと思います。

Demo

パララックスユニットを作ってみよう

さて、Backpax.jsがどのようなJavaScriptかお分かりになったと思いますので、実際にパララックスユニットを作成していきます。

カスタムユニットを利用する

テキストユニットではテキストの入力欄が1つ、画像ユニットであれば画像を1枚アップできるような単機能なユニットになりますが、カスタムユニットを利用するとユニットに自由にカスタムフィールドやカスタムフィールドグループを設定することができます。 今回はブレイクポイントごとに背景画像を設定したいのでカスタムフィールドグループをユニットで登録できるようにします。

管理ページの実装

カスタムユニットは、少しだけカスタムフィールドと記述が違いますので、カスタムフィールドメーカー を利用する際には、カスタムユニット(フィールドグループ)を選択してください。以下は実際に利用するカスタムユニットのサンプルになります。 ソースコードは /themes/*利用テーマ*/admin/entry/unit/extend.html に貼り付けてください。 パララックス内の記事に対するカスタムフィールドは Ver.2.11から導入されるPaperEditorを利用したものになりますのでここは利用に応じて適宜カスタムフィールドを書き換えてください。

<!-- BEGIN custom_parallax -->
<table class="acms-admin-table-admin-edit">
  <tr>
    <th>パララックス内の記事</th>
    <td>
      <div class="js-expand js-acms-expand">
        <div class="js-acms-expand-inner">
          <button class="js-expand-btn js-acms-expand-btn" type="button">
            <i class="acms-admin-icon acms-admin-icon-expand-arrow js-expand-icon"></i>
          </button>
          <div class="js-paper-editor" data-heading-start="2" data-heading-end="3">
            <div class="js-paper-editor-edit"></div>
            <input type="hidden" class="js-paper-editor-body" name="parallax_contents{id}" value="{parallax_contents@html}" />
            <input type="hidden" name="unit{id}[]" value="parallax_contents{id}" />
            <input type="hidden" name="parallax_contents{id}:extension" value="paper-editor" />
          </div>
        </div>
      </div>
    </td>
  </tr>
</table>
<h2 class="acms-admin-admin-title2">パララックスグループ</h2>
<table class="js-fieldgroup-sortable adminTable acms-admin-table-admin-edit">
  <thead class="acms-admin-hide-sp">
    <tr>
      <th class="acms-admin-table-left acms-admin-admin-config-table-item-handle"> </th>
      <th class="acms-admin-table-left">ブレイクポイント</th>
      <th class="acms-admin-table-left">背景画像</th>
      <th class="acms-admin-table-left acms-admin-admin-config-table-action">削除</th>
    </tr>
  </thead>
  <tbody>
    <!-- BEGIN parallax_group:loop -->
    <tr class="sortable-item">
      <td class="item-handle acms-admin-table-nowrap">
        <i class="acms-admin-icon-sort"></i>
      </td>
      <td>
        <input type="text" name="parallax_breakpoint{id}[]" value="{parallax_breakpoint}" class="acms-admin-form-width-full" />
      </td>
      <td class="js-media-field">
        <div class="js-droparea" data-thumbnail="{parallax_image@thumbnail}" data-type="image" style="width:200px"></div>
        <p class="js-text acms-admin-text-danger" style="display:none">許可されていないファイルのため挿入できません。</p>
        <div class="acms-admin-margin-top-mini">
          <button type="button" class="js-insert acms-admin-btn" data-type="image">選択</button>
        </div>
        <input type="hidden" name="parallax_image{id}[]" value="{parallax_image}" class="js-value" />
      </td>
      <td class="acms-admin-table-nowrap">
        <input type="button" class="item-delete acms-admin-btn-admin acms-admin-btn-admin-danger" value="削除" />
      </td>
    </tr>
    <!-- END parallax_group:loop -->
    <tr class="sortable-item item-template">
      <td class="item-handle acms-admin-table-nowrap">
        <i class="acms-admin-icon-sort"></i>
      </td>
      <td>
        <input type="text" name="parallax_breakpoint{id}[]" value="" class="acms-admin-form-width-full" />
      </td>
      <td class="js-media-field">
        <div class="js-droparea" data-type="image" style="width:200px"></div>
        <p class="js-text acms-admin-text-danger" style="display:none">許可されていないファイルのため挿入できません。</p>
        <div class="acms-admin-margin-top-mini">
          <button type="button" class="js-insert acms-admin-btn" data-type="image">選択</button>
        </div>
        <input type="hidden" name="parallax_image{id}[]" value="" class="js-value" />
      </td>
      <td class="acms-admin-table-nowrap">
        <input type="button" class="item-delete acms-admin-btn-admin acms-admin-btn-admin-danger" value="削除" />
      </td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <td colSpan="4">
        <input type="button" class="item-insert acms-admin-btn-admin" value="追加" />
      </td>
    </tr>
  </tfoot>
</table>
<input type="hidden" name="@parallax_group{id}[]" value="parallax_breakpoint{id}" />
<input type="hidden" name="unit{id}[]" value="parallax_breakpoint{id}" />
<input type="hidden" name="parallax_image{id}:extension" value="media" />
<input type="hidden" name="@parallax_group{id}[]" value="parallax_image{id}" />
<input type="hidden" name="unit{id}[]" value="parallax_image{id}" />
<input type="hidden" name="unit{id}[]" value="@parallax_group{id}" />
<!-- END custom_parallax -->

次にa-blog cmsの管理画面でパララックスユニットの登録をします。 管理ページ > コンフィグ > 編集設定 > ユニット追加ボタン に移動し、新しいユニットボタンを追加します。ユニット追加ボタンの [追加] をクリック、「モード」は 拡張 を選択し、上記のコード に書かれているものと同様の custom_parallax と入力、「ラベル」は利用者に分かるようなラベルを設定します。今回の場合、パララックスのように記述するといいでしょう。


ユニット追加ボタンの設定ができたら、管理ページ > コンフィグ > ユニット設定 を開いてください。ユニット設定の一番下に「パララックス」が増えていますので、[パララックス] ボタンをクリックしてください。そうする事で以下のようにグラフユニットが1つ追加されます。


表示側の実装

実際に作ったHTMLを表示するには以下の場所にHTMLを記述します。 /themes/*利用テーマ*/include/unit/extend.html また、CSSについては言及しませんのでサイトに応じて適宜記述しましょう。

<!-- BEGIN unit#custom_parallax -->
<!-- BEGIN_SetRendered id="js-parallax" -->
<script src="https://cdn.jsdelivr.net/npm/backpax@1.0.2/bundle/backpax.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
  new Backpax('.js-parallax');
});
</script>
<!-- END SetRendered -->

<div class="js-parallax" <!-- BEGIN parallax_group:loop -->data-img-{parallax_breakpoint}="{parallax_image}" <!-- END parallax_group:loop -->>
{parallax_contents@html}
</div>
<!-- END unit#custom_parallax -->

JavaScriptの読み込み

/themes/*利用テーマ*/include/head/js.html

標準のテーマであれば js.htmlがありますが、無い場合には タグのどこかに、下記の1行を追加ください。この記述をheadタグ内に記述することで、エントリーで何回パララックスユニットが追加されていたとしても、JavaScriptが実行される回数が一回だけになるのでパフォーマンス的におすすめです。

<!-- GET_Rendered id="js-parallax" -->

以上になります。エントリー記事にアクセントをつけたい場合やクライアントからパララックス効果を求められた場合はぜひ Backpax.js を思い出していただければとおもいます。

a-blog cmsで使っているJavaScriptのファイルサイズを減量した話

a-blog cmsの主にフロントエンドを担当する堀です。今回はかなりニッチな話をします。a-blog cmsのJavaScriptのサイズを削減した話です。

経緯

a-blog cmsでは、Ver.2.8以降、モダンなフロントエンド開発環境にするべく、WebpackやBabel、Reactなどの整備をスタートしました。同時に独自のモジュールシステムでインポートしていたjsを一部、JavaScriptのimport文に切り替えたりしました。

Webpackに切り替えたことによってnpmのエコシステムに乗っかり以下のようにフロントエンドの力が試されるリッチな機能を比較的短期間で作れるようになりました。

  • 2.8の時は、LiteEditorやナビゲーションモジュール、SmartPhoto、クイックサーチ機能
  • 2.9の時にはプレビュー機能、管理メニューのカスタマイズ機能
  • 2.10ではメディア機能

ただ、2.10の時期になるとnpm、Webpackに頼りすぎたためその歪みもでてきました。

フロントで使うindex.jsのbundleサイズが52KB(非圧縮時95KB)、管理画面で使うadmin.jsが156KB(非圧縮時 269KB)なのに対し、フロントとindex.jsとadmin.jsが共通で使うvendor.chunk.jsも967KB(非圧縮時2.16MB)ありました。

そのため、CMSにログインしているユーザーの場合だと、画面が開いた時点でだいたい1MB近くのJavaScriptを読み込むことになってしまっていました。

これはパフォーマンス的にも非常によろしくないので、もう少しバンドルサイズを小さくしようという話になりました。

JavaScriptのファイルサイズ削減は画像などのファイルサイズ削減よりも重くパフォーマンスに影響します。ファイルサイズを減らすことによってページがロードされてから動作可能になるまでの時間が削減できます。

どのように削減したか

ではどのように削減したか紹介していきます。

  1. Webpack Bundle Analyzerの活用
  2. core-jsの見直し
  3. dynamic importの積極利用
  4. CacheGroupの利用

1. Webpack Bundle Analyzerの利用

まずはnpmから利用しているパッケージの何が大きな割合をしめているか把握したかったので WebpackBundleAnalyzerというWebpackのプラグインを利用しました。以下のような結果になりました。このようにvendor.chunk.jsが非圧縮時2MBという結果でした。これはa-blog cmsのフロント画面で、組み込みjsを利用する際、または管理者の場合は必ず読み込まれるJavaScriptなので、まずはこのvendor.chunk.jsで使わないであろうモジュールを削ぎ落として減量する作業が必要でした。



2. core-jsの見直し

まずはこの部分の対策からスタートしました。



core-jsというライブラリで、a-blog cmsはIE11まで対応しているのでこのライブラリの導入がマストでした。 core-jsは、モダンブラウザーでは実装されていて、古いブラウザーだと実装されていないJavaScriptの機能をpolyfillするためのJavaScriptです。

core-jsでは各機能ごとにpolyfillが提供されています。当初はどのpolyfillが使われるのか予想がつかなかったのでcore-jsのすべての機能を読み込んでいました。 ただそれだと必要のないpolyfillまで使われることになるので2.10でみなおしました。 具体的には以下のようにbabelの設定をみなおし、ソースコード内で使われていて、ターゲットのブラウザにない機能をpolyfillするようにしました。

['@babel/preset-env',
  {
    useBuiltIns: 'usage',
    corejs: 3,
    targets: {
      ie: 11,
      firefox: 30,
      chrome: 55
    },
  }
],

babelでは@babel/preset-envというプリセットを利用すると、useBuiltIns: 'usage'とオプションを指定することで自動で使われている機能だけをPolyfillしてくれます。 useBuiltin: 'usage'は@babel/preset-env7.4以上で利用することができます。 その結果、core-jsが実際に必要なモジュール(js)に使用されるようになるので、vendor.chunk.jsからはcore-jsがほとんどなくなりました。 これで非圧縮時のバンドルサイズが1.96MBになりました。



3. dynamic-importの積極利用

dynamic-importとはJavaScriptを必要なタイミングでJavaScriptから読み込む仕組みです。dynamic-importを積極的に利用することによってBundleされるファイルにはロード時に本当に必要なモジュールしか読み込まれないためBundleサイズの削減が見込めます。

とくにa-blog cmsの場合は、vendor.chunk.jsに依存モジュールを大量に溜め込んでいたため特に効果がありました。これによりBundleサイズが一気に非圧縮時のバンドルサイズが581KBにまで削減されました。



4. Cache Groupの利用

最後に検討したのがWebpackのCache Groupの導入です。どのモジュールでも共通で使われているモジュールをグループ単位で別のモジュールとして吐き出しておく設定ができます。実際に以下のように、styled-componentprop-types, react react-dom`が共通で使われていたのでそれらを抜き出してCache Groupに設定しました。Webpack.config.jsでは以下のように記述しました。

optimization: {
  splitChunks: {
    name: 'vendor',
    chunks: 'initial',
    cacheGroups: {
      vendorAdmin: {
        test: /styled-component|prop-types|react|react-dom/,
        name: 'vendor-admin',
        chunks: 'initial',
        enforce: true
      }
    }
  }
},

これでvendor.chunk.jsの中から、reactreact-domprop-typesなどのモジュールを追い出すことができたので最終的にバンドルサイズは213KB(非圧縮時 385KB)になりました。先ほどの画像と見比べてみると、reactprop-typesなどのモジュールがなくなっているのが確認できると思います。



まとめ

今回は特に改善が必要だった vendor.chunk.js に焦点をあてて記事にしましたが、結果的にvendor.chunk.jsは213KBに、index.jsは58KBに、admin.jsも27KBになり、最初に最低限読み込まなければいけないjsが300KB以内に抑えられるようになりました。1MB超→から300kBということで今回の一連の改善はかなりの効果がありました。この改善は a-blog cmsのVer.2.10.18から適応されています。 新機能を開発するのも大事ですがパフォーマンスなどに目を配ることも大事ですね。

Ver.2.10をお使いの皆さんはJavaScriptの改善が見込めますのでぜひバージョンのアップデートをご検討いただけたらと思います。 最後までおつきあいいただきありがとうございました。

Google Chrome(バージョン: 79.0.3945.79) でセレクトメニューのスタイルが崩れる不具合について


最新版のGoogle Chrome(バージョン: 79.0.3945.79)にて、セレクトメニューのスタイルが崩れてしまう不具合を確認しています。

ご迷惑おかけしてしまい大変申し訳ございませんが、お早めにご確認いただきますようよろしくお願いいたします。

影響している箇所

  • 管理画面のセレクトメニュー
  • acms.cssを使用したセレクトメニュー

この不具合はVer.2.0以上のバージョンで確認されています。

対応方法

以下のようにCSSを上書きすると解決されます。

/* acms.css に対応した記述 */
.acms-form select,
.acms-form select:hover,
.acms-form .acms-form-select
.acms-form .acms-form-select:hover  {
  -webkit-appearance: none;
}

/* 管理画面・閲覧側の編集画面のUI に対応した記述 */
.acms-admin-form select,
.acms-admin-form select:hover,
.acms-admin-form .acms-form-select
.acms-admin-form .acms-form-select:hover {
  -webkit-appearance: none;
}

管理画面側の対応

管理画面の修正方法ですが、上記のコードを themes/system/include/head/admin-css.html に追記します。

<link href="/css/normalize.css" rel="stylesheet">
<link href="/css/acms-admin.min.css" rel="stylesheet">
<link href="/css/acms-system.css" rel="stylesheet">
<style>
.acms-admin-form select,
.acms-admin-form select:hover,
.acms-admin-form .acms-form-select
.acms-admin-form .acms-form-select:hover {
  -webkit-appearance: none;
}
</style>

@include("/include/head/blog-color.html")

この現象は次期バージョンで修正予定です。

使われているGoogle Chromeで発生していなくても、Google Chromeのバージョンアップは自動で行われますのでお早めに対応いただくことをお勧めいたします。