Inside Japan Kokura Stripe

/ Text by

仕事のプロジェクトで “Japan Kokura Stripe” という Web コンテンツのアートディレクション / デザイン / フロントエンド周りを担当しました。

北九州に伝わる「小倉織」という古くからの織物があり、残念なことに昭和の初期にその伝統は一時途絶えてしまったのですが、染織家である築城 則子さんの手により現在は「縞縞 SHIMA-SHIMA」のブランド名の下、現代的なセンスも取り入れ新時代の小倉織として見事に復活を遂げています。

その縞縞さんのクリエイティブ パートナーを務めている HULS Inc. さんからお誘いいただき、縞縞さんの縞デザインをより広く認知させる目的で Web らしい表現でコンテンツに落とし込むことができないかと考える機会をいただいたのが発端でした。また、縞縞さんが今月開かれるミラーノ・サローネへ出展されるため、その会場で見ていただく展示物のひとつとしての意味合いも含んでおります。

今回の技術面でのスコープは SVG / CSS3 / Webfonts / Device Motion / 縦書き で、さほど技術的な目新しさはないものの SVG をここまで扱ったのは自分にとって初めての経験でしたし、実現にあたりその他にも細々とノウハウなども溜まったので書いておくことにしました。


縞デザインと遷移エフェクト

縞デザインをどうやって Web らしく見せるか悩んだ結果、あえて写真は使わず縞のデザインをグラフィック的に解釈し、繰り返しパターンとして見せるシンプルな落としどころとなりました。写真を使わないというのはかなりチャレンジングですが、縞のデザインそのものにフィーチャーするという点をクライアント様にご納得いただけたことが大きいのではないかと思います。

そして、生地のデザインを切り替えて見せる際にただフェードイン・アウトさせるだけでなく、せっかくなら少し動きが欲しいよねというところで検討したのが今回の動きで、織物を織る際の糸の動きにインスパイアされたものです。実際に小倉織を織り上げる工程がこのような動きをすることはありませんが、小倉織の特徴である「縦縞」が徐々に出揃って模様が浮かびあがる様子を少しでも表現に取り入れたかった。

で、これをやるのに利用を検討したのが canvas / SVG / CSS3 だったわけですが、生地の縞デザインが「パターン」の繰り返しであることを踏まえつつ、

  • canvas: 速いだろうし表現力も高いが、一定パターンの繰り返し描画の仕組み自体から自分で実装する手間がかかる。レスポンシブ以前に、ウィンドウサイズが変わった時の描画の再計算の仕組み作りも面倒なことになりそう。そして個人的に苦手意識。
  • SVG: 調べてみたら柔軟に繰り返しパターンの定義ができる、よっしゃ。CSSとの親和性が高くアクセスできるプロパティも多い、よっしゃ。工数も他2つより手軽、よっしゃ。ただ、canvas と比べると遅くなるのかも。
  • CSS3: 背景画像の扱いはお手のもの。ただ、背景画像を動かしてアニメーションを実現させることを考えると工数がおぞましいことになりそうだし動作の負荷が高そうでまともな動作速度を得られるかが不安。また、背景画像ではなく <div> で構成することも考えたが前述する SVG のようなパターン定義の仕組みはなく、それならば CSS 単独で実装するメリットがない。

上記の理由から SVG が最善と結論づけた。canvas の速度と表現力には最後まで迷いましたが、縞縞のブランドやテキスタイル デザインの持つ雰囲気や佇まいにド派手な演出はそぐわないため SVG でいけると判断したわけです。

実装の基本デザインを検討する

さあ SVG でやるぞと決め、バックエンドも含めた構成は次のようにすることを考えた。

生地の名称・説明文・サムネイル画像・アイキャッチおよび OG 画像など各生地に付随するデータのうち、縞デザイン以外のデータを管理する CMS として WordPress を利用しました。管理画面構築の手間がかからないほか、各生地のページをテンプレート化できた方がやはり便利ですし、簡単な修正ならお客さんでも対応できるメリットが大きかったからです。

一方、生地のデザインデータも可能なら WordPress で一元管理したかったのですが Advance Custom FieldsACF の環境下では管理画面が混沌としすぎるほか ACF の構造上、データの読み出しにコストがかかりすぎるという判断で、デザインデータについては独自で生のテーブル (= Textile Table) を利用するのみとした。生地データの追加は基本こちらで請け負う前提なので CMS は特に入れないことにしました。

また図中には書きませんでしたが、SVG を都度生成したり API を毎回呼ぶ必要はないのでキャッシュの仕組みも入れてバックエンドの負荷を軽くする工夫も施しています。

CSS からアクセシブルな SVG の属性にはブラウザ間相違がある

