ウェブサイトギャラリーを作り直した

気に入ったサイトを集めるだけのページが欲しくて前に作ったものがある。
けどそれがあんまり使いやすくないので作り直した。

前のやつは、管理用のページを用意して、そこにURLとパスワード入力したらデータベースに登録して、実際のリストに反映されるという形にしてた。
でも、そのデータベースを置いてたサーバーの契約更新時期が来たのをきっかけに、これくらいのことで自分でデータベースを使うのは大袈裟なような気がして、もっと簡易的な仕組みにしたいと思った。

ということでまず、自分のタイミングで更新できるページなので、サーバーサイドとかAjaxでデータ取ってきてレンダリングするというのはやめて、ローカルでテンプレートエンジンを使って静的なページにすることに決めた。
それで、ローカルでビルドするときに基になるデータが必要なので、その管理をどうするかで悩んだ。BaaS使おうかなと思ったけどそれでは前と同じ手間だし、登録しやすくするにはどうしたらいいか考えて、いつもページをブックマークするときに使ってるPocketを使うのがよさそうだと思った。chrome拡張機能を使ってページをすぐに登録できるので便利。

ということで、気に入ったサイトはPocketに専用のタグをつけて登録して、ページを更新するときはPocketのAPIからそのタグのついたデータを取ってきてビルドするという形にした。API使うのに最初だけOAuth認証しないといけないのがめんどくさかった。

Website Gallery

サムネイルはHeartRails Captureってやつがあったので使わせてもらった。
毎回手作業で作るのは手間すぎるし、それを自動化するのもなかなか大変そうだったので既存のもので対応。

GitHub Pagesに置いた。
yuheiy/gallery.yhey.me

図形による画像のクリップをクロスブラウザで実現する

この記事の内容は全体的に古いので、クロスブラウザ周りの情報はあまりあてにしないでください。

Webサイト上で、画像を六角形にクリップしたいということがあった。
やり方はいろいろあるけど、クロスブラウザで動作するようにと考えるとSVGのclipPathを使った方法がよさそうだった。
ただ、ブラウザによる解釈の違いではまるところもあったので備忘録として書いておく。

まず、clipPathを定義するSVGをHTML内にインラインで書く。
外部ファイルに記述することもできるけど、クロスブラウザでは動作しない。
以下のコードは六角形にくり抜くclipPath。

<svg width="0" height="0" style="position: absolute;">
  <defs>
    <clipPath id="hexagon" clipPathUnits="objectBoundingBox">
      <polygon points=".5,0 .933,.25 .933,.75 .5,1 .067,.75 .067,.25"/>
    </clipPath>
  </defs>
</svg>

clipPathUnits="objectBoundingBox"という属性をつけると、要素に対して相対的にクリップできる。
これを使わない場合だと、クリップするためのSVG要素をpxで指定することになって、伸縮する要素に対応できない。
インラインスタイルでposition: absolute;を指定しているのは、これがないとこのSVG要素分の空間ができてしまうので、この指定で高さを無くしている。
あと、このclipPathを参照できるようにIDをつけておく。
この記述を、HTML内のどこに書いても問題なさそうだけど、bodyの終了直前に書くのが多いっぽい。

次に、実際にクリップされた要素を配置する場所にSVGを記述する。

<svg width="500" height="500">
  <image class="clipped" xlink:href="./img/photo.jpg" width="100%" height="100%"/>
</svg>

クリップする要素にCSSから参照する用のクラスをつけておく。 複数の要素をクリップするときはg要素とかで囲ってやるといい。

<svg width="500" height="500">
  <g class="clipped">
    <image xlink:href="./img/photo.jpg" width="100%" height="100%"/>
    <rect width="100%" height="100%" style="fill: rgba(0,0,0,.8);"/>
  </g>
</svg>

そして、次のようなCSSを書く。

.clipped {
  clip-path: url("#hexagon");
}

-webkit-をつけるとSafariで動作しないので外しておく。
Autoprefixerとかを使ってると勝手につけてくれるのでそれも考慮しておかないといけない。
PostCSSの設定で特定のプロパティだけ無視するみたいなことができるかもしれないけど、やり方がわからないので、僕はAutoprefixerの実行後に不要なベンダープリフィックスを削除するようにしてる。

あと、Firefoxではまるところがあって、clipPathのurlとして指定するIDの前に、CSSファイルの位置から見たHTMLファイルまでのパスを書く必要がある。

css/
- style.css

index.html

みたいな階層だと、

.clip-path {
  clip-path: url("..#hexagon");
}

という風に書いたりする。ルートまでの指定だったら

.clip-path {
  clip-path: url("/#hexagon");
}

とか。
この辺で複数ファイルがあったときの挙動とかは調べてないけど、どうしても問題があればHTML内にインラインCSSで書いてやれば動くはず。

デモ作った

で、実際に作ったものとソース)はもうちょっと複雑だったけど、CSSのpositionを駆使すれば実現できるレベルだった。
とはいえ若干手間でわかりにくいので、IEとかもclipPathの実装はがんばってほしいとしか言いようがない。

$.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ではまだ試してない。