The future starts today

Webとか英語とか育児とかに関する雑記

ElectronアプリにGoogle Analyticsを導入する

f:id:shibe97:20170116230507p:plain

先日、Electron製のTwitterクライアントであるworcを公開した。

shibe97.hatenablog.com

せっかくなのでアクセス解析をしようと思い、Google Analyticsを導入したのだが、色々とハマりどころが多かったのでメモ。

Google Analyticsに登録

f:id:shibe97:20170116221551p:plain

新規にアカウントを作ろうとすると上記のような画面が出る。

その際、ウェブサイトのURLを入力する欄があるが、Electron製のためURLがない。 ここは、example.comなど適当に入力しておく。

ラッキングコードを設定する

アカウントの作成が終わるとトラッキングコードが生成される。
しかし、このコードをそのままElectronアプリのHTMLに挿入してもうまく計測がされない。

Google Analyticsではcookieを用いてユニークユーザーの判定をしているが、Electronアプリはドメインがないためcookieの登録ができない。

そこで、electron-cookiesを用いる。

github.com

その名の通り、Electron上でcookieを擬似的に持たせることができる。

npm installを行なった後、これを用いて以下のようにトラッキングコードを設定する。

$ npm install @exponent/electron-cookies --save
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
  (() => {
    'use strict';
    const ElectronCookies = require('@exponent/electron-cookies');
    ElectronCookies.enable({ origin: 'https://example.com' });
    ga('create', 'GOOGLE_ANALYTICS_ID', 'auto');
    ga('set', 'location', 'https://example.com/');
    ga('set', 'checkProtocolTask', null);
    ga('send', 'pageview');
  })();
</script>

node_modulesに注意

Electronアプリは容量が多いためWebpackなどでバンドル後、ビルド時にnode_modulesを除外しているパターンもあるだろう。
一見、Webpackでの依存解決でelectron-cookiesbundle.jsに含まれそうだが、このトラッキングコードはindex.htmlに記述されているため、Webpackのバンドルの対象外だ。

よって、node_modules/electron-cookiesを含めた状態でアプリをパッケージングする必要がある。

これに気付くのにすごい時間かかった。。

2017年の目標

ブログで宣言。

Happy New Year

ブログ

月2回以上書く。
去年は途中全然書いていなかったので、今年は飽きずに頑張る。

英語

とりあえずTOEIC800点超える。
海外出張1回以上行く。
海外ドラマや映画に手を出す。

社外活動

登壇4回以上する。
WorcのDAUを1000人以上にする。
定常的な副収入源を作る。

社内活動

熟練JSerを増やす。
給料上げる。
残業時間を月10時間以内に抑える。

総じて

今年で30歳になるので、色々とチャレンジしていきます〜!

2016年振り返り

2016年も残すところあと数日ということで振り返ってみる。

f:id:shibe97:20161228230741j:plain

社内外活動

デブサミ2016登壇

codezine.jp

かなりの大舞台でさすがに緊張した。
ここぞとばかりに黒曜石プレゼンターをポチって発表して、いい経験になった。

初の海外出張でFluent Conferenceに参加

conferences.oreilly.com

※これは2017年のリンク

Fluentに参加するために英語を勉強し始め、1年越しでなんとか目標のTOEICスコアを獲得し、海外出張の権利を獲得した。
憧れのサンフランシスコに降り立ち、現地の雰囲気を肌で感じることができたので良かった。

レベル的には自分たちでも充分海外勢と戦えるという印象を持った。
英語さえ完璧に使いこなせればw

Scripty #5 参加

scripty.connpass.com

Bikeshed.js 参加

connpass.com 無駄にすごい人たちが集まっていてすごかった(語彙がひどい)

社内 React Meetup 開催

弊社人数が多いので色々隠れた事例が多いだろうということで社内向けにReact Meetupを企画して主催した。 社内フロントエンドの横のつながりができて良かった。

CodeGrid4周年記念パーティ 参加

atnd.org

2年連続で参加。いつも通りゆるい感じで良かった。

管理画面チラ見せ♡ナイト #3 参加

connpass.com

Modern Web App LT!(freee x WACUL) 参加

connpass.com

meyasubacoリリース

meyasuba.co

友人と進めていたWebサービスをリリース。 アプリのグロースハックのためにユーザーのフィードバックを簡単に得る仕組みを提供。

Scripty #6 登壇

scripty.connpass.com CSS Modulesについて発表したけど、発表後すぐに考えがだいぶ変わり、発表内容を封印。

「いまから始めるWebフロントエンド開発」発売

友人と共に進めていた電子書籍が発売された。自分はほぼレビューしかしていないが、意外と簡単に本を出せるということを知った。

React.js Meetup #4登壇

reactjs-meetup.connpass.com

ReactコンポーネントCSSコンポーネントの関係について発表した。
ReactコンポーネントとCSSコンポーネントは1対1なのか問題について // Speaker Deck

W3C TAG デベロッパー・サミット 参加

frontend.connpass.com

YJ meetup 登壇

yj-meetup.connpass.com Houdiniについて発表。学生向けのイベントだったため、難しすぎた説。

社内JavaScriptセミナー 登壇

社内のJavaScript人口を増やすためにセミナーを開催。
自分はnpmのエコシステム周りについて発表した。

