CHROMA

世の中の "当たり前" を確認する

ページ移動・離脱時のアラートメッセージ

利用者にとってフォームからの予期せぬ離脱を防ぐため、アラートメッセージを作った。

入力、確認、完了ページがあるフォームで、完了ページでは既にデータも送信されていて利用者が失うものが薄いと思ったので、ここではアラートを出さないようにした。配慮した点はそのくらいか。

$(function(){
  $(window).on('beforeunload', function() {
    return '入力したデータは保存されませんがよろしいですか?';
  });

  $('input[type=submit], .edit-btn').click(function() { // 送信ボタンや編集ボタン
    $(window).off('beforeunload');
  });

  if (window.location.pathname == '/path') { // `/path` をアラートを出したくないページのパスに
    $(window).off('beforeunload');
  }
});

参考にしたドキュメント

物体を円周や進行方向に従って動かす

f:id:thleap:20141224171843p:plain

JavaScript Advent Calendar 2014 の24日目。

このデモでは、マウスを押している間は障害物を中心とした円周上を操作対象が回り、マウスを押していない間はその時の進行方向に向けて操作対象が直線的に移動する。

One More Line というゲームの断片を JavaScript で作ろうとしたが、そう呼ぶにはこれはあまりに乏しい。

とは言っても、数学的な知識が中学(もしくはそれ以前)で止まってしまっている僕にとってはベクトルや三角関数といったものは脳を熱くさせるのに十分なレベルだったし、周りに助けてもらわなければここまでは到底作れなかっただろう。

プログラミングは言語の書き方と、筋道を立ててモノを作る方法を理解しないといけないということを知った。特に動きのあるモノを作るのは難しい... 。

コードの解説

まず、{x: 0, y: 0} みたいなオブジェクトを二次元のベクトルとして扱うため、便利な関数を以下のように用意する。

function vector2(x, y) {
  return {
    x: x,
    y: y
  };
}

function addVector2(a, b) {
  return {
    x: a.x + b.x,
    y: a.y + b.y
  };
}

function subVector2(a, b) {
  return {
    x: a.x - b.x,
    y: a.y - b.y
  };
}

function magnitudeVector2(a) {
  return Math.sqrt(a.x * a.x + a.y * a.y);
}

function normalizeVector2(a) {
  var m = magnitudeVector2(a);

  return {
    x: a.x / m,
    y: a.y / m
  };
}

circle (白線の円)を操作対象、target (赤線の円)を操作対象がつかむ障害物としてそれぞれ変数を用意しておく。変数 tapping はマウスの押し離しの結果を判定するのに用いる。

start 関数内で Canvas の描画を宣言し、操作対象の開始位置、マウスの押し離しのイベント、繰り返し処理を定義しておく。stop 関数では繰り返しを止める処理を定義しておく。

onUpdate 関数

さて、ここからがこのコードのメインになるが、繰り返し処理の際に使う onUpdate 関数について解説していこう。

この関数では大きく 2つのことをしている。

  • 角度や進行方向の計算、条件分岐による処理の設定
  • Canvas への描画

重要なのは 1つ目のもので、Canvas を使った描画はほとんど基礎的なことしか行っていない。

角度や進行方向の計算、条件分岐による処理の設定

ここでは circletarget を中心とした円周上に入るときの角度の計算や、circle の進行方向の計算を行う。また、if (tapping) から始まる条件文でマウスを押している間と離してる間に行う処理を定義する。一つ一つ見ていこう。

以下の変数は、後で target を中心とした円周を求める際に使用する。selfToTargettarget から circle までのベクトルを求め、これを使い変数 r で円の半径を算出する。

var selfToTarget = subVector2(target, circle);
var r = magnitudeVector2(selfToTarget);

変数 radianAngle では Math.atan メソッドを使って target の円周に対する selfToTargetラジアン値を求める。今後の計算を度数で行うため、変数 angleラジアン値を度数に変換する。

var radianAngle = Math.atan(selfToTarget.y / selfToTarget.x);
var angle = radianAngle * (180 / Math.PI);

最後の変数 newPosition は繰り返し処理の際、circle が次に移動する位置を示す。

そして、これらの変数を用意した後は、target の円周上で circle を上手く回すために次の 2つの 条件文を用意しておく。

Math.atan はなす角しか返さないので、変数 angleJavaScript の座標系で円周の -90度から 90度の範囲しか返さない。円の残り半分の角度も取るために、circle の X座標が target の X座標よりも左に来たときに角度を 180度加算する。これにより angle は -91度から -180度、91度から 180度も返すようにする。

