理屈に合うイージングまとめ

人間は今まで経験してきた物理現象と同じ動きをするアニメーションを「自然な気持ちよさ」として感じることができるみたいです。

そのUIアニメーションが何を表しているのか?によって、なるべく理屈に合うイージングを選択することで、気持ちの良いアニメーションを作ることができるんじゃないか、と最近思うわけです。

私はこれまでイージングは「Quadだと遅いからQuartにしてみようかな〜」のようなアバウトな実装をしてました。

しかしこの方法では、実装工程で何度も動きを見ているうちにゲシュタルト崩壊して気持ちの良いアニメーションが何なのか分からなくなってきます。
しかも、そもそもQuartの速度を下げてもQuadのグラフとは一致しません。物理現象とは無関係なアニメーションになってしまうのです。

実世界の環境を忠実に再現することができない以上、物理現象と全く同じ動きを実装することは不可能ですが、理屈ありきの実装をすればある程度近い動きにはなるはずです。

また、主観的な決めつけで実装する必要がなくなるので、万人受けしやすくなります。ゲシュタルト崩壊して妙にクドいアニメーションになってしまうこともありません。

そういうことで、物理現象ごとに理屈に合うイージングをまとめてみました。

(半分ギャグです)

自由落下

モノが上から降ってくるような演出の場合。
これは一定の加速度で速度を上げ続ける運動(等加速度直線運動)なので、移動距離は経過時間の2乗に比例します。
(空気抵抗は無視)

当てはまるイージングは easeInQuad です。

Quadだと動きが鈍くて気持ちよくないと感じることが多いですが、Quadの計算式は単純に時間の2乗しか使っておらず、重力加速度が加味されていませんので、そのへんはdurationで調節してあげます。

どういうことかというと、動かす要素が実世界でどのくらいの大きさで、どのくらい離れて観測しているのかを考えます。
エヴァの空から降ってくる使徒サハクィエルは、随分ゆっくり降りてくるように感じますね。
あれはドでかいものを遠くから観測しているからです。

具体的には、落下距離を決めてそれをピクセル換算できれば、下記計算式でdurationを求められます。
(重力加速度は9.8としてます)

[時間] = √([距離] / 4.9)

CSSプリプロセッサでMixinでも作ったら捗るかもしれません。

スライドイン

例えば、ハンバーガーメニューをクリックして画面外からオーバーレイ要素が滑り込んでくるようなアニメーションの場合。

摩擦によって常に加速度がマイナス方向にかかっていると考えられるので、自由落下の逆で easeOutQuad を使います。
(速度によって摩擦係数は変わってくるらしいが、その辺は無視)

durationの算出にも自由落下と同じ式を使えますが、スケール感に加えて加速度(摩擦係数)もイメージする必要があります。
これには物体と接地面の素材を想像して、摩擦がどの程度あるかをテキトーに仮定します。

[時間] = √([距離] / ([摩擦係数] / 2))

動摩擦係数でググるとけっこう見つかるので参考にしましょう

徐々に消える、フワッと色が変わる

これもよく使うアニメーションです。hoverでボタンの色を変えたりとか。

色が変わる遷移をミクロな世界で物理的に説明してみると、たぶん分子レベルで特性が変わっていくんじゃないかなと思います。

そんなときはこれ。放射性同位体の原子数の時間的変化の式。
放射性物質の半減期を計算するときに使うような式です。

原子的に不安定な状態である放射性物質は、放射線の放出とともに原子の崩壊をして、いずれは安定した原子に変化していきます。
毎秒10%ずつ変化する原子が2000個あると、1秒後には残り1800個、2秒後には1620個、というように。
こちらの説明が分かりやすいです)