大枠の実装デザインが固まったのでここから SVG の実装に入っていく。縞縞さんからは Web コンテンツに掲載する実際の生地のサンプルをご提供いただいたので、それを元にして

  1. 生地を一枚ずつスキャンする。
  2. スキャンした生地を元に Photoshop のシェイプツールを使って描き起こす。色調整はスポイトツールで機械的に拾うほか、一定のホワイトバランスの照明の下、目視で確認しつつ調整した。
  3. Photoshop から CSS として書き出し。
  4. CSS を SVG の描画に必要な値のみ残して整形。
  5. 上図の “Textile Table” に示す DB のテーブルにインポートするため、CSS を CSV データに変換する。この作業は手作業では面倒なほかミスを起こす可能性が高いので変換用のプログラムを書いて行った。
  6. CMS として仕込んだ WordPress から SVG を出力するテンプレートや、Ajax アクセス用の API を作成。

というプロセスを経て画面上に描画できた画像がこちら。

See the Pen Indigo Rondo by Takehiko Ono (@onopko) on CodePen.

x, y, width, height を XML 属性として定義したもの

See the Pen Indigo Rondo (with CSS) by Takehiko Ono (@onopko) on CodePen.

x, y, width, height を CSS で定義したもの

さて。ここで上下同じ画像が見えているという人が使っているブラウザは Safari か Chrome の WebKit ブラウザでしょう。そして下の方は見えないという人は Edge / IE11 / Firefox のいずれかその他のブラウザだと思います。両者の違いは明確で、上の SVG 画像はパターンの各要素に関連する x, y, width, height の値を XML 属性として定義しているのに対し、下の SVG 画像は CSS で定義してます。

最初自分は下の方法で進めていて WebKit 以外でまともに表示されないことに気づき、これは地雷を踏んでしまったかもしれないやはり canvas でやっておけば… と絶望を味わったのですが、これは WebKit が優秀すぎるというか余計な実装をしているからであって、他のブラウザが悪いというわけではありませんでした。

W3C の SVG styling の項目を見ると、CSS からアクセス可能なプロパティがちゃんと定義されているんですね。その中には x, y, width, height どれもありません。ソースとスタイリングを厳密に分けることができないのは残念ですが、アクセシブルでないプロパティについては XML 中で定義してあげる必要があります。


Velocity.js is Justice

現在表示している生地から次の生地へデザインをトランシジョンさせるには、まず次の生地のデザインを構成する各縞のデータを API から読み込み、Velocity.js を用いてアニメーションさせました。この時、毎回同じアニメーションとならないよう各縞の動き始めの時間をコントロールして順序をランダマイズしています。

$('#stripe1').velocity({
    translateZ: 0,
    x         : 100,
    width     : 5,
    fillRed   : 128,
    fillGreen : 32,
    fillBlue  : 64
}, {
    duration  : 300,
    delay     : 100
});

のような形で各縞にアニメーション効果を当てていきます。x や width は SVG の XML 属性であり CSS からアクセシブルではないことを書きましたが、他の CSS プロパティのアニメーションと同列に扱えてしまうところが Velocity.js の強力なところ。Velocity.js 同様に CSS アニメーション ライブラリとしてそこそこ使われているものとして Transit が挙げられると思いますが、こちらは SVG の XML 属性にはまるで対応していません(CSS のアニメーション ライブラリなのでそこを求めるのが酷というものですが)。

なお “translateZ” の指定は本来実現したい動き上は不要なのですが、指定をしておくとPC・スマートフォンとも CSS アニメーションに強制的にハードウェア アクセラレーションが働きますので動きが高速になるほか CPU の負荷も下げられます。なんでもかんでも指定してしまうとブラウザの使用メモリが増大するほか描画のバグが出てしまうデメリットもあるようなのですが、滑らかな動きが欲しい時には必須と言えます。

Forcefeeding

概ね大満足な Velocity.js なんですが、時たまどうも不思議な挙動をして困る。アニメーションをさせたい要素に対し、CSS 内であらかじめ別のアニメーションが定義されている時などに起こりがちです。

期待されるアニメーションを確実に実行するには、[] を用いてアニメーションを開始する時の初期値も合わせて指定するのが無難です。アニメーションを強制するという意味合いで Forcefeeding と呼んでいるようです。

$('#stripe1').velocity({
        translateZ: 0,
        translateX: [目標値, 初期値]
    }, {
        duration  : 300
    }
);

なお要素の現在の translate の値を得るのは vender prefix の影響もあり実は結構厄介なので、関数化しておくと便利でした。