if (circle.x < target.x) {
  angle += 180;
}

あと、angle がマイナスの値を返すと円周 0 から 360度の角度は取れないので、この際には angle に 360度を足し合わせる。

if (angle < 0) {
  angle += 360;
}

if (tapping) から始まる条件文ではマウスの押している間、離している間の circle の位置移動を定義する。

マウスを押している間は target を中心とした円周上に newPosition を取るようにし、この処理が呼び出される度に円周上の角度を 1度足していく。

マウスから指が離れている間は circle の進行方向に従って直線的に進んでいくようにする。

if (tapping) {
  var newAngle = (angle + 1) % 360;
  newPosition = addVector2(target, {
    x: r * Math.cos(newAngle * Math.PI / 180),
    y: r * Math.sin(newAngle * Math.PI / 180)
  });
} else {
  newPosition = addVector2(circle, {
    x: circleDir.x * circleVelocity,
    y: circleDir.y * circleVelocity
  });
}

Canvas による描画処理に入る前に、circle の進行方向( circleDir )と座標を上記のコードで得られた値に書き換える。

circleDir = normalizeVector2(subVector2(newPosition, circle));
circle = newPosition;

onUpdate 関数の最後で、circle の Y座標が 0 を越えたときに繰り返し処理を止める。

if (circle.y <= 0) {
  stop();
}

Canvas への描画

一つ前の描画が Canvas 上に残らないように、clearRectonUpdate 関数が呼び出される度に Canvas を一度白紙に戻してる。

他は特に解説するようなことが無く、Canvas に用意されているメソッドとプロパティを使って円や線の描画を行っている。

参考

マークアップや文章の編集をちょっと楽にする

Vim Advent Calendar 2014 19日目。

初めに、僕は普段主に HTML と CSS、それとブログ記事などの文章を Markdown で書いてる。

今回はこれらのコードを Vim で編集する中で、今まで不満に思いながらも放置していたことを解消したいと思う。

また、問題の解決にあたっては去年の Vim Advent Calender や Web 上に公開されているドキュメント、Vim に用意されているヘルプに大変お世話になった。感謝の念が絶えない。

指定範囲の折り畳み

コードの閲覧や編集中、特定の箇所以外のコードが視覚的に邪魔になることがある。そんなときには Vim の折り畳みを使おう。

.vimrc に以下を追記。

set fdm=indent

折り畳み範囲は {{{ を開始位置とし、}}} を終了位置として指定する。

<!-- Header {{{ -->
<header>
  <h1 class="logo"> ... </h1>
  <nav class="nav"> ... </nav>
</header>
<!-- }}} -->

各種コマンドで折り畳みの作成や削除、開閉を行う。

  • zf ... 作成
  • za ... 開閉
  • zd ... 削除

参考

ファイルのアウトラインを表示

HTML や JavaScript を書いているとき、ブログ記事を書いているときなどはアウトラインをどこかに表示したい。

これには unite-outline プラグインを使う。

.vimrc に以下を追記し、プラグインを使えるようにする。

NeoBundle 'h1mesuke/unite-outline'

アウトラインを表示する。

:Unite outline

垂直分割でアウトラインを表示させたい場合は -vertical オプションを付ける。

:Unite -vertical outline

参考

インデントの可視化

いらないかと思ってたけど、数ヶ月前自分が書いたファイルを最近になって開いてみるとインデントが正しく取れてないものがいくつもあった... 。編集中から気をつけるためにも、インデントを目で見てわかりやすくしておく。

indentLine プラグインを使う。.vimrc に追記。

NeoBundle 'Yggdroot/indentLine'

可視化したインデントのラインの色なども変更できるみたいだけど、今はデフォルトのもので特に不満がないのでこのままに。

参考

インデントの自動挿入

以下を .vimrc に追記。

set autoindent

以上... 。

参考

文字数のカウント

ビジュアルモードでカウントしたい範囲を選択し、g <CTRL-g> コマンドを使うと選択範囲の文字数をステータスラインに表示してくれる。

例えば「てきすとテキスト」という文字列をカウントした場合は以下のような結果が得られる。8 of 2740 Chars で文字数 8がカウントされたことがわかる。

Selected 1 of 133 Lines; 1 of 236 Words; 8 of 2740 Chars; 24 of 4936 Bytes

参考

HTML の開始タグと終了タグの移動

CSSJavaScript ではほとんど {}% を使って移動をするだけなので問題ない。

しかし、これでは HTML を編集する場合などに開始タグと終了タグの移動ができないので vim-matchit を使う。

.vimrc に以下を追記。

NeoBundle 'tmhedberg/matchit'

