$.animate()でのコールバック地獄を回避する

jqueryで、あるアニメーションの終了後に次のアニメーションを続けたいということがあった。
普通に書くとコールバック地獄に陥ってしまうので、Promiseっぽくthenチェーンで書きたい。
jquery的に書くなら、毎回$.Deferredでラップして書いていかないとダメなのかなと思ってたら、$.animate()$.promise()をつなげると$.Deferredが返ってくるので、それを使ったら簡潔に書けた。

$el.animate({ left: 100 }).promise()
  .then(function () {
    return $el.animate({ left: 200 });
  })
  .then(function () {
    return $el.animate({ left: 300 });
  });

もうちょい複雑なデモ

thenの中で返してるオブジェクトは、promiseを持ってたら次のthenまでいくので、thenで返すオブジェクトにpromiseをつなげる必要はない。
コードこの辺

入院してる

1月1日の夜、突如肺に激痛がした。
尋常でない痛みだと感じたので救急車を呼ぼうとしたが、あまりに体が痛くて動けない。
僕は所々で意識を失いながらもなんとか携帯を手に取り、119番へ連絡した。
それから病院まで搬送され、緊急手術を行った。

原因は、もともと僕が気胸を持っていて、前に発症したときの穴を塞いでいた、かさぶたのようなものが剥がれたこと。
それがきっかけになって血管が切れ、1リットル以上の出血をした。

今は病室からこの記事を書いていて、かなり元気に回復してる。
数時間前に体に入っていた管が抜けて、さっき自分でシャワーを浴びることもできた。
ただ、あのとき助けを呼べなかったら確実に死んでいたと考えると、恐怖でしかない。
誰しも常に、死と隣り合わせになることはあり得るのだと認識して、もう少し対策を考えなければならないと実感した。

ビルド時にデータベースのデータをJSファイルに埋め込む

webpackでビルドするときに、外部データベースのデータをJSファイル内に埋め込む方法。
機密にする必要がなくて、ほぼ変更しないデータがあったので、毎回サーバーから受けとるよりもJSファイルのなかに埋め込んでしまえば、通信するコストがかからないと思ったのでやってみた。

最初、gulp-dataを使えばできるんじゃないかと思ったけど、どうやらそれでは無理らしく、webpackのDefinePluginを使えばやりたいことができた。

webpackのDefinePluginは、webpackでビルドするときに設定値に値を注入することができるプラグイン
例えば、モジュールに含まれるprocess.env.NODE_ENVproductionに置き換えたりということができる。

こんな感じのコードでうまいこといった。

'use strict';
const MongoClient = require('mongodb').MongoClient;
const webpack = require('webpack');

const DATABASE_URL = 'mongodb://for_webpack:pass@ds061974.mongolab.com:61974/heroku_k4vml408';

const fetchData = query => {
  return new Promise((resolve, reject) => {
    MongoClient.connect(DATABASE_URL, (err, db) => {
      if (err) {
        return reject(err);
      }

      db.collection('posts').find(query).toArray((err, docs) => {
        if (err) {
          return reject(err);
        }

        resolve(docs);
        db.close();
      });
    });
  });
};

const build = data => {
  return new Promise((resolve, reject) => {
    webpack({
      entry: {
        app: './src/app.js'
      },

      output: {
        path: './dist',
        filename: '[name].js'
      },

      plugins: [
        new webpack.DefinePlugin({
          FETCHED_DATA: JSON.stringify(data)
        })
      ]
    }, (err, stats) => {
      if (err) {
        return reject(err);
      }

      console.log(stats.toString({ colors: true }));
      resolve();
    });
  });
};

fetchData({})
  .then(build);

これで、モジュール中のFETCHED_DATAが取得したデータに置き換えられる。

mongodbのURLは、以前作ったブログのデータベース。
一応読み取り専用のユーザーにしてる。

全コードはここに置いた。

herokuでbabel-nodeを使う

babel-cliをdependenciesに追加し、Procfileに以下のような記述をする。

web: $(npm bin)/babel-node .

package.jsonのrun scriptからは、依存モジュールのCLIまで勝手にパスが通ってくれるけど、Procfileの場合はそうでない。
$(npm bin)がそこまでのエイリアスになってるので、上記のように書くと動く。

ちなみに、node .とするとpackage.jsonのmainに指定しているファイルが起動するけど、babel-nodeでも同じようになる。

一通り動くようにしたレポジトリ

追記
package.jsonのrun scriptsに"start": "babel-node ."、Procfileにweb: npm startって書くだけでよかった。

CSSだけで要素を垂直中央に配置しつつ、あふれたらスクロールできるようにする