function getTranslates ($_obj) {
    var transformMatrix = $_obj.css("-webkit-transform") ||
        $_obj.css("-moz-transform") ||
        $_obj.css("-ms-transform") ||
        $_obj.css("-o-transform") ||
        $_obj.css("transform");

    var matrix = transformMatrix.replace(/[^0-9\-.,]/g, '').split(',');

    var x = matrix[12] || matrix[4];  // translateX
    var y = matrix[13] || matrix[5];  // translateY

    transformMatrix = matrix = void 0;

    return [x, y];
};

var translates = getTranslates($('#id'));
var translateX = translates[0];
var translateY = translates[1];

画像のブレンドをしたいなら canvas

生成した SVG に生地の質感を与えるため、当初はテクスチャ画像を SVG Filter を用い全体に「乗算」する形でブレンドしたのですが、これでアニメーションを行うと CPU 負荷が跳ね上がりコマ送り状態の非常に厳しい処理結果となりました。

動かす要素がベクターであっても、そこに画像をブレンドした瞬間に内部的にはラスタライズしているのと同じとなり、アニメーションの毎フレームごとに全体を画像としてレンダリングする形となるようです。今回はテクスチャを SVG 全域にわたってかぶせる必要がありましたし、また SVG の表示領域も大きかったことから「使える」動作速度にはなりませんでした。

元のテスクチャ画像(白色部分の透過処理をしていない)
テクスチャ部分を残し透過処理した画像(透過部分が赤色)

今回はテスクチャ画像の白地部分を抜いて透過処理することで、SVG Filter を使用することなく目的とする 描画を実現できたので難を逃れましたが、描画するものに乗算やオーバーレイなどのブレンドを扱ったアニメーションを行うなら確実に canvas を使った方が良いです。


See the Pen bpvVjr by Takehiko Ono (@onopko) on CodePen.

横スクロールの初期位置を右端にする

上のように、一定のブロック要素が横並びになったようなリスト表示でしか使えないテクニックとなりますが、ページを開いた時に、横スクロールの初期位置を右端にしたい場合。リストを overflow:hidden で内包するブロック要素 (.p-cn__container) を180度回転させ、かつ中のリストの子要素 (.p-textile-link) を180度回転させることで可能です。ポイントは、

.p-cn__container > .p-textile-links > .p-textile-link

と構成されている要素のうち、180度の回転を行うべき要素はそれぞれ「」と「」の関係にある点です。実際に試してみればすぐに分かりますが、「親」と「子」の関係では左端のままとなりうまくいきません。

Scroll Snapping

現在のところ Safari • Chrome といった WebKit 系のブラウザでしかサポートされていませんが、スクロールを行った際にあらかじめ決めていたグリッド位置へスクロール後の位置をピタッと合わせることが可能です。

上のサンプルにおいてもスクロールが水平方向に 95px 移動するごとに吸いつくように止まる実装をしています。※ただし、特殊な事例ですが上のように iframe でコンテンツを読み込んでいる場合にはバグがあり、正常に動作しない場合があるようです。

また、上のような細かな間隔で scroll-snap を設定してしまうと、PC ではかえってスクロールがしにくいと感じると思いますので、タッチデバイスにのみ適用されるように CSS を調整すべきです。

FYI: Scroll Snapping Examples (WebKit.org)

1画面ごとにスクロールを止めたいような場合、現状では JavaScript に頼らざるを得ない状況ですので他のブラウザにもサポートされると良いなと思います。細かい部分では Scroll Snapping してもしなくても大した印象の変化は生まれないかもしれませんが、ちょっと小粋な動きではありますね。


favicon を動的に変更する

Japan Kokura Stripe では favicon に生地の一部をクロップした画像を設定しています。縞デザインごとに favicon を作るだけでも結構面倒な作業ではありますが、favicon と縞デザインが一致してないとなんだか気持ちわるいでしょう。というだけのこだわりからなのですが。

生地の切り替え時、ページ全体を読み込むのではなく非同期にページの一部分を動的に書き換えることによって切り替えの速度を向上させています。この時当然 favicon の切り替えも必要になるのですが、

<link id="favicon" rel="shortcut icon" href="http://japankokurastripe.com/favicon.ico">

favicon の指定部分が上記の link 要素の通りだとして、一見 href の値を書き換えれば良いだけのように思えますが実はそれではうまくいきませんので、一旦 link 要素を削除して新しく link 要素を追加する対応が必要でした。

$('head #favicon').remove();
$('head meta:last')
    .after(
        $(document.createElement('link')).attr({
            id  : 'favicon',
            rel : 'shortcut icon',
            href: '切り替え先の favicon のファイル名'
        })
    )
;

favicon の書き換えを行う事例として上記はちょっと特殊で、通常は Gmail などのように新規メッセージなどの「通知」の数を知らせる場合に小粋な演出として使われることが多いと思います。