昔入れてたこのプラグインは今年頭に一旦外したんだけど、HTML を編集するときはこれが無いとやっぱりつらい。改めて便利だと思った。

参考

ウィンドウの分割領域を少しずつ変更したい

ウィンドウ幅や高さをを少しだけ大きくしたり、小さくしたい時なんてのがある。<CTRL-w> { >, <, +, - } でこれはできるけど、幅や高さを少し変更するときに何度もコマンドを打つのはしんどい。

vim-submode というのを使うと微妙な分割領域を変更するのが大分楽になる。例えば <CTRL-w> > で Submode に入り、このモードに入っている間は >< だけでウィンドウ幅の変更ができる。便利。

実際に vim-submode を使うには、プラグインの追加と合わせていくつかの設定を行う。

NeoBundle 'kana/vim-submode'

" vim-submode setting
call submode#enter_with('winsize', 'n', '', '<C-w>>', '<C-w>>')
call submode#enter_with('winsize', 'n', '', '<C-w><', '<C-w><')
call submode#enter_with('winsize', 'n', '', '<C-w>+', '<C-w>-')
call submode#enter_with('winsize', 'n', '', '<C-w>-', '<C-w>+')
call submode#map('winsize', 'n', '', '>', '<C-w>>')
call submode#map('winsize', 'n', '', '<', '<C-w><')
call submode#map('winsize', 'n', '', '+', '<C-w>-')
call submode#map('winsize', 'n', '', '-', '<C-w>+')

あと、vim-submode は関係ないけど、ファイルを開くときにウィンドウの領域を指定するには :vs:sp に引数を持たせてあげれば良いみたい。CSS ファイルを狭いウィンドウで開きたいとかよくある。

:50vs style.css
:10sp article.md

参考

JavaScript を書くときの簡単な注意

書いたコードを人に見せると次のようなことを指摘されたので、これから JavaScript を書くときは注意していこうと思う。

JavaScript やその中で使う計算はやっぱり難しく感じる。けど、小さなことでも意図した通りの表示や挙動が見れると嬉しい。

明日も腰を丸めながら頑張ろう。

下から上へ円が移動するアニメーション

Canvas 上の最下部から最上部に向けて、円が移動していくアニメーションを作った。

function canvasApp() {
  var canvas = document.getElementById('stage');
  var ctx = canvas.getContext('2d');

  var x = canvas.width/2;
  var y = canvas.height;
  var dx = 0;
  var dy = -1;
  var r = 20;

  startUp();

  function drawScreen() {
    // canvas の初期化
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    ctx.fillStyle = 'black';
    ctx.beginPath();
    ctx.arc(x, y, r, (Math.PI/180)*0, (Math.PI/180)*360, false);
    x = x+dx;
    y = y+dy;
    ctx.fill();
  }

  function startUp() {
    var timer = setTimeout(function() {
      startUp();
    }, 10);
    if (y == 0) {
      clearTimeout(timer);
    }

    drawScreen();
  }
}

2回目以降の描画の際には、drawScreen 内で clearRect を使って Canvas 上の描画を一度真っ白にしないと円が移動しているようには見えない。前の描画処理の内容が残ると線が下から上に引かれる用に見えてしまう。

他は特に工夫したところはなくて、ドットインストールや HTML5 Canvas の本で学んだことを応用しながら作ってみた。

今は直線にしか移動させてないけど、次は移動の途中で動きの方向を変えたりさせてみたい。

参考

スタイルは適度に小さく正確に

CSS Architecture Advent Calendar 2014 の12日目。

SMACSS や BEM など、去年のように新しい CSS の設計手法は今年学ぶ機会が作れなかった。その代わり、いくつかのサイトを作る中で気づいたことや、自分なりに気にかけたことがあるのでそれを書いておこう。

コードを載せたら少し長くなってしまったので、ページ内リンクを貼っておく。

CSS を書く以上はプロパティや値の扱いを軽視することはできない。もしかしたら設計の話からは逸れているかもしれないが、2つ目と 3つ目ではプロパティや値に注意して書くことで予期した結果が得られるというようなことを書いた。

要素本体と変化しやすいスタイルを分ける

基準となるものは除いて、配置場所によって内容が変化しやすいスタイルは主要なスタイルと切り離しておいたほうが良さそうだ。

そして、"変化しやすい" スタイルと聴いて思い浮かぶのはサイズや余白ではないだろうか。

これらの指定をモジュールの派生クラス( Modifier )として扱ってしまうとクラスはどんどん数を増していく。

サイズの変更だけでも、ボタンなら .btn--small, .btn--large, .btn--small-x, .btn--large-x... と増えていくことになるだろう。

