わざと引っかかりのあるアニメーションを実装する
一般的にはアニメーションは高パフォーマンスであることが良しとされる。が、あえてFPSを下げたアニメーションにしたいことがあった。レトロゲームのような世界観を表現したいというやつだった。
tween.jsは、ユーザーがアニメーションの更新タイミングを制御できるAPIになっている。こういう感じ。
var coords = { x: 0, y: 0 }; var tween = new TWEEN.Tween(coords) .to({ x: 100, y: 100 }, 1000) .onUpdate(function() { console.log(this.x, this.y); }) .start(); requestAnimationFrame(animate); function animate(time) { requestAnimationFrame(animate); TWEEN.update(time); }
つまりTWEEN.update
を呼ぶ頻度を制御できればいい。それを踏まえてこういう風になる。
const createFpsControlledTicker = (handleTick, fps = 60) => { let startTime = null let lastFrame = -1 const secondsPerFrame = 1000 / fps let requestId = null let isRunning = false const tick = () => { requestId = requestAnimationFrame(tick) if (startTime == null) startTime = performance.now() const currentTime = performance.now() const elapsedTime = currentTime - startTime const currentFrame = Math.floor(elapsedTime / secondsPerFrame) if (lastFrame !== currentFrame) { handleTick(currentFrame) lastFrame = currentFrame } } const start = () => { if (isRunning) return requestId = requestAnimationFrame(tick) isRunning = true } const stop = () => { if (!isRunning) return cancelAnimationFrame(requestId) isRunning = false } return { start, stop, } } createFpsControlledTicker(() => { TWEEN.update() }, 15).start()
以上のようなコードで15fpsのアニメーションを実装できた。
ブレイクポイントは端末のサイズに依存させない
レスポンシブデザインの最適なブレイクポイントは何pxなのかという話題をよく見る。既存のデバイスのサイズを比較することで答えにたどり着こうとするアプローチがほとんどだが、それらは間違っている。
ブレイクポイントは、コンテンツの見た目が切り替わるべきタイミングを基準に設定されなければならない。特定のデバイスのサイズを基準にすべきではない。
主流のデバイスのサイズは時間が経てば変わるし、あるデバイスに合わせた設計にしていたら別のデバイスには嬉しくない結果になってるかもしれない。レスポンシブデザインはどんな環境においてもある程度の最適化をさせるために妥協可能な落としどころを目指す思想なので、それと対立してしまう。
改善策は、既存のデバイスのサイズを気にし過ぎずに、コンテンツ自身のためのブレイクポイントを考えること。コンテンツ自身に合わせたブレイクポイントの設計にすることで、結果的にどのデバイスでも最適な見た目になるという形が望ましい。
とはいえ現実的には、ブレイクポイントは決定事項であった方が都合がいい。デザインごとに最適なブレイクポイントを探るのは手間がかかるし、デザイナーとコーダーが分業する際のやりとりは少ない方がいいからだ。そのため、あらかじめ定数として定義しておいた方がやりやすいというのは理解できる。個人的には以下のように決めている。Material designを参考にした。
$breakpoint-xs: 480px; $breakpoint-sm: 600px; $breakpoint-md: 960px; $breakpoint-lg: 1280px;
デバイスを意識させない命名にした上で、適切な位置や間隔でブレイクポイントを配置することを意識している。これらは、コンテンツの見た目が切り替わるブレイクポイントとして汎用性の高い値だと思っている。
最終的にやってることは既存の記事と同じようになったけど、そもそもなぜこうするのかを理解した前提でするべき話だと思ったのでこれを書いた。
もっと細かい値を基準にしたVertical Rhythm
本文のline-height
を基数としたVertical Rhythmは、値が大きくなりすぎるのでどうしても細かい調整がしたくなってうまくいかない。
それに悩んでいて@terkelに聞いてみたら、「本文のline-height
より細かい数字を基準にしてる、4px
とか。Material Designも多分そういうルールに則ってる」という話を聞いた。なるほど良さそうと思ったものの、細かい数字になってくると計算がめんどくさそう。
Rhythmic Sizingを利用できれば、それはかなり簡単に実現できる。要素のサイズやline-height
の値を、必ず指定した数値の倍数になるようにできるというやつだ。以下はline-height
を4px
の倍数にする例。
html { line-height-step: 4px; } .text { font-size: 1rem; // 16px line-height: 1.7; // 27.2px => 28px } .heading { font-size: 3rem; // 48px line-height: 1.3; // 62.4px => 64px }
これが動くようになれば嬉しいんだけど、まだ実装されるかもわからないのでSassでline-height-step
もどきを作ってみる。
$line-height: 1.7; $line-height-small: 1.3; $font-size: 1rem; $font-size-large: 3rem; $rhythm-text-line: ($font-size * $line-height); $rhythm-unit: ($rhythm-text-line / 4); // step unit like // Reference: https://www.w3.org/TR/css-rhythm-1/#step-unit @function rhythm-step($length) { @if unit($length) != unit($rhythm-unit) { @error "The unit of $length should be #{unit($rhythm-unit)}"; } $steps: ceil($length / $rhythm-unit); @return ($rhythm-unit * $steps); } // unit less `line-height` // Reference: https://twitter.com/terkel/status/826600067930873857 @function rhythm-line-height($font-size, $line-height) { @if unit($font-size) != unit($rhythm-unit) { @error "The unit of $font-size should be #{unit($rhythm-unit)}"; } @if not unitless($line-height) { @error "$line-height should not have a unit"; } $rhythmical-length: rhythm-step($font-size * $line-height); @return ($rhythmical-length / $font-size); } .text { margin: 0 0 $rhythm-text-line; font-size: $font-size; line-height: rhythm-line-height($font-size, $line-height); } .heading { margin: 0 0 $rhythm-text-line; font-size: $font-size-large; line-height: rhythm-line-height($font-size-large, $line-height-small); }
rhythm-step
に与えた値は$rhythm-unit
の倍数になる。$rhythm-unit
の値は、px
にしたときに整数になるようにした方が安心できるんじゃないかとか、本文のfont-size
とline-height
の最大公約数になってた方が都合がいいんじゃないかとか考えると4px
にするのがちょうどいい気もする。けど、小さすぎる値になるとパターンとして認識されない気がしたので、少し大きめの本文のline-height
を4で割った値にしてる。
rhythm-line-height
は、line-height
から単位を取り除いた値に変換する。というのも、line-height
に絶対的な値を指定していると、フォントサイズが想定より大きい値になったときに行間が詰まりすぎてしまうからだ。具体的には、Chromeの最小フォントサイズ設定はデフォルトでは10pxになっているが、ユーザーがそれを16pxに設定しているとする。その場合、次のようになっていると問題が起こる。
.text { font-size: 10px; // => 16px line-height: 15px; }
単位無しの値を設定しておくと、リズムは崩れるものの、最低限の可読性を確保することはできる。JavaScriptで無理やり解決することはできそうだけど、そこまでのリスクを負ってやるべきことではなさそう。
ユーザーは常にスクロールしながらコンテンツを閲覧するので、視点を一定のリズムで制御できる利点は失われるという意見もあるので、どの程度のコストをかけて実装していくべきなのかは考えたい。
エモさ on 地味な土台 in ウェブサイト
地味な土台の部分をきちんと作った上に、いわゆるエモさみたいなものが積み上げられた形のウェブサイトを作りたいなというのをずっと思ってる。
地味な土台というのは、アクセシビリティやパフォーマンス、サイト全体を通してのスタイルの一貫性など、ウェブサイトを作るために普通に意識すべきことのことを指している。
というのも、最近のウェブサイトを作るときの意識って、気持ちいいアニメーションがあって、インタラクティブに動いて、見た人が魅了されるみたいなエモさを追求することに向かい過ぎてる。その結果、当たり前にやるべきはずのことが蔑ろにされている。しかし、エモさはあくまで付加価値であって、それ自体を最優先で考えるのはウェブサイトの目的を見失っていることに他ならないと感じる。
なんでその目に見えない地味な部分を見た目にわかりやすいエモい部分より優先しないといけないのかというと、それが他のメディアにはないウェブの強みを活かすために必要な要素だからだ。ウェブは普遍的であることで発展してきた。誰もがどこからもアクセスできて、いつでもちゃんと使える。これはごく当たり前のことに聞こえるけど、多くはそれを意識できてないか無視してる。
反して、ウェブの利点を犠牲にしてまでウェブサイトのエモさを追求したところで他のメディアには敵わない。縦横比が固定された動画や、加えてプラットフォームが限定されたゲームなどと違って、多様なユーザーエージェントに対応するためには点で見たときの見た目や体験だけをあてにできない。だから、発想の柔軟性は制限されているし、コストも掛かり過ぎる。
ユーザーの多様性を認めることがウェブの良さを最も素直に発揮できる方法であって、全てのウェブサイトはその前提の下で設計されるべきだ。
とは言いつつも、僕はウェブサイトにおけるエモさを否定はしてないし、それを取り入れること自体は良いことだと思ってる。けど、そのために別の環境やユーザーからはアクセスできない、使えないようになってしまうなら採用すべきでない。
ウェブサイトを制作するために必要なことをピラミッドで表すと、地味な土台の上にエモさは位置している。下の層から順に達成できている状態で、その先でエモさを作り出すことに取り組めれば最も理想的。そんな優先順位でウェブ制作に取り組むことが当たり前になれば、みんなにとって魅力的なウェブにしていけると僕は信じてます。
CSSにおける和欧混植のベストプラクティス
欧文はウェブフォント、和文はユーザーエージェントのデフォルトにするというのがベスト。
html { font-family: Lato, sans-serif; }
というのも、ローカルの欧文フォントを指定するためにはプラットフォームごとにインストールされているフォントを把握しておく必要があるからだ。現状のシェアを占めているものだけに絞ればまだしも、クライアント環境が多様化し続けている昨今ではそのアプローチは不十分に思える。
ちなみに、主要なプラットフォームにインストールされているフォントの一覧は以下。
ウェブフォントを利用した指定にしておけば、プラットフォームに関わらず和欧混植にできる。読み込みに失敗するというケースも考えられるが、その場合の見栄えは妥協できるだろう。
和文の指定に関しては、まずウェブフォントはコストが高いので避けたい。加えて、前述したような理由でフォントファミリー名を指定するのも微妙。そこでsans-serif
などのジェネリックな指定にしておけば、ユーザーエージェントが最適なフォントを選択してくれる可能性が高い。
とはいえ、タイポグラフィとしては和文フォントと欧文フォントの組み合わせがまずいものにならないようにはしたい。そういう場合は、それぞれのフォントが利用できる場合のみ特定の処理を行うというアプローチも取れる。
参考:
Sassのmixinでユニークなキーフレーム名を宣言する
スプライトアニメーションをコンテンツの背景にフィットさせる - ライデンの新人ブログ
こういうやつを複数回やりたかったのでmixinにした。普通にやるとキーフレーム名がかぶるので、mixinを呼び出すたびにIDをインクリメントさせるようにしたらいけた。
$_sprite-id: -1; @mixin sprite($frame-count, $seconds-per-frame) { $_sprite-id: $_sprite-id + 1 !global; background-size: 100% ($frame-count * 100%); animation: sprite-#{$_sprite-id} #{$frame-count * $seconds-per-frame}s steps($frame-count) infinite; @keyframes sprite-#{$_sprite-id} { from { background-position: left top; } to { background-position: left (100% * $frame-count / ($frame-count - 1)); } } } .sprite1 { @include sprite(3, .1); background-image: url("sprite1.png"); } .sprite2 { @include sprite(12, .2); background-image: url("sprite2.png"); }
keyframesの内容が同じでも複数回宣言するので雑だと思うけど、ちゃんとやるのめんどくさそうなのでやめた。Mapで$frame-count
をキーにしてIDを保存しとくとかすればいけそう。
2017年に向けてやってきたこと
去年の今頃ってなにしてたんだっけって思ったら、倒れて手術してたんだった。これを機に、人はいつでも死ぬのでなんかやるなら早めが良さそうという気持ちになったんだった。
この頃の僕には、自分のフロントエンドの専門性を高めたいという目標があった。とはいえ、専門学校に入ってからウェブサイトのコーディングとかするようになって2年目という程度の経験しかなくて、それでも実際にプロとしてやってる人たちに絶対に負けたくない気持ちがあった。そして、負けないためには短期間で効果的に成長する必要があった。そのために僕は、とにかく考える物事の範囲を狭めて、獲得した成長が必ず専門性を深める結果になるように意識した。短期的にはその試みは成功したと思ってる。
そうこうやってるうちに少し遅れて就職した。そこで感じたのは理想と現実の剥離だった。
僕が専門性を高めることを意識してきた理由は、自分が担当する役割の中においてベストな結果を実現させるためだ。けれど現実には、自分の担当範囲だと思っていた部分の外に見過ごせないだけのたくさんの問題があった。そんな状況のもとでは、仮に自分の手だけを正しく動かしていても、最終的な成果物を正解に近づけられない。現状を変えるためにもっといろんなことを考えないと良いものが作れないという意識に変わってきた。
入社してから今日までは、それらをいかにして改善していくかを考えてきた毎日だった。
僕がGitHubに公開してるReal World Website Boilerplateはそのひとつだ。他の人が作った開発環境が作業しづらくてかなりストレスを感じていたので、こうやれば快適に開発できるという枠組みを考えて提案するために公開している。「ぼくがかんがえたさいきょうのgulpfile.js
」みたいなのは本質的でない部分に労力を割いているようで馬鹿にされがちだけど、その結果どうすれば開発しやすい形になるか考えることが軽視されて、関わりたくないつらいプロジェクトになっていく。あんまり開発環境にこだわりがない人に向けて「これ使ってたらおおよそ間違いないです」と薦めたい。
最近は会社で場を設けてもらって、ウェブはこうやって作っていくべきみたいな話をできるだけ具体的にしてる。それを叩き台にして全員で議論したり、加えて、これまでのやり方を改善して形にするための試験的なプロジェクトもやってみてる。
改善の手がかりが掴めたような気がして、変化の兆候が見え始めた気がしてる。