アドベントカレンダー2本 公開

shibe97.hatenablog.com

shibe97.hatenablog.com

毎年書こうと思いつつ、チキって書けていなかったアドベントカレンダーを2本書くことができ、はてブ数も上々だったのでやって良かった。

その他

  • DMM英会話1年以上継続中
  • エブリデイ育児
    • 地味にこれがこの1年のほとんどを占める
  • 1年を通して残業ほとんどしてない
  • 社内フロントエンドもくもく会を13回開催
    • こじんまりとやっている
    • 来年はもうちょっと人増やしたい
  • ご近所とBBQ、こどもの国、ボードゲーム
  • 両家ともたくさん遊んだ(けっこう親孝行できた)
    • 毎週末だいたいどちらかの家に遊びに行っていた

総括

思い返してみると、今年も公私ともに色々やったなぁ。

去年の7月に娘が産まれてから、基本的には18時に退勤して、家族との時間を優先するようにした。 おかげで残業もほぼゼロだし、娘が寝てからは自分の趣味開発に力を注ぐことができた。(良い感じの生活スタイル)

個人的には海外のカンファレンスに参加したいというのが長年の想いとしてあったので、3月のサンフランシスコ出張は思い出深い。また、そこで英語の勉強を辞めずにここまで続けてこれたのは我ながら偉いと思う。

デブサミ2016、React.js Meetupなど、著名なイベントに登壇できたのも嬉しかった。社外のつながりも増えた。

家庭と仕事+プライベート活動の両立が出来た1年だったかな。

来年は節目の30歳になってしまうので、より一層頑張りたいところ。

今年もお疲れ様でした〜〜〜!!!

仕事中に閲覧できるTwitterクライアント「Worc」を作った

この記事は Electron Advent Calendar 2016 の9日目の記事です。

作ったもの

f:id:shibe97:20161123233256p:plain

github.com

GitHubで公開しています。Releaseページから最新バージョンの「worc-darwin-x64.zip」というリンクからダウンロード可能です。

以下のランディングページからダウンロードできます。(追記:2017/2/17)

shibe97.github.io

※未署名のため、ダウンロード後は「右クリック」→「開く」という手順で開く必要があります。

動機

Twitterは情報収集のツールとして非常に優秀だと思っていて、特にTech系の情報はTwitter経由が一番速くつかみやすいです。 界隈で強い方々はよくTwitter使って発信しているイメージがあります。

世の中には非常に多くのTwitterクライアントが存在しているのですが、個人的にしっくりくる物がないんですよね。。

これはあくまで僕個人の話ですが、会社内でTwitterを見て情報収集したいという願望があります。
しかし、SNSは娯楽系のイメージが強いため、会社内で堂々と閲覧しているのは憚れます。。

「よし、会社内で普通に見れるようなTwitterクライアントを自作しよう!」

と思ったわけです。

加えて、普段から気になっている技術や、試してみたい構成を突っ込んでみる良い機会ということで作り始めました。

どんなものを作ったか

Twitterが娯楽っぽく見えてしまう要因を考えてみたところ、個人アイコンがずらっと並んでいる感じがそれを助長しているのではないかと考えました。
そこで、できる限りテキストベースにし、派手さをなくした物を作りました。

f:id:shibe97:20161127222012p:plain

意外とアイコンなしでも人を判別できるものです。
しかし時たま、これ誰だっけ…というときのためにハンドル名クリックでアイコンは表示されるようにしました。

f:id:shibe97:20161127222450p:plain

基本的にはデスクトップの左端、または右端においてコーディング横目に閲覧するようなイメージです。
黒い画面の隣だと若干目を引きますが、他のアプリケーションと同時に使うことも想定し、無難な白基調のものにしました。

f:id:shibe97:20161127230808p:plain

とりあえず、ストリーム形式のホームタイムラインと、リストタイムラインは必須だと思い、導入しています。

ハッシュタグの検索結果のストリーム版もエンジニアにとっては欲しい機能かな〜と思ったのですが、勉強会とかに参加していない時はそこまでハッシュタグ追わないかなと考え、後回し中です。(昼のイベントに参加できず、会社でハッシュタグを追うというのはたまにありそうですが)

フリーで落とせるのでぜひ気になる方は使ってみてください! Star、Issue、プルリクも大歓迎です!

ダウンロード

今後の展望

  • メニューからデザインを自由に変更できるようにしたい(背景色など)
  • ストリームAPIの接続が切れた際に自動でつなぎ直したい(やり方がよくわからない)
  • ハッシュタグにリンクをつける
  • リプライ先にリンクをつける
  • 自動アップデート機能(Apple Developer Programへの登録が必要)

などなど、やりたいことはたくさんあります。
地道にアップデートを重ねていく所存です。

名前の由来

ちなみに由来は、仕事という意味を表す「Work」です。
そして、やはりTwitterクライアントということで鳥に関するサービス名が良いと思い、今回は闇に溶け込んで集中を途切れさせない的な意味を込めて、カラスあたりが適役かなと思いました。
カラスは英語で「Crow」です。

勘の良い方はお気づきになったかもしれませんが、「Worc」を逆から読むと「Crow」となります。
奇跡か!!と思いました。

構成

