SVGでMaterialDesign風のボタンをつくってみた

ワンパクでは派手な技術よりも役に立つビジネスに貢献できることが求められているがゆえに技術者とかデザインをやればやるほどなんだか無難で地味なものばかりやる方向に行ってしまいがちでこれではいかんという思いから[イケてる技術をつかってイケてるUIをつくろう](http://yd-niku.holiday/documents/20150114/)という思想でSVGやWebGLでUIを作っていくマイブームが到来しました。

[MaterialDesign風のボタンのDEMO](http://jsrun.it/yd_niku/kEJ8)

# SVGでボタンをつくってみて

- 参考にしたのは[Material Designのボタン](http://www.google.com/design/spec/components/buttons.html#buttons-floating-action-button)
- SVGの操作は[svg.js](http://svgjs.com/)
- フィルターは[プラグインつかう](https://github.com/wout/svg.filter.js)と簡単にできる
- ドロップシャドウはちょっと[一工夫する](http://tutorials.jenkov.com/svg/filters.html)とできる

# コード解説

```js
var unit = 4; // とりあえず4dpの単位をつくる
var width = 38;
var height = 12;
var radius = 32;

// SVGを突っ込む要素のIDを渡す
// だいたいチェーンメソッド
var draw = SVG('button').size(unit * width, unit * height + unit);

// ピンクの下地
var base = draw.rect(unit * width, unit * height);
base.radius(unit, unit).fill('#f06');

// 演出用のレイヤーたちをクリップするためのグループ
var group = draw.group();

// クリップマスク用のシェイプ
var clipRect = draw.rect(unit * width, unit * height);
clipRect.radius(unit, unit);

// ホバーしたとき用の白めのシェイプ
var focusCircle = group.ellipse(unit * radius, unit * radius);
focusCircle.center(unit * width * 0.5, unit * height * 0.5);
focusCircle.fill('#fff').opacity(0);
focusCircle.filter(function(add) {
  add.gaussianBlur(1); // ガウスエフェクト
});

// プレスしたとき用の黒めのシェイプ
var pressedCircle = group.ellipse(unit * radius, unit * radius);
pressedCircle.center(unit * width * 0.5, unit * height * 0.5);
pressedCircle.fill('#000').opacity(0);
pressedCircle.filter(function(add) {
  add.gaussianBlur(1); // ガウスエフェクト
});

// ボタンのラベル
var labelText = group.text('Button');
labelText.font({
  family : 'Helvetica',
  size   : unit * 4,
  anchor : 'middle'
});
labelText.x(unit * width * 0.5);
labelText.dy(unit * 2);
labelText.fill('#fff');

// クリップ作成
var clip = draw.clip().add(clipRect);
group.clipWith(clip);

// ドロップシャドウ
base.filter(function(add) {
  var blur = add.offset(0, 2).in(add.sourceAlpha).gaussianBlur(1);
  // ドロップシャドウを半透明にする処理
  blur = blur.colorMatrix('matrix', [
    0, 0, 0, 0, 0,
    0, 0, 0, 0, 0,
    0, 0, 0, 0, 0,
    0, 0, 0, 0.5, 0
  ]);
  add.blend(add.source, blur);
});

function onFocus() {
  var loopFlag = false;
  function loop() {
    var r = loopFlag ? radius : radius - 2;
    this.animate(1600, '>').size(unit * r, unit * r).after(loop);
    loopFlag = !loopFlag;
  }

  focusCircle.stop().animate(450, '>', 250).opacity(0.5).after(loop);
  pressedCircle.stop().animate(200).size(unit * radius, unit * radius).opacity(0);
}
function onBlur() {
  focusCircle.stop().animate(800).opacity(0).size(unit * radius, unit * radius);
}
function onPress(event) {
  var innerRadius = radius - 12;
  var outerRadius = radius + 4;
  pressedCircle.center(event.layerX, event.layerY).opacity(0.2).size(unit * innerRadius, unit * innerRadius)
    .animate(600, '>').size(unit * outerRadius, unit * outerRadius).opacity(0);

  focusCircle.stop().animate(200).opacity(0).size(unit * radius, unit * radius);
}

// イベント設定
group.click(onPress);
group.touchstart(onFocus);
group.touchend(onBlur);
group.mouseover(onFocus);
group.mouseout(onBlur);
```

# 技術的なポイント

## 基本

```js
var draw = SVG('target');
```

SVG要素を生成する。

## 要素作成

```js
var rect = draw.rect(30, 30);
```

SVGの子要素として四角をつくる。

## グルーピング

```js
var group = draw.group();
var rect = group.rect(30, 30);
```

group要素の子要素に四角をつくる。

## クリッピングマスク

```js
var base  = draw.rect(100, 40);
var rect = draw.rect(); //クリップするシェイプ
var clip = draw.clip().add(rect); // クリップを作成してシェイプを追加

base.clipWith(clip); // クリップの適応
```

四角とかだけじゃなくて、groupにも適応できる。

## アニメーション

```js
rect.animate().size(100, 100);
```

アニメするためのプロキシオブジェクトをつくってそれにプロパティ指定するとうごくぽい。


```js
rect.animate(5000, '>', 1000).size(100, 100);
```

引数に duration, easing, delayを設定できる。

## フィルター

```js
rect.filter(function(add) { // 要素に適応されるフィルタオブジェクト
  add.gaussianBlur(1); // ガウスエフェクト
});
```
プラグインを入れると .filter オブジェクトが追加されます。

## イベント処理

click, mouseXXX, touchXXXあたりはとりあえず使えました。

```js
rect.click(function(event) {
  // do something.
});
```

現場からは以上です。
ヒマがあったらもうすこしかきます。