また、余白に関してはボタンそのものの大きさや周囲の要素を考慮しながら変更する必要が出てくる。これもまた多くの派生クラスを生むだろう。

つまり、このようにクラスが増える。

.btn { ... }
.btn--small { ... }
.btn--small-x { ... }
.btn--small-xx { ... }
.btn--small-xxx { ... }
.btn--large { ... }
.btn--large-x { ... }
.btn--large-xx { ... }
.btn--large-xxx { ... }
... /* このあとに余白に関する派生クラスが続く */

個人的にこれはあまり好ましくないので、ボタンやアイコンを定義したモジュールでは色や画像ソースの指定と基準となるサイズの指定にとどめ、サイズを変更したり余白を加えたい場合はそれ自身が置かれるモジュール内で定義したほうが良いと思う。

このように:

/* button */
.btn {
  box-sizing: border-box;
  display: inline-block;
  text-decoration: none;
  cursor: pointer;
}

.btn-primary {
  border: 1px solid #519bf2;
  background-image: linear-gradient(180deg, #73aff4, #519bf2);
  background-color: #519bf2;
  font-weight: bold;
  color: #fff;
  text-align: center;
  text-shadow: 0 1px 0 rgba(0,0,0, .2);
  box-shadow: inset 0 1px 0 rgba(255,255,255, .2), 0 2px 2px rgba(0,0,0, .2);
}

/* box */
.box {
  overflow: hidden;
  padding: 1em;
  border: .1em solid #eeefef;
  background-color: #fcfcfd;
}

.box__text {
  float: left;
  margin-top: 0;
  margin-bottom: .5em;
}

.box__button {
  float: right;
  padding: .25em 1.25em;
}

尚、要素本体の存在はそれ自身だけで成り立つことも重要だ。上の例でいうとボタンは最低限それがボタンであるとわかるスタイルを振っておく必要がある。

スタイルを分ける際には「基準と異なるサイズと余白、配置の指定だけ」のように、ある程度ルールを決めておいても良いかもしれない。

表現したい内容を正確に表す

例えば要素を円形にするクラスを定義するとき、border-radius の値は要素に対して半分の値を適用すれば思い通りの結果になる。

しかし、40 * 40 の要素に border-radius: 20px を適用すれば、これは 40 * 40 以下の要素でしか効果が発揮されない。

.circle {
  border-radius: 20px;
}

「要素に対して半分」というのを 50% という値で指定するとどうだろうか。

.circle {
  border-radius: 50%;
}

同じクラスを使って要素を円形にするという目的が果たせたのがわかる。

少し例が悪かったかもしれないが、これは何もピクセルの指定を一切使うなと言ってるわけではないし、ヘルパークラスを増やすことを目的としているわけでもない。

プロパティに値を指定するときは、自分が表現したい内容を正確に表すのが重要だということが言いたかった。

ショートハンドは少なめに

初めから要素に当たっているスタイルの打ち消し・上書きは、それを適用したい箇所に対してのみ行うようにしている。

プロパティが重複したときがつらいというのと、ショートハンドの指定を覚えるのが面倒なのが理由だ。

次のような場合、後から指定した background プロパティが前のものを上書きしてしまう。結果、background-image やその他の指定は初期値に戻されるので画像は表示されず、.image-bg で指定した背景色だけが表示される。

.image {
  width: 36px;
  height: 36px;
  background: url(data:image/png;base64, ...) center center / 75% 75% no-repeat;
}

.image-bg {
  padding: 5px;
  background: linen;
}

以下のように書くことでこの問題は避けられる。多少手間が増えても、分けて書いたほうが予期せぬ結果を生まないので良い。

.image {
  width: 36px;
  height: 36px;
  background-image: url(data:image/png;base64, ...);
  background-position: center center;
  background-size: 75% 75%;
  background-repeat: no-repeat;
}

.image-bg {
  padding: 5px;
  background-color: linen;
}

状況にもよるけど、僕がショートハンドを使って書いてるのは border と全方向の指定をする margin および padding くらいだと思う。

色付き画像に白色を上塗り

今日は filter: brightness() を使って色の付いてる画像を白色にしたりしてた。

.box__icon--invert {
  -webkit-filter: brightness(3);
  filter: brightness(3);
}

明るい色の背景を暗い色に反転させたとき、背景に載ってるアイコンの色を白に変えたい時がある。

普段は画像の置き換えで何とかしてるけど、これが CSS でできると便利。背景画像にも filter は有効なので、やっぱり便利。

IE でも早く filter 使いたい。