ここから先は技術の話となります。 今回は以下の構成で作りました。

  • Electron
  • React
  • Redux
  • Redux-saga
  • Webpack
  • CSS Modules
  • Jest
  • Enzyme
  • ESLint
  • Atomic Design
  • Travis CI

ズラッと並べると凄そうに見えますね。笑

コンポーネントの内訳

以前、「ReactコンポーネントCSSコンポーネントは1対1なのか問題について」という発表をReact Meetup #4で行ないました。

ReactコンポーネントとCSSコンポーネントは1対1なのか問題について // Speaker Deck

その時はまだ机上の空論感があったので実際に試してみました。
試してみた結果、やはり1対1の粒度で作っていくと綺麗に作れると感じました。

Atomic Designを取り入れ、できるだけ細かい単位で分割し、今回は以下のものをコンポーネントとしてひとまとまりにしました。

Loadingの例を挙げると以下のような感じです。

  • Loading.js
import React from 'react'; 
import styles from './loading.css'; 

export default () => ( 
  <div className={styles.loading} />
); 
  • Loading.test.js
import React from 'react'; 
import { shallow, mount, render } from 'enzyme'; 
import Loading from './Loading'; 

describe('components/atoms/loading', function() { 
  it('should have a "loading" class', function() { 
    expect(shallow(<Loading />).hasClass('loading')).toEqual(true); 
  }); 
}); 
.loading { 
  height: 100%; 
  background: url('./loading.svg') no-repeat center center; 
  background-size: 40px 40px; 
} 

この4つを同一階層に配置します。
Loadingの表示に必要なものが全てまとまっているので、これぞコンポーネントだ!という感想を持ちました。

ちなみに、CSSの読み込みにはCSS Modulesを、ReactコンポーネントのテストにはEnzymeを利用しています。

こうなった経緯

最初からこの方式で作り始められれば良かったのですが、実は途中からリファクタリングで徐々に直していきました。
当初、普通にsassを使っていたのを途中からCSS Modules形式に変更しました。
同時にAtomic Designに基づいた形でコンポーネントを分割し直しました。

Atomic Designの話

今年は結構話題になることも多かったのでご存知の方も多いと思いますが、 Atomic Designとは、コンポーネントをそれ以上分割できない単位(原子)まで分割し、それらを組み合わせて徐々に大きな単位のコンポーネントを形作っていくという考え方です。

通常、以下の5つに分割するのがAtomic Designの考え方になります。

  • Atom(原子)
  • Molecule(分子)
  • Organism(有機体)
  • Template(テンプレート)
  • Page(ページ)

しかし、個人的にはTemplateとPageがあまりしっくり来なかったので、今回はtemplate以上の部分をModuleと名付けた以下の構成にしました。

  • Atoms
  • Molecules
  • Organisms
  • Modules

ページごとにディレクトリが分かれるのでは無く、コンポーネントの大きさ単位でディレクトリが分かれているので、より再利用性に富んだ形であると思います。

進めていくと、「あ〜あのコンポーネントはどこだっけな?」とわざわざ読み込み先からパスを見て探しに行くケースも出てきました。
なんとなくコンポーネントの粒度で当たりはつけられるのですが、そのあたりはちょっと慣れが必要そうです。

Electronはどうだったか

Electronはブラウザプロセスとレンダラプロセスで構成されています。

OSの機能を直接つかったりするのはブラウザプロセス側です。重い処理はブラウザプロセス側でやるのが推奨されていたりもします。プロセス間は通信の手段がいろいろあります。

今回はTwitterクライアントということで、基本的にはTwitterAPIをフルに利用したものなので、ほとんどの処理はレンダラプロセスのみでいけました。

レンダラプロセスに入ってしまえば、後は普通のWebアプリと一緒です。 出力したindex.htmlからバンドルされたJSを読み込み、Reactアプリケーションを構築します。

特有なのはブラウザプロセスだけなので、その部分さえ乗り越えてしまえば楽でした。

Electronのファイルサイズ

今回、136MBという馬鹿でかいサイズとなってしまいました。

当初はJSのバンドルは行なっていなかったのですが、バンドルした方が容量が小さくなるという記事を見つけ、Webpackで1つのファイルにしました。 実は最初はもっと重くて、162MBあったものが、Webpackを通すと135MBになりました。(それでもまぁ重いのですが・・・)

V8が搭載されているため、空の状態でもかなりの容量があるのでしょう。

アプリケーションのダウンロードに時間がかかってしまうのが難点です。
何か良い解決策がありましたら是非教えてください〜。

まとめ

Electron触ってみたいなぁ〜ということでちょうど欲しかったTwitterクライアント作りに挑戦してみました。
ほとんどWeb開発と同じ感覚で作れるのでやりやすかったです。

Chromeの最新版のみに対応すれば良いので、ブラウザ対応という面倒なことを考えなくて良いのが最高でした。

パッケージングやGitHubへのデプロイなど、今まであまりやったことがなかったため良い勉強になりました。

Houdini、それはCSSの進化を促すプロジェクト

この記事は CSS Advent Calendar 2016 の5日目の記事です。
W3C Houdini Task Forceで進められている「Houdini」と呼ばれるプロジェクトの話をします。

FlexBoxの例