そうした通知の際にもし favicon をアニメーションさせられたら?と考えると、視覚的に訴える効果も出せそうでナイス アイデアと思えるものの、実際に実装するとなるとコマ送りの要領でアニメーションに必要な分の favicon を作る必要があり作業的になかなかシビアです。しかしモダンブラウザに限れば [canvasに描画] → [pngに変換] → [faviconへ書き出し] という芸当も可能になっており、世の中そうしたニーズに応える奇特なライブラリというものが探せばあるもので、favico.js がガッツリと応えてくれることでしょう。

setAnimationFrameTimeout

モダンブラウザでの繰り返し実行によるアニメーションの実装には、setTimeout / setInterbal ではなく requestAnimationFrame を使え。と言われて久しいですが、少なくとも setTimeout を使う機会は定期的な処理の実行時ではなく、複数の処理を並列で実行する場合に一部の処理をあえて遅らせて負荷を軽減する「遅延実行」時に多くはないでしょうか。

また setTimeout は指定された遅延のタイミングで何が何でも渡された処理を実行しようとするのに対し、requestAnimationFrame はウィンドウがアクティブではない時には FPS を落とすなど柔軟性を持っています。そのため、requestAnimationFrame で処理を実行中に setTimeout で別の処理を実行すると、ユーザーの行動によっては両者の実行タイミングの辻褄が合わなくなることも考えられます。

Japan Kokura Stripe でもモバイル端末での devicemotion イベントの処理時に同じような状況に置かれたため、setTimeout が実現する機能をモダンブラウザが持つ requestAnimationFrame と performance.now で代替する jQuery プラグインを書いた。

こちらは別エントリーにて。

Webfont の読み込み後に処理を実行する

Webfont の表示はフォントベンダーから配信されるか、自サーバーに設置した Webfont ファイルの読み込み後に行われ、ブラウザにキャッシュされていない場合にはそれなりに時間がかかります。そして Web サイトを実装していると、中には Webfont の読み込みを待たずに実行されてしまうと困る場合や、並列に処理している内容が多く負荷がかかるので、部分的に実行を遅らせたいという場合が出てきます。

そんな時に使えるイベント処理として $.onLoadWebfonts を用意しました。元ネタは Stack Overflow にあったこちらの記事なのですが、フォントの読み込みごとにコールバック関数が実行されてしまう不具合があったので修正した上で jQuery プラグイン化した。Japan Kokura Stripe でも上記の setAnimationFrameTimeout と同様に負荷分散の目的で使用しました。

使い方は以下の通りで $.onLoadWebfonts に対し、Webfonts の CSS 上での名前リストと読み込み後の処理を引数として渡してあげるのみです。リストに書く Webfont の名前をコントロールすれば、特定の Webfont の読み込みのみを監視することも可能です。

ただし 1 つ問題はあって日本語等のマルチバイト フォントにおいて、その Webfont にページ中で使われている文字のみをロードするダイナミック ロードの機能が備わっている場合(ほとんどのケースだと思いますが…)には監視が困難です。基本的には欧文フォントのように全ての文字を一気にダウンロードするタイプの Webfont の場合に有効な手段かと思います。

$.onLoadWebfonts(['webfont1', 'webfont2', ...], function() {

    // do something;

});

そしてここまで書いてナンですが、ブラウザが CSS Font Loading API をサポートしているならこんなことをする必要はありませんのでそちらを使いましょう。


devicemotion

制作も終盤になって演出として、スマートフォン等加速度センサーに対応したデバイスに限りますが、端末の傾きにより生地がゆったりと流れるような演出を加えました。

PC についてはマウスで横にスクロールできるようなギミックを持たせてもよかったのですが、デバイスを制限したのには理由があり、「生地をマウスでスクロールする行動は実際には有りえない」という判断からでした。何を言っているんだ…?という話ですが、人が着物やその生地を手にとって眺めようと思う時には、両手に抱えた生地を左右に傾けたり、見たい場所へとゆっくりと滑らせるように生地を移動させるという行動を取ると思います。

Japan Kokura Stripe で扱っているのは生地のテキスタイル デザインですが、そのデザインが反映されているのは現実世界にある生地という「物」になります。その物を扱う時にする動作と演出はリンクすべきと考え、対応端末を制限する結論に至りました。もちろんスマートフォンやタブレットで行う手の動きが生地を眺める時の動きと全く同じという訳でもありませんが。

前述した通り、このサイトにド派手な演出は不要という前提があります。が、気づくか気づかないかの細々とした仕掛けを施すことで、サイトにリッチな印象を積み重ねていくことができれば良いなと思います。


TypeKit から特定の文字が配信されない

これはもう完全に TypeKit 側のバグとなりますが、WebFont として使用している「A-OTF 太ミンA101 Pr6N」のうち何故かカタカナの「キ」と「ク」だけ配信されない。すごく地味に辛いので直してください。