このような遷移を表すイージングは easeOutExpo
(出典はこちら

イージング関数を最初に作った人はやっぱりちゃんと理屈ありきで作ってたんだなあと関心しますね。

durationは半減期一覧を参考に近しい値を計算します。

と言いたいところですが、比較対象の想像ができる人なんてまず居ないと思うので適当な値でイイ感じにしてください。

もはや主観入ってますが、難しいので諦めました。

事務所を移転しました

10月から渋谷区千駄ヶ谷に事務所機能を移転しました。
https://nasbi.jp/about

これまで事務所機能は菅沼の自宅に置き、作業自体は完全リモートで各自の自宅から作業していましたが、主に下記3点の理由によりちゃんと事務所を構えることにしました。

  • 営業が安定してきた
  • 集まって仕事をすることで得られる知識もある
  • 信頼度アップ

これからもリモートで仕事してたりすることも多くあるとは思いますが、基本的に余裕のある日は出社して、パツってるときは自宅作業という分け方になるんじゃないかなと思っています。

業務自体はこれまでと何ら変わりませんので、関係者様に於かれましては、引き続きよろしくお願いいたします。

HTMLの文字コードをどうするべきか、あるいはHTMLとは何かという話

HTML文書は文字エンコーディングUTF-8でなければなりませんという記事があり、混乱があるようなのでHTMLについてHTML5とHTML Living Standard(以下HTML LSと省略)について、そしてHTMLファイルの文字コードをどうするかについて、まとめておきます。

TL;DR

  • HTMLファイルの文字コードはHTML Living Standardに従ってUTF-8にする
  • 古いSJISやEUC-JPのHTMLファイルをUTF-8に変換する必要はない

What is "HTML" ?

一般にHTMLと呼ばれる規格には複数あります。

  • HTML4.01を含むそれ以前のHTML (W3C)
  • XHTML1.1 (W3C)
  • HTML5.1 (W3C)
  • HTML Living Standard (WHATWG)

まず一旦古い話は置いておいて、HTML5とHTML LSについて考えることにします。 以下「HTML5」は特筆なき場合「W3Cの勧告したHTML5.1」の意味です。また、HTML5の日本語訳へのリンク先は5.0の文書ですのでご注意下さい。

HTML5(W3C) とHTML LS(WHATWG)の違い

HTML5とHTML LSは基本的には違いはありません。
1.2 Is this HTML5? - HTML Living Standard1.2 これはHTML5か? ― HTML Living Standard 日本語訳
にあるようにWHATWGの策定したHTML LSに基いてW3CがHTML5として勧告を出しています。

そもそもWHATWGはW3CがHTMLをアップデートしないのに業を煮やしたAppleやMozilla、Operaによって設立されました。それを後にW3Cが取り込んだのが規格としてのHTML5となります。
経緯は1.6 歴史 ― HTML Living Standardを参照するのが良いでしょう。

W3CとWHATWGのどちらに従うべきか

ブラウザの実装による、と言ってしまえばそれまでですが、基本的にHTML Living Standardを守れば良いです。ブラウザの実装もこれに則っています。またW3CではHTML LSに基いているだけで、全く同一ではありません。また、標準化のタイミングも異なります。

  1. HTML LSで標準化済みで、HTML5で勧告済み(多くの基本的な内容)
  2. HTML LSで標準化済みだが、W3Cでは未勧告(最新の内容)
  3. HTML LSで標準化済みだが、W3Cでは違いがある(一部の内容)

という3つのパターンがありえます。

3.については1.12 About this document - HTML5.1に列挙されています。

やっと文字コードの話

再掲になりますがHTML文書は文字エンコーディングUTF-8でなければなりませんの文字エンコーディングの話は

  • WHATWGでHTML Living Standardの話
  • W3Cでは未勧告(まだissueが切られただけ)

となります。
つまり、HTML4.01やXHTMLは関係がないので、古いサイトの文字コードをUTF-8に変換する必要はありません。
BOMについては、UTF-8についてはBOMをつけることが許可されているので、つけても良いですが、つけなくて良いでしょう。<meta charset="utf-8">のようにmetaタグで文字コードを指定することも禁止されていません。(が、当然ファイルそのものの文字コードと一致する必要があります。)

逆に他のSJISやUTF-16などの文字コードが利用できないかについては、HTML LSでは

the actual character encoding used to encode the document must be UTF-8.

4.2.5.5 Specifying the document's character encoding - HTML Living Standard

とあり、一方HTML5の現状では

Authors should use UTF-8. Conformance checkers may advise authors against using legacy encodings.
(中略)
Authors must not use encodings that are not defined in the WHATWG Encoding standard. Additionally, authors should not use ISO-2022-JP.

4.2.5.5 Specifying the document's character encoding - HTML5.1

と微妙に差異があります。が、これに逆らってUTF-8以外を選択するメリットは殆どないでしょう。

おわりに

Web開発者であれば、WHATWGとW3Cの文章に目を通すようにしましょう。

スマートにiPhoneXの対応のためのpaddingを設定する

iPhoneXには、画面上部にセンサーや前面カメラが仕込まれているディスプレイの切り欠きがあります。

safariではviewportを下記のように指定することで、横持ちした際にこの切り欠きの周りまでレンダリング領域とすることができます。

<meta name="viewport" content="viewport-fit=cover">

この指定をすると、ディスプレイ全体に背景色等を引き伸ばせるため、より一体感のあるデザインにすることができるのですが、切り欠き部分がコンテンツと重なってしまうことが起こり得るため、CSS定数を使ってpadding等を余分に持たせてあげることで、これを回避してあげる必要があります。

設定できるCSS定数は以下の通り

constant(safe-area-inset-top)
constant(safe-area-inset-right)
constant(safe-area-inset-bottom)
constant(safe-area-inset-left)

しかし、このCSS定数で使われる constant 関数ですが、他のブラウザでは一切実装されていない機能になります。
そのため、既にpaddingが設定されている要素にcalc等を使って切り欠き分をプラスしてあげる方法だと、構文解釈ができずにpaddingが未設定とされてしまいます。
とはいえわざわざiPhoneXのために新しい要素で囲ってあげるのもバカらしい…。

そんなときは下記の様に同じセレクタ内でpaddingを複数記述するとスマートに解決することができます。

.container {
	padding: 10px 20px;
	padding-right: calc(20px + constant(safe-area-inset-right)); // for iPhone X
	padding-left: calc(20px + constant(safe-area-inset-left)); // for iPhone X
}

safariでは padding-rightpadding-left を上書きするように後述しているので、切り欠き分も余分に余白が適用されますが、それ以外のブラウザでは constant を使っている行は解釈されないので1行目のみ適用されます。

要は記述順が重要で、ベンダープレフィックスのような用法で使ってあげればよいのです。

movファイルを手軽にgifに変換するシェル関数

パッと画面上の動きなどをSlackなどで共有したい場合に、mov形式の動画からGIFを生成するのは何かと面倒ですし、ファイルサイズも大きくなりがちです。かといってAppStoreから余計なフリーソフトなどを入れたくないですよね。そこでシェル関数という形でまとめてみました。

1. FFmpegとgifsicleをインストール

brew install ffmpeg
brew install gifsicle

などする。FFmpegは言わずと知れた動画や画像を変換するためのソフトウェアで、gifsicleはアニメーションGIF化をいい感じにやってくれるやつです。

2. 下記のシェルスクリプトを.zshrcなどにコピペする

function mov2gif() {
	mov=$1;
	if [[ -z $2 ]]; then
		width=300
	else
		width=$2
	fi
	if [[ -z $3 ]]; then
		rate=15
	else
		rate=$3
	fi
	gif=`basename $mov`".gif"
	ffmpeg -i $mov -vf scale=$width:-1 -pix_fmt rgb24 -r $rate -f gif - | gifsicle --optimize=3 --delay=3 > $gif
}

3. movファイルを変換する

mov2gif hoge.mov #オプション指定なしだと横幅300px・FPS15のGIFを出力
mov2gif hoge.mov 500 30 #と指定すれば横幅500px・FPS30で出力

アニメーションGIFのファイルサイズはFPSに大きく依存しますが、動きをよく見せたい場合もあるので、FPSを指定できるようにしてあります。
またGIF画像の高さを指定できないのは、殆どの場合キャプチャした動画は正方形に近く、サッと共有したい場合にはだいたいの画像の大きさが指定できれば十分という理由からです。

出力例

こんな感じのGIFファイルが生成できます。(横幅500pxで出力)

iOS10のSafariではVideo要素のインライン再生が可能になった

iOS9までのSafariでは、基本的にvideoのインライン再生はできず、再生すると強制的にフルスクリーンになっていました。

iOS10からはvideo要素にplaysinline属性をつけてあげることでインライン再生が可能になってます。

<video src="path/to/video.mp4" controls playsinline></video>

※ただしiOS9でもWebViewでは webkit-playsinline でインライン再生できました。

そしてこの仕様に併せて、JSからvideoのフルスクリーン制御もできるようになってます。

const video = document.querySelector('video');

// フルスクリーンにする
if(video.webkitSupportsFullscreen){
  video.webkitEnterFullscreen();
}

// フルスクリーンから離脱する
if(video.webkitSupportsFullscreen){
  video.webkitExitFullscreen();
}

video以外の要素で webkitRequestFullscreen を使ってフルスクリーンするのはまだiOSでは解禁されていませんが、videoのみ webkitEnterFullscreen でフルスクリーンできるようになった、ということです

HHVMではv3.19以降でないと定数に配列を使えない

HHVMはPHPとある程度互換性がありますが、完全互換ではありません。

const ARR = ['foo', 'bar'];
var_dump(ARR);

このような定数に配列を代入すると、Fatal errorを吐いてしまいます。これはクラス定数も同じです。
HHVMを3.19以降では機能がサポートされているので利用可能です。

SSRしないNuxt.jsでページロード時に非同期部分を更新する方法

Nuxt.jsでは、外部サーバ等から非同期でデータを取得・表示する場合にも asyncData を使ってあげることでクライアントサイドはもちろん、サーバサイドでもデータを取得し、HTMLで出力することが出来ます。

SSRできる本番環境があれば何の問題もないのですが、SSRせずプリレンダリングのみで運用する場合には、 asyncData は静的ファイルをgenerateした時点でしか走っておらず、ページロード時にデータが更新されていない問題が起こり得ます。

例えばコーポレートサイト等で、別のサーバにWordPressが設置してあり、トップページのお知らせとしてWordPressから記事を取得するような場合を想定すると、こんな感じで作ってあげることで解決できます

// 非同期でデータを取ってくる関数
async function getPostData() {
  return { data } = await axios.get('https://blog.example.com/wp-json/wp/v2/posts');
}

export default {
  async asyncData(context) {
    const obj = {};
    if (context.isServer) { // asyncDataするのはサーバサイド(プリレンダリング時)のみ
      obj.posts = await getPostData();
    }
    return obj;
  },
  async created() {
    if (!this.$isServer) { // クライアントサイドはここで更新をかける
      this.posts = await getPostData();
    }
  },
};

要は asyncData を使うのはサーバサイド(プリレンダリング時)のみ。
クライアントサイドでは created でデータの更新をかけてあげるわけです。

こうすることでプリレンダリング運用でも、一応(少し古いかもしれないけど)非同期データのHTMLもプリレンダリングされていて、ブラウザで訪れた際にはちゃんと最新の情報に更新されます。

PhpStormでnode製サイトのプレビューをする

node製サイトを開発時はwebpackを利用していればwebpack-dev-serverなどを利用するのが常ですが、プロダクションビルド後のファイルで確認したい場合もあります。

そういうときにわざわざサーバーにアップロードしたり、MAMPなどを利用するのは面倒です。ですが、PhpStormであればビルドも含めてショートカット1発でサーバーを起動することができます!

今日はその設定をしていきましょう。設定は1分で終わります。

Step.1 サーバー設定

「Run」->「Edit Configurations」から「Run/Dubug Configurations」のウィンドウを表示し下記に設定します。

  • Name: Preview server(好きな名前を設定してください)
  • Single instance only: チェックを入れる(重複起動を許可しないかのチェックです)
  • Host: localhost
  • Port: 3001
  • Document Root: ドキュメントルートへのパス

HostとPortは他のアプリケーションが利用中だと起動できないので、webpack-dev-serverやMAMPなどを利用している場合は被らないように設定するのが吉です。

Step.2 npmスクリプトの起動設定

次に「Before launch: Acvtive tool window」で「+」をクリックして「Run npm script」をクリックします。

すると「NPM Script」のウィンドウが開くので下記に設定します。

  • Command: run
  • Scripts: build(など自分でpackage.jsonに設定したスクリプト名)

Step.3 起動

あとは^ Rまたは「Run」->「Run 'Preview server'(上記のName:で設定した名前)」をクリックすれば指定したHostとPortでサーバーが起動します。

PhpStormなどJetBrains製のIDEはWeb関係の技術と親和性が高いので、Web開発者は必携ですね。