f:id:shibe97:20161202210321p:plain

突然ですが、FlexBoxの話をします。 モジュールの横並びには重宝しますよね。 今年になってだいぶ利用が進んだ印象がありますが、随分と前からFlexBoxの仕様は存在していました。

一番最初の草案に遡ってみると、なんと2009年。
7年前です。

2013年くらいからFlexBox良いぞという記事はちらほら出始め、おそらく皆認識はしていました。
が、ブラウザの対応状況などを考慮し実装できずにいたと思います。 仕様定義の議論から実際にここまで普及するまで7年間もかかっているわけです。

流れを整理してみると、以下のようになります。

f:id:shibe97:20161202210428p:plain

提案、議論、仕様書作成あたりに時間がかかるのは仕方がないと思いますが、ブラウザベンダーによる実装やユーザーの利用状況に大きく影響を受けるのはあまりよろしくありません。

何が問題かというと、「後方互換性がない」ことだと思います。

一方、JavaScriptは…

TC39という団体により、ステージング形式が採用されています。
各仕様の策定具合は4つのstageで表されており、毎年の策定時点でStage4に到達していたものが、その次のバージョンのECMAScriptとして入ってきます。

  • Stage1: proposal
  • Stage2: draft
  • Stage3: candidate
  • Stage4: finished

JavaScriptJavaScript自身でPolyfillを作ることができます。 また、Babelを用いて、未対応のブラウザでも新機能が使えるように変換することができます。

つまり、新仕様が実装されてもPolyfillをJS自身で書けるので「後方互換性」があります。

CSSでは同じことができないの?

CSSの場合は多くの処理がブラウザ本体に依存しているため、Polyfillを作ろうとしても作れません。

そこで出てきたのが今回紹介するHoudiniというプロジェクトです。

Houdiniとは

簡単に言うと、JavaScriptからブラウザ本体をいじれるようにしよう!というプロジェクトです。 ブラウザの低レベルAPIを提供し、JavaScriptからブラウザの描画アクションにフックさせ、CSSからアクセス可能にします。

ブラウザのレンダリングの仕組みは以下のようになっています。

f:id:shibe97:20161202210445p:plain

このうち、基本的にはDOMにしかJavaScriptからアクセスすることができません。(一部CSSOMも可能)

そこで、DOM以外の部分もJavaScriptからコントロール可能にし、CSSの制御に干渉できるようにします。

f:id:shibe97:20161202210457p:plain

※これから紹介するものは草案段階のため、今後大いに変更される可能性があります
※また、Editor's draftを元に解説している部分が多いため、参考程度だと思ってください

一つずつ見ていきましょう。

CSS Properties and Values API(2016/12/3時点)

Editor’s draft : https://drafts.css-houdini.org/css-properties-values-api/

  • 仕様あり、Chrome/Firefoxにて開発中
  • CSSカスタムプロパティに型や初期値などを設定できる
CSS.registerProperty({
  name: "--my-color",
  syntax: "<color>",
  initialValue: "black"
});

もし--my-colorというカスタムプロパティを以下のように使おうとした場合、

.thing {
  --my-color: green;
  --my-color: url("not-a-color");
  color: var(--my-color);
}

2つめのurl(“not-a-color”)はinvalidとなるため、1つめのgreenが適用されます。

CSS Parsing API(2016/12/3時点)

仕様:https://wicg.github.io/CSS-Parser-API/

  • 仕様はあるが、ブラウザ実装はまだない
  • CSS3の解析アルゴリズムを公開する
  • CSS Typed OMより簡単で緩やかに型付けされた表現で結果を返す

CSS Typed OM(2016/12/3時点)

Editor’s draft : https://drafts.css-houdini.org/css-typed-om/

  • 仕様あり、Chromeで一部実装あり、Firefoxで開発中
  • CSSの属性と対応する値型をkey-value形式のオブジェクトとして保持できる
  • 属性のバリデーションは実行時に使われる
  • px, em, rem, % などを相互に変換可能になる

Worklet

要素のLayout、Paint、Compositeを司る部分に関しては、Workletという仕組みが導入されます。
Web Workerに似ていて、レンダリングエンジンがWorkletを動かします。
(ただし、オーバーヘッドが大きいので利用は控えめにするべきです)

以下のようなregisterXXX()というAPIをコールし、グローバルスコープにClassとして処理を登録します。
(各UserAgent間でスコープを守るためにメソッドではなくClassを登録する形式にしています)

  • registerLayout
  • registerPaint
  • registerAnimator

グローバルスコープ、つまりwindowオブジェクトに各Workletは紐づいており、各Workletはimportというモジュール読み込みメソッドを持っています。

例:

window.paintWorklet.import('paintworklet.js');

このimportまわりではセキュリティ的な懸念があります。

CSS Layout API(2016/12/3時点)

Editor’s draft : https://drafts.css-houdini.org/css-layout-api/

  • 仕様はあるが、ブラウザ実装はまだない
  • ノードの子をノードのボックス内でどう配置するかを定義できる
  • cssのdisplayプロパティを自作できる
  • flexやtableのようなものを作れる
registerLayout('some-layout', class {
    *layout(space, children, styleMap, breakToken) {
        
        // ...

        return {
            blockSize: blockOffset,
            inlineSize: inlineSize,
            fragments: childFragments,
            // ...
        };
    }
});