JSを使わずにCSSだけで要素をページの真ん中に配置するという方法はいろいろある。
けど、その要素の中身の高さが、親の高さを超えてしまうと、その上部分が見切れてしまうというパターンが多い。
そういう場合はJSを使って位置を調整することが多いけど、CSSだけで解決できそうな気がしたのでやってみた。

.center {
  position: fixed;
  right: 50%;
  bottom: 50%;
  width: calc(100% - 20px);
  max-width: 600px;
  max-height: calc(100% - 20px);
  overflow-y: auto;
  transform: translate(50%,50%);
}

Element in the vertical center where you can scroll

これで、.centerの中身が少ないときは垂直中央に、.center自身の高さを上回ったときにはスクロールできるようになる。
ポイントは、max-widthとmax-heightで最大の大きさを指定しつつ(100%以下にする)、overflowではみ出したらスクロールできるようにしてること。
IEではまだ試してない。

WebGLで展示会のシンボルを表現した

毎年年度末くらいに学校でやってる展示会があって、その告知サイトで使うシンボルのアニメーションの実装を担当した。
すでに完成したポスターのビジュアルがあり、そのイメージを残しつつWeb向けにアレンジしたという形。

f:id:yuheiy:20151204013038p:plain

WebGLを使ったと言ってもたいして難しいことをしたというわけではなく、ライブラリを使って簡単なアニメーションをさせただけだったので実装は難しくなかった。といってもいきなりスムーズには作れてない。
まずポスターのデータを渡されたとき、シンボルがパスの集合なのを見て、SVGで書き出したデータを動かそうと考えた。でも実際にやってみると大きく2つの問題があった。

まずポスターのデータは、媒体のサイズが決まった上でデザインされているので、Webのように複数の画面サイズで見せることが想定されていなかった。
ポスターのイメージをサイトでも同じように表現するため、ウィンドウサイズいっぱいにシンボルを配置することに決めたけど、複数のサイズに対応するためにあらかじめ画像を作っておくという手は現実的ではないので、なんらかの方法でリサイズするたびに見た目を作り直さないといけない。なのでそのままのデータを使うことは無理そうだった。

それに加え、SVGではアニメーションのパフォーマンス上の問題があった。
ポスターのシンボルは1000以上のパスで構成されていて、SVGとしてパスをそれぞれ動かすと負荷が大きく、まともに見れるアニメーションにならなかった。
それで、2DのCanvas上にシンボルに似せたものを描画してアニメーションさせると、多少は改善はできた。けどまだまだスムーズとは言い難い動きだった。
このままでは処理を多少書き換えてもアニメーションが劇的に改善されるということはないと思ったので、パフォーマンスがいいらしいWebGLを使って描画することにした。

僕自身まったくWebGLの知識はなく、three.jsというのがデファクトのライブラリだということだけ知っていたので、とりあえずそれを使って2Dの描画に挑んだ。
けど、three.jsで2Dを描画する方法が初学者にとっては理解しにくく、早々と挫折した。

他に方法はないかと調べていると、2D描画をWebGLで行えるライブラリがあるということを知った。
Pixi.jsとCreateJSがWebGLでの2D描画をサポートしているらしく、使ったことのあるCreateJSを採用しようとしたけど、CreateJSにはWebGLでグラフィック描画を行う機能がないのでpixi.jsを使った。
アニメーションに関してはtween.js(CreateJSじゃない方)を使ってる。

ECC EXPO 2016 Teaser Site

結果的にかなりパフォーマンスを改善できた。
スマホに関しても、2年以内くらいに発売された機種ならまともに動いてくれる。
CanvasIE9以上、WebGLはIE11以上でないと対応していないけど、このサイト自体がIE10以下はサポートしないというポリシーなので問題なかった。まずこの前提があるおかげでいろいろ選択肢が広がったのかなという感想。

ソースこんなん

参考
HTML5 CanvasとWebGLの使い分け - ICS LAB

ポートフォリオ作り直した

Reactで作ってたポートフォリオサイトを、サーバーサイドレンダリングを有効にして作り直した。
他の部分もコードを書き直して、無駄に分かりにくくなってた処理を単純化できた。
デザインもちょっと変えて、前はナビゲーションをハンバーガーボタンにしてたのを、あんまり使いやすくないのでやめた。

ソースはGitHubで公開してる。

バックエンドはnode.jsのExpressで作って、サーバーはHeroku使ってる。
お名前.comで取得してたドメインとつなげて、前と同じURLからアクセスできるようにした。
Heroku眠ってたらなかなかつながらないし、常に起こしとこうとしても無料プランだったら18時間までしか稼働してくれないので厳しい。

Yuhei Yasuda Portfolio Site