regiterLayoutというAPIを用いてレイアウトを登録し、CSSから利用する。

CSS Painting API(2016/12/3時点)

Editor’s draft : https://drafts.css-houdini.org/css-paint-api/

上記のrippleのコード

registerPaint('ripple', class {
  static get inputProperties() { return ['background-color', '--ripple-color', '--animation-tick', '--ripple-x', '--ripple-y']; } 
  paint(ctx, geom, properties) { 
    const bgColor = properties.get('background-color').cssText; 
    const rippleColor = properties.get('--ripple-color').cssText; 
    const x = parseFloat(properties.get('--ripple-x').cssText); 
    const y = parseFloat(properties.get('--ripple-y').cssText); 
    let tick = parseFloat(properties.get('--animation-tick').cssText); 
    if(tick < 0) 
      tick = 0; 
    if(tick > 1000) 
      tick = 1000; 
    ctx.fillStyle = bgColor; 
    ctx.fillRect(0, 0, geom.width, geom.height); 
    ctx.fillRect(0, 0, geom.width, geom.height); 
    ctx.fillStyle = rippleColor; 
    ctx.globalAlpha = 1 - tick/1000; 
    ctx.arc( 
      x, y, // center
      geom.width * tick/1000, // radius
      0, // startAngle
      2 * Math.PI //endAngle
    ); 
    ctx.fill(); 
  } 
});
<button id="ripple"> 
  Click me! 
</button> 
<style>
  #ripple {
    width: 300px;
    height: 300px;
    border-radius: 150px;
    font-size: 5em;
    background-color: rgb(255,64,129);
    border: 0;
    box-shadow: 0 1px 1.5px 0 rgba(0,0,0,.12),0 1px 1px 0 rgba(0,0,0,.24);
    color: white;
    --ripple-x: 0;
    --ripple-y: 0;
    --ripple-color: rgba(255,255,255,0.54);
    --animation-tick: 0;
  }
  #ripple:focus {
    outline: none;
  }
  #ripple.animating {
    background-image: paint(ripple);
  }
</style>
<script>
  window.paintWorklet.import('paintworklet.js');
  const button = document.querySelector('#ripple');
  let start = performance.now();
  let x, y;
  document.querySelector('#ripple').addEventListener('click', evt => {
    button.classList.add('animating');
    [x, y] = [evt.clientX, evt.clientY];
    start = performance.now();
    requestAnimationFrame(function raf(now) {
      const count = Math.floor(now - start);
      button.style.cssText = `--ripple-x: ${x}; --ripple-y: ${y}; --animation-tick: ${count};`;
      if(count > 1000) {
        button.classList.remove('animating');
        button.style.cssText = `--animation-tick: 0`;
        return;
      }
      requestAnimationFrame(raf);
    })
  })
</script>

registerPaint APIでpaint関数を定義し、cssのbackground-colorプロパティから呼び出すことができます。
registerPaint内ではCanvasライクなAPIを用いて描画処理を記述することが可能です。

CSS Compositor API(2016/09/26時点)

解説:https://dassur.ma/things/animworklet/

  • 仕様はあるが、ブラウザ実装はまだない
  • Compositor Worklet の仕様は WICG(Web Incubator Community Group) に移され、Animation Workletに置き換わった
  • レイヤーの重ね合わせのタイミングでスクロール位置やtransform、opacityなどにアクセス可能
  • パララックススクロールなどの実装が可能になる
  • Polyfillによるデモ(なので実際Animation Workletは使われていない):http://googlechrome.github.io/houdini-samples/animation-worklet/sync-scroller/

registerAnimator APIを用いて処理を定義します。

WICGによる解説: github.com

Font Metrics(2016/12/3時点)

Editor’s draft : https://drafts.css-houdini.org/font-metrics-api/

  • 仕様はあるが、ブラウザ実装はまだない
  • フォントのバウンディングボックスのサイズを取得できる

f:id:shibe97:20161202210543p:plain

各仕様のブラウザ実装状況

こちらにまとまっています。 https://ishoudinireadyyet.com/

2016/09/26時点では以下のようになっています。

f:id:shibe97:20161202210603p:plain

まとめ

Houdiniと聞くとWorkletの方に注目が集まってしまいますが、実際に仕様を見てみると、カスタムプロパティの登録であったり、型定義の部分が重要なアップデートだと感じました。

Houdiniによって様々なPolyfillの作成は可能になると思いますが、Workletの部分は実案件として使うにはパフォーマンス面などに影響がありそうです。
また、実装例を見ていても、CSS以上にJavaScriptの記述量が多く、なかなか骨が折れる印象です。
現段階で遊べそうなのはPainting APIくらいでしょうか。
Layout APIが個人的には気になりますが、まだ動かせる環境が無いのが残念です。

Houdiniをあくまで仕様の安定化を促進するためのものと考えれば、有意義なものであると思います。

まだまだ議論の途中段階だと思うので、今後もちょくちょく追っていきたいと思います。

DMM英会話を始めて1年が経ったのでその振り返り

Study

早いもので、DMM英会話を始めてから1年が経ちました。

参考:前回の半年の際の日記 shibe97.hatenablog.com

ペースはのんびりですが、ここまで続けられたことは自分でもビックリ。(学生時代、英語は大の苦手科目でした…)

せっかくなのでここまでの振り返りをセルフインタビュー形式でお送りします。

ズバリ上達したのか?

したか、してないか、で言ったら上達はしていると思うのですが、最近は何となく伸び悩んでいる感じがします。

TOEICも受けていないのでなかなか定量的に判断つきづらいところではあります。 そしてTOEICに反映されるような能力とはちょっと軸が違うので、正直今受けても点が上がっているかと言われたらちょっと怪しいです笑 けどまぁ、リスニング力は上がっていると思うので、少しは点伸びるのかな?今度受けてみます。

会社で海外の提携パートナーを招いたBBQがあったのですが、そこでは向こうの社長と2時間くらいずっと英語で会話できたので、喋る能力は何だかんだ付いたのかな〜という感じです。

振り返ってみると、発音はちょっとよくなった気がします。スカイプで話しているときに自分の声を客観的に聴いてみると、なんか外人っぽい感じになってるなぁと思うことがあります。

日本人って、カッコよく発音しようとするのを恥ずかしがる傾向にあると思うんですよね。(自分もそうでした) 外人相手だと、むしろ相手の発音を自然と真似するようになってきて、日本人の棒読み発音をいつの間にかしなくなったような気がします。

頻度や担当の講師はどうしてた?

f:id:shibe97:20161117222430p:plain

先日ちょうど3,000分を達成してゴールドランクになりました。(別に特典とかはないんだけど) 3,000分ということは1回25分なので120回受けたということです。120回ということはだいたい3日に1回のペースですね。 まぁぶっちゃけすごい頑張っている人に比べればかなり遅いペースだとは思うんですが、継続は力なりということで自分的には満足です。

だいたい月水金か、火木土に受けるようにしているのですが、バタバタと忙しい時期だったり先生の予約が取れなかったりで、均すと3日に1回になったという感じです。

お気に入りの講師が2人いて、行ったり来たりしています。 僕のプランでは予約している授業を受け終わった後でないと次の予約ができません。いざ予約しようとするとだいたい空いてるのは2日後くらいからなので、自然と一日おきスタイルに落ち着きました。もちろん全検索すれば次の日でも空いている講師はいくらでもいますが。自分はお気に入りで固めたいタイプなんです。

ちなみに自分は女性より男性の声の方が聞き取りやすく、ほとんど男性の講師を指名しています。

良いと思っているところは?

やっぱり一番は安さですかね。 1日1回25分のプランだと月4,950円です。 年6万円なので、自己投資としては完全にアリな金額だと思います。 英会話スクールに行くとしたら100万くらいかかっちゃいますよね。 そんな財力自分にはありません。

あとは、意外とやってみないと気付かないところだと思うんですが、実際の英会話スクールと違って良いと思うのは、その場でパソコンで単語の検索ができる点です。 ちょっと単語が分からなくて詰まっても、ササッと調べられるんです。 でも、調べて出てきた単語が日常的に使わないような単語で、逆に相手が知らなかったりすることもあります。

課題だと思っているところは?

単語力。 こればっかりは英会話以外で鍛える必要がありそうです。

自分は本当に本で読む勉強法が大嫌いで全然続かないので、何か良い方法知っている人いたら教えて欲しいです。

教材は?

だいたい、デイリーニュースかデイリーニュースライトかフリートークですね。 一番力が付きそうなのはフリートークな気がします。

デイリーニュースは講師の後に続けて一文ずつ続けて読むので、発音を意識しながら行うと効果ありそうです。

モチベーションは?

今、何をモチベーションに頑張っているのかと聞かれると、特にないんですよね。 続けておいて損はないだろうくらいの感じで続けています。

慣れてきて、レッスン嫌だなぁという気持ちが無くなってきたので特に辞めることもないかなという感じ。

英語が必要になる機会って突然訪れたりしますからね。 あとは海外カンファレンスに行くことかなぁ。(会社のお金で)

なんだかんだ英語を喋れる人ってまだまだ少いので、希少価値はあります。

エンジニアやデザイナーは情報のキャッチアップのためにも英語は使えたほうが良いですよね。 特に最近は勉強会等のイベントでも海外ゲストが呼ばれることも多いので、そういったときに内容を直接理解できたり、懇親会で話ができたりするのはかなりのメリットだと思います。

まとめ

こういうのって継続することが大事だと思うので、とりあえずここまで続いた自分を褒めたい。 そして、ここまで続けると英語に対する恐怖感はほぼ無く、話せる機会があれば話したいくらいな感じにはなれます。

興味がある方はチャレンジしてみては?

eikaiwa.dmm.com

実戦で使える本格的なSVGアニメーションを作る

SVGのアニメーションって綺麗だし、カッコいいですよね。

海外のサイトだったり、dribbbleではよく見るのですが、日本のサイトでは凝ったアニメーションはあまり見かけません。

解説記事も偏っていて、モーフィングだったり、ラインアートの記事はたくさんあるのですが、オブジェクトが重なっていくつもシャシャシャッと動くようなアニメーションを解説している記事となると極端に少なくなります。

というわけで、そんなアニメーションを勉強がてら作ってみました!

まずは全体のイメージを作る

今回は試しに、スマホ上にカード形式のタイトル・ディスクリプションが並ぶようなものを作ってみようと思います。 以下のような感じです。

f:id:shibe97:20160621221322p:plain

Illustratorを使って作成しました。 Illustratorでなくても、パスが書けるようなツールで、なおかつsvg形式で書き出せるものであれば何でも良いと思います。(sketchとかでもいけるのかな、おそらく)

SVG形式で書き出してみる

Illustratorであれば「ファイル」 > 「別名で保存」からSVG形式で保存ができます。

保存したファイルをSubline Text等のエディタで開いてみると、XML形式のSVGを見ることができます。

先ほどの画像をエディタで開いて、ちょっと整形すると次のようになります。

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
    x="0px" y="0px" viewBox="0 0 541 446" style="enable-background:new 0 0 541 446;" xml:space="preserve">
    <g class="smartphone">
        <g class="background">
            <path class="bodyShadow" fill="#ccc" d="M180,348.8v-331c0-6.6,5.5-10.5,12-8l179,70c5.7,2.6,12,9.4,12,16v331c0,6.6-7.5,14.5-16,12l-175-73
          C186.1,362.9,180,357.8,180,348.8z"/>
        <path class="headerShadow" fill="#555" d="M175,36V19.8c0-6.6,10.5-12.5,17-10l179,70c5.7,2.6,12,9.4,12,16v19.7l-5,3.8"/>
        <path class="body" fill="#eee" d="M175,352V21c0-6.6,5.5-10.5,12-8l179,70c5.7,2.6,12,9.4,12,16v331c0,6.6-3.5,11.5-12,9l-179-70
                C181.1,366.1,175,361,175,352z"/>
        <path class="header" fill="#666" d="M175,39.3V21c0-6.6,5.5-10.5,12-8l179,70c5.7,2.6,12,9.4,12,16v20.3"/>
        </g>
        <g class="parts" fill="#333">
            <circle cx="235.7" cy="46.5" r="3"/>
            <path d="M302.3,76.4l-51.5-20.8c-1.3-0.5-1.9-2-1.4-3.3v0c0.5-1.3,2-1.9,3.3-1.4l51.5,20.8c1.3,0.5,1.9,2,1.4,3.3h0
          C305,76.3,303.6,76.9,302.3,76.4z"/>
        </g>
    <g>
        <path class="card1" fill="#fff" d="M167.7,59.1c0-2.4,2-3.9,4.4-3L358.8,131c2.1,1,4.4,3.5,4.4,5.9v68.9c0,2.4-1.3,4.3-4.4,3.3l-186.6-74.8
          c-2.2-1.1-4.4-3-4.4-6.3"/>
            <g class="text1" fill="#7cccbe">
                <polygon points="183,106.5 348.5,172.1 348.5,176.8 183,111.2"/>
            <polygon points="183,77.2 237.5,98.8 237.5,113.5 183,91.8"/>
            <polygon points="183,119.5 348.5,185.1 348.5,189.8 183,124.2"/>
        </g>
        </g>
    <g>
        <path class="card2" fill="#fff" d="M167.7,147.1c0-2.4,2-3.9,4.4-3L358.8,219c2.1,1,4.4,3.5,4.4,5.9v68.9c0,2.4-1.3,4.3-4.4,3.3l-186.6-74.8
          c-2.2-1.1-4.4-3-4.4-6.3"/>
            <g class="text2" fill="#7cccbe">
            <polygon points="183,194.5 348.5,260.1 348.5,264.8 183,199.2"/>
            <polygon points="183,165.2 237.5,186.8 237.5,201.5 183,179.8"/>
            <polygon points="183,207.5 348.5,273.1 348.5,277.8 183,212.2"/>
        </g>
        </g>
    <g>
        <path class="card3" fill="#fff" d="M167.7,235.1c0-2.4,2-3.9,4.4-3L358.8,307c2.1,1,4.4,3.5,4.4,5.9v68.9c0,2.4-1.3,4.3-4.4,3.3l-186.6-74.8
          c-2.2-1.1-4.4-3-4.4-6.3"/>
            <g class="text3" fill="#7cccbe">
            <polygon points="183,282.5 348.5,348.1 348.5,352.8 183,287.2"/>
            <polygon points="183,253.2 237.5,274.8 237.5,289.5 183,267.8"/>
            <polygon points="183,295.5 348.5,361.1 348.5,365.8 183,300.2"/>
        </g>
        </g>
    </g>
</svg>

できる限り分かりやすいようにclassを振りました。 この整形作業が最も重要なポイントだと思います。

どのパスが画像でいうどの部分なのかを把握し、分かりやすく配置し直すイメージです。

<path>は文字通りパスを表し、<g>は複数のパスをグループ化したものになります。

図で示すと以下のような構成になっています。

f:id:shibe97:20160623191529p:plain

動きをつけてみる

いよいよ本題です。 こいつに動きをつけてみます。

最初は素のSVGだけで何とかならないものかと頑張っていたのですが、かなり辛かったので、TweenMaxというライブラリを使用しました。 TweenMaxを用いると、jQueryライクにメソッドチェーンで直感的に動きをつけることができます。

greensock.com

まずはセレクタで要素を取得する

JSでSVGタグで作成した各要素を取得してきます。

var xmlns="http://www.w3.org/2000/svg",
    select = function(s) {
        return document.querySelector(s);
    },
    selectAll = function(s) {
        return document.querySelectorAll(s);
    },
    smartphone = select('.smartphone'),        
    background = select('.background'),
    parts = select('.parts'),
    card1 = select('.card1'),
    card2 = select('.card2'),
    card3 = select('.card3'),
    text1 = selectAll('.text1 polygon'),
    text2 = selectAll('.text2 polygon'),
    text3 = selectAll('.text3 polygon');

transformの基準点を中心にする

これから要素を拡大したり動かしたりするのですが、普通にtransformを行うと要素の左上が基準となってしまい、微妙なアニメーションが出来上がってしまいます。

なので、物にもよりますが、拡大させたい要素にはtransformOriginを設定してあげると良いでしょう。

TweenMax.set([background, card1, card2, card3, text1, text2, text3], {
    transformOrigin : '50% 50%'
});

第一引数に要素を配列で渡して、第二引数にCSSを指定するイメージでオブジェクトを渡します。

タイムラインを作成する

実際に動かしてみます。

ここではTimelineMaxというclassを用います。

TimelineMaxのインスタンスを生成時には様々なパラメータが指定できます。

  • repeat
    • 何回リピートさせるか
    • 例えば1を指定すると、計2回再生される
    • 無限にリピートさせたい場合は-1を指定する
  • delay
    • 開始前に何秒遅延させるか
  • yoyo
    • アニメーションが最後までいったら逆回転で再生させるかどうか
    • Boolean値で指定
  • paused
    • デフォルトはfalseとなっており、アニメーションは自動的に開始されてしまう
    • trueを指定した場合は、JSで好きなタイミングでplay()を呼べば開始できる

今回は以下のように指定しました。

var timeline1 = new TimelineMax({repeat:0, delay:1, yoyo:false, paused:false});

それ以降はメソッドチェーンでアニメーションを記述していきます。

timeline1.from(background, 0.5, {
    scale: 0,
    ease: Power1.easeOut
})
.from(parts, 0.5, {
    opacity: 0,
    ease: Power1.easeOut
})
.from(card1, 0.5, {
    scale: 0
})
.staggerFrom(text1, 0.5, {
    scale: 0
}, 0.1)
.from(card2, 0.5, {
    scale: 0
})
.staggerFrom(text2, 0.5, {
    scale: 0
}, 0.1)
.from(card3, 0.5, {
    scale: 0
})
.staggerFrom(text3, 0.5, {
    scale: 0
}, 0.1);

ここまでで各要素が順番に出現していくアニメーションを作ることができました。

各メソッドの説明

from()

  • 第一引数:対象の要素
  • 第二引数:何秒間でアニメーションさせるか
  • 第三引数:ここで指定した状態から、SVGタグが示す元の状態までアニメーションを行う

to()

  • 第一引数:対象の要素
  • 第二引数:何秒間でアニメーションさせるか
  • 第三引数:SVGタグが示す元の状態から、ここで指定した状態からまでアニメーションを行う

staggerFrom()

これがかなり便利。 複数要素を指定して、少しずつタイミングをズラしながらアニメーションをさせることができる。

  • 第一引数:対象の要素
  • 第二引数:何秒間でアニメーションさせるか
  • 第三引数:ここで指定した状態から、SVGタグが示す元の状態までアニメーションを行う
  • 第四引数:複数指定した要素たちの開始タイミング何秒ごと遅延させるか

staggerTo()

staggerFromの逆バージョン。こちらも便利。

  • 第一引数:対象の要素
  • 第二引数:何秒間でアニメーションさせるか
  • 第三引数:SVGタグが示す元の状態から、ここで指定した状態からまでアニメーションを行う
  • 第四引数:複数指定した要素たちの開始タイミング何秒ごと遅延させるか

Power1.easeOut

これはメソッドではないですが、一緒に説明してしまいます。

TweenMax側で用意されているeasingです。

どんなものがあるかは以下のease-visualizerを見ると分かりやすいです。(超便利!)

greensock.com

さらに並行してタイムラインを追加する

ついでにもうひとつタイムラインを加えてみます。

上下にふわふわと浮いているようなアニメーションをつけてみます。

var timeline2 = new TimelineMax({repeat:-1, delay:1, yoyo:true, paused:false});
timeline2.from(smartphone, 2, {
    y: 10,
    ease: Power1.easeInOut
});

yoyo = trueにすることで上下に行ったり来たりさせています。

結果、こんなものができました。

f:id:shibe97:20160622215028g:plain

最終的なコードはgistに置いておきます。

card type animation

まとめ

いかがだったでしょうか。

慣れてしまえば、このレベルのアニメーションであればサクサク作ることができそうです。

TweenMaxには他にもまだまだ機能があったので、おいおい試してみたいと思います。

おまけ

せっかくなので、私が運営しているサービスであるMeyasubacoのトップページに今回の成果物を組み込んでリニューアルしてみました。

meyasuba.co

ユーザーがある一定位置までスクロールしてきたらアニメーションがスタートするようにしてあります。

こちらももし良ければチェックしてみてください。