SNS や検索エンジンからのトラッキングについて雑感

まず前提として

  • HTTPS から HTTP へ遷移した場合 HTTP 側 (遷移先) でリファラが取れない
  • 最近の SNS、検索エンジンは SSL (HTTPS) がデフォルトに

という背景があり、盆槍していると流入元の正確なトラッキングができなくなってしまう。サービス側で出来る対策としては…

  1. サービス側の通信も SSL (HTTPS) 化する
  2. (Google Analytics を使っているのなら) utm パラメータで流入元をトラッキング

といった方法がありそう。しかし問屋はそうはおろさない。

1 番目の方法は SSL 証明書の取得に金銭的・時間的コストが掛かることは当然として、認証局の立場から見た時にドメインの所有者に確実に証明書を届ける必要が生じるため、取得するドメインにおいてメールが受信できる環境を作っておかなければならない点に注意。(取得するドメインにてメールを受信 = ドメインの所有者であることの証明に)

2 番目の方法はパラメータが付与され URL が長くなってしまうため、特に Twitter において文言が制限されることを留意されたし。短縮 URL サービスを使うことで URL を短くすることができるが、動的に URL を複数生成する場合において、短縮 URL サービスの API 呼び出し制限に引っかかる可能性を考慮しなければならない。また転送先のサービスが、短縮 URL サービスの規約に抵触する場合もあるので注意。(例えばキャンペーン等に利用できないなど。)

2 番のもう一つの注意点として、流入先のランディングページで 30x 系のリダイレクトをすると、クライアント側での Analytics による計測が出来ない。かといって、サーバサイドで Analytics のビーコンを送信する仕様にすると、クローラーやボットからのアクセスも送信してしまうため計測値にノイズが乗る。そういうわけで、ランディングページでリダイレクトは絶対ダメ。

この辺りをすっ飛ばしてしまうと、Google Analytics を導入しているのにもかかわらず DB 内のデータや Nginx (Apache) のアクセスログを収集・集計する必要が生じたり、愚直に合算値を出すだけで済むはずの作業が、統計的手法を使って近似値を求めなければならなかったりしますので、効果測定をされる方におかれましては、この点ご留意いただけますようお願い申し上げます。

あれっ、もしや知らないの自分だけだった……?!もっとベターな方法をご存知の方はこっそり教えて下さい〜!以上、チラ裏でした〜!

This was posted 5 months ago. It has 5 notes and 0 comments.

MyDB の紹介

Node.js Advent Calendar 18 日目の記事です。二日も遅刻してしまいました。切腹ものです。先日は hiyokur さんによる Sails.jsを使ってハマったところとか でした。

今回は MyDB の話をします。まだ調査したばかりなので間違いなどありましたらご指摘下さいませ…

MyDB とは Realtime で Reactive な Web Application を作るための Node.js 向けのライブラリです。今年初頭に LearnBoost のメンバーによって Cloudup というサービスがリリースされましたが、そのバックエンドに使われているようです。(現在は WordPress.org などを運営している Automattic 社に買収されました。)

今年の夏頃に MyDB の概要がアナウンスされ12 月に GitHub 上でオープンソースとして公開されました。開発のリードは Socket.IO で著名な Guillermo Rauch さんです。

MyDB の概要

こんにちのウェブアプリケーションは応答性が求められます。

Ajax 通信は当たり前となり、WebSocket のようなリアルタイムでサーバと通信する技術が登場し、HTTP 2.0 ではレイテンシが小さくなるよう仕様が検討されていたり、クライアントサイドではオブジェクトの変更に対して UI がリアクティブに変更される世界観がトレンドとなりつつあります。

MyDB では、サーバ上のデータに対してクライアントから subscribe し、データの変更を購読し、サーバ側でデータを更新すると、購読しているクライアントに変更を通知する仕組みになっています。(いわゆる pub/sub)

Socket.IO との大きな違いは、ドキュメント指向であること、それゆえにデータの永続性が重視されているというところでしょうか。現在のところ MongoDB への永続化がサポートされています。

Realtime/Reactive な姿勢はどちらかと言えば Socket.IO より Meteor に似ていますが、Meteor がIsomorphic さを重視しドラスティックかつアクロバティックなアプローチを採用しているのに対して MyDB はより保守的で RESTful の考え方がベースになっているため、とっつきやすさがあると思います。

説明はこのぐらいにして、MyDB を利用したデモアプリケーションのコードを見てみましょう。

デモアプリ

ここでは変更通知を受け取る簡単なデモアプリケーションを作成します。

ミドルウェアに MongoDB と Redis を使います。Mac の場合は Homebrew で事前にインストールしておきます。

$ brew install mongodb redis
$ redis-server &
$ mongod &

クライアント側のライブラリは Component で書かれているため、事前に component をグローバルインストールしておきます。最新版の component ではリダイレクトするリポジトリからコードを正しく取得できない不具合があり mydb-client がインストール出来ないため、0.17.5 をインストールしておきます。

$ npm install -g component@0.17.5

次に、サーバ側で利用するライブラリを package.json に書いてしまいます。

package.json

{
  "name": "mydb-demo",
  "dependencies": {
    "express": "~3.4.7",
    "mydb": "~3.1.1",
    "mydb-driver": "~0.5.2"
  }
}

npm install で依存性を解決します。

次に component.json でクライアント側の依存ライブラリを書いてしまいます。

{
  "name": "mydb-demo",
  "dependencies": {
    "cloudup/mydb-client": "*"
  }
}

component install && component buildbuild.js を生成しておきます。

MyDB サーバは HTTP サーバと協調して動作するため、まずは定番の Express を使ってシンプルなアプリケーションを書いてみます。

app.js

var http = require('http');
var express = require('express');

var app = express();
var server = http.createServer(app);

app.set('port', process.env.PORT || 3000);

server.listen(app.get('port'), function() {
  console.log('listening on port %s', app.get('port'));
});

出来ました。これに MyDB をセットアップしてみます。MongoDB のデータベース名は mydb-demo とします。

app.js

var http = require('http');
var express = require('express');
// Connect/Express 用ミドルウェア
var expose = require('mydb-expose');
// DB ドライバ
var db = require('mydb-driver')('localhost/mydb-demo');

var app = express();
var server = http.createServer(app);
// MyDB 本体
var mydb = require('mydb')(server);

app.set('port', process.env.PORT || 3000);
app.use(express.json());

// ミドルウェアの登録
app.use(expose({
  mongo: 'localhost/mydb-demo',
  url: function() {
    return 'http://localhost:' + app.get('port');
  }
}));

app.use(express.static(__dirname + '/build'));
app.use(express.static(__dirname + '/public'));

それぞれのライブラリの役割は以下のとおりです。

  • mydb
    • MyDB 本体。
    • ドライバでのデータの変更とクライアントの通知の橋渡しを行う。
    • クライアントとは Engine.IO を使い push 通知を行う。
  • mydb-expose
    • MyDB と Connect/Express を組み合わせて使うためのミドルウェア。
  • mydb-driver
    • MongoDB のドライバ。
    • API は monk 互換。
    • データ更新時に Redis にて MyDB へ通知 (publish) する。

簡単のため事前にデータを入力しておきます。

$ echo "db.users.insert({ name: 'nulltask', url: 'http://null.ly', interest: 'Node.js' })" | mongo mydb-demo

次はサーバ側に、データを読み出す部分とデータを更新する部分を実装してみます。

var db = require('mydb')();

// users コレクションのハンドラを取得
var users = db.get('users');

// データ取得
app.get('/users/nulltask', function(req, res) {
  var doc = users.findOne({ name: 'nulltask' });
  res.send(doc);
});

// データ更新
app.post('/users/nulltask', function(req, res) {
  var update = req.body;
  var doc = users.findOne({ name: 'nulltask' });
  delete update.name; // name を上書きしないよう念のため
  doc.success(function(doc) {
    res.send(users.update(doc._id, { $set: update }));
  });
});

monk や mongoose を利用したことのある人なら今までのイディオムと殆ど変わらないことがわかるかと思います。

次にクライアント側のコードを実装してみます。

var mydb = require('mydb')();

// mydb.get に HTTP 側で GET するための URL を指定し変更を subscribe する
var user = mydb.get('/users/nulltask', function() {

  // subscribe 後に実行されるコールバック
  // ドキュメントのデータが user オブジェクト経由でアクセスできる
  console.log(user);

  user.on('url', function(val) {
    console.log(val); // 変更後の url の値
    console.log(user.url); // 自動的に user オブジェクトも更新される
  });

  user.on('interest', function(val) {
    console.log(val); // 変更後の interest の値
    console.log(user.interest); // 自動的に user オブジェクトも更新される
  });
});

MyDB を require した時点で Engine.IO を介してサーバとの通信が確立されます。mydb.get でデータの変更を subscribe し、user.on('プロパティ名') でデータの変更をハンドリングします。

初回のデータ取得は HTTP 経由で行われますが、データの更新は Engine.IO 経由でサーバから push されます。

curl などを使ってサーバにデータを POST してみるとブラウザ上で変更が通知されるかと思います。

$ curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{ "url": "http://twitter.com/nulltask" }'  http://localhost:3000/users/nulltask
$ curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{ "interest": "AWS" }'  http://localhost:3000/users/nulltask

以上、駆け足となってしまいましたが MyDB の紹介を終わります。テストコードを読むと他にもさまざまな機能がありそうですので引き続き調査していこうと思います。

Node.js Advent Calendar 19 日目の記事は takeshy さんによる Socket.IO用 フレームワーク socket.io-reqev です。

リンク

This was posted 9 months ago. It has 1 note and 0 comments.

xbc-shim が見つからないだとかで node-canvas がインストール出来ない時

Cairo も XQuartz も入っているのに node-canvas のインストール時に失敗した時。環境は Maverics (10.9)

$ npm install canvas
npm http GET https://registry.npmjs.org/canvas
npm http 200 https://registry.npmjs.org/canvas
npm http GET https://registry.npmjs.org/canvas/-/canvas-1.1.2.tgz
npm http 200 https://registry.npmjs.org/canvas/-/canvas-1.1.2.tgz
npm http GET https://registry.npmjs.org/nan
npm http 200 https://registry.npmjs.org/nan
npm http GET https://registry.npmjs.org/nan/-/nan-0.4.4.tgz
npm http 200 https://registry.npmjs.org/nan/-/nan-0.4.4.tgz

> canvas@1.1.2 install /Users/nulltask/node_modules/canvas
> node-gyp rebuild

gyp http GET http://nodejs.org/dist/v0.10.22/node-v0.10.22.tar.gz
gyp http 200 http://nodejs.org/dist/v0.10.22/node-v0.10.22.tar.gz
Package xcb-shm was not found in the pkg-config search path.
Perhaps you should add the directory containing `xcb-shm.pc'
to the PKG_CONFIG_PATH environment variable
Package 'xcb-shm', required by 'cairo', not found
gyp: Call to './util/has_cairo_freetype.sh' returned exit status 0. while trying to load binding.gyp
gyp ERR! configure error 
gyp ERR! stack Error: `gyp` failed with exit code: 1
gyp ERR! stack     at ChildProcess.onCpExit (/Users/nulltask/.brew/Cellar/node/0.10.22/lib/node_modules/npm/node_modules/node-gyp/lib/configure.js:467:16)
gyp ERR! stack     at ChildProcess.EventEmitter.emit (events.js:98:17)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (child_process.js:789:12)
gyp ERR! System Darwin 13.0.0
gyp ERR! command "node" "/Users/nulltask/.brew/Cellar/node/0.10.22/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /Users/nulltask/node_modules/canvas
gyp ERR! node -v v0.10.22
gyp ERR! node-gyp -v v0.11.0
gyp ERR! not ok 
npm ERR! canvas@1.1.2 install: `node-gyp rebuild`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the canvas@1.1.2 install script.
npm ERR! This is most likely a problem with the canvas package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     node-gyp rebuild
npm ERR! You can get their info via:
npm ERR!     npm owner ls canvas
npm ERR! There is likely additional logging output above.

npm ERR! System Darwin 13.0.0
npm ERR! command "/Users/nulltask/.brew/Cellar/node/0.10.22/bin/node" "/Users/nulltask/.brew/bin/npm" "install" "canvas"
npm ERR! cwd /Users/nulltask
npm ERR! node -v v0.10.22
npm ERR! npm -v 1.3.14
npm ERR! code ELIFECYCLE
npm ERR! 
npm ERR! Additional logging details can be found in:
npm ERR!     /Users/nulltask/npm-debug.log
npm ERR! not ok code 0

xbc-shim が無いと。

$ find / -name xcb-shm.pc 2> /dev/null
/opt/X11/lib/pkgconfig/xcb-shm.pc

ふむふむ。環境変数 PKG_CONFIG_PATH/opt/X11/lib/pkgconfig を指定してみると…

$ PKG_CONFIG_PATH=/opt/X11/lib/pkgconfig npm install canvas

> canvas@1.1.2 install /Users/nulltask/node_modules/canvas
> node-gyp rebuild

  CXX(target) Release/obj.target/canvas/src/Canvas.o
  CXX(target) Release/obj.target/canvas/src/CanvasGradient.o
  CXX(target) Release/obj.target/canvas/src/CanvasPattern.o
In file included from ../src/CanvasPattern.cc:10:
../src/CanvasPattern.h:24:9: warning: private field '_width' is not used
      [-Wunused-private-field]
    int _width, _height;
        ^
../src/CanvasPattern.h:24:17: warning: private field '_height' is not used
      [-Wunused-private-field]
    int _width, _height;
                ^
2 warnings generated.
  CXX(target) Release/obj.target/canvas/src/CanvasRenderingContext2d.o
  CXX(target) Release/obj.target/canvas/src/color.o
  CXX(target) Release/obj.target/canvas/src/Image.o
  CXX(target) Release/obj.target/canvas/src/ImageData.o
  CXX(target) Release/obj.target/canvas/src/init.o
  CXX(target) Release/obj.target/canvas/src/PixelArray.o
  CXX(target) Release/obj.target/canvas/src/FontFace.o
In file included from ../src/FontFace.cc:7:
../src/FontFace.h:27:15: warning: private field '_ftFace' is not used
      [-Wunused-private-field]
    FT_Face   _ftFace;
              ^
1 warning generated.
  SOLINK_MODULE(target) Release/canvas.node
  SOLINK_MODULE(target) Release/canvas.node: Finished
canvas@1.1.2 node_modules/canvas
└── nan@0.4.4

おっけーおっけー。

This was posted 10 months ago. It has 1 note and 0 comments.

JavaScript でグローバル領域を汚染しているプロパティを見つける

グローバルオブジェクトをガンガン汚染してる得体の知れない HTML をメンテせざるを得ないこともあったりします。(ちょっと韻踏んでる)

iframe で生成したピュアな window オブジェクトと差分を取ることでグローバルリークしているプロパティを探せます。ちなみに Tumblr のダッシュボードはこんな感じ。

["ENVIRONMENT","Tumblr","tinyMCE","_sf_startpt","language_for_tinymce","l10n_str","localized_str","i","localized_str_ajax_error","localized_str_loading","localized_str_remove_tag","localized_str_promote_this_post_in","localized_str_promote","localized_str_promote_warning_none","localized_str_promote_warning_singular","localized_str_promote_warning_plural","localized_str_markdown","localized_str_html_enabled","localized_str_bold","localized_str_italic","localized_str_strikethrough","localized_str_enter_the_url","localized_str_insert_link","localized_str_adding_tags","localized_str_removing_tags","localized_str_only_100_posts","localized_str_select_posts_to_edit","localized_str_select_posts_to_delete","localized_str_enter_tags_to_add","localized_str_select_posts_to_tag","localized_str_wait_for_last_operation","localized_str_confirm_delete_selected_posts","localized_str_my_posts","localized_str_search_tumblr","localized_str_my_dashboard","localized_str_search_posts","localized_str_search_help","localized_str_search_by_tag","localized_str_search","localized_str_this_tumblelog","localized_str_you_answered","localized_str_thank_you","localized_str_confirm_block_this_person","localized_str_cancel","localized_str_reply","localized_str_250_max","localized_str_confirm_block","localized_str_new_posts","localized_str_over_max_file_size_mb","localized_str_empty_query","localized_str_image_upload","localized_str_old_password","localized_str_password_mismatch","localized_str_confirm_password","localized_str_valid_email","localized_str_unsaved_changes","$","jQuery","_","Backbone","video_thumbnail_hover","load_typekit","select_field","get_cookie","set_cookie","unset_cookie","trackable_follow","toggle_video_embed","cycle_video_thumbnails","pano_iframe_preloader","flashVersion","renderVideo","replaceIfFlash","Ajax","Spinner","AutoPaginator","loading_next_page","BeforeAutoPaginationQueue","AfterAutoPaginationQueue","add_to_image_queue","process_image_queue","start_processing_image_queue","increment_note_count","decrement_note_count","GIF","jQuery110205483331584837288","_gaq","tumblr_custom_tracking_url","_gat","gaGlobal","_qevents","_comscore","__qc","quantserve","uh","YAHOO","__","udm_","ns_p","COMSCORE","dialog_translations","audiojs","audiojsInstance","tinymce","data-mce-expando","Markdown","mejs","JpegMeta","MediaElement","createSetter","createGetter","vdata1384174873883","_V_","VideoJS","default_search_text","next_page","toast_translations","img_obj"]

多すぎ!

追記 (2013/11/12 2:46)

@haya14busa さんにブックマークレット化して頂きました。お試しあれ。

This was posted 10 months ago. It has 10 notes and 0 comments.

Homebrew で OpenCV が、無い!

いつの間にか Homebrew から OpenCV が消えている模様。コミットログを見ると別のリポジトリに動いているっぽい。

brew tap コマンドでリポジトリを追加すればインストールできるようになる。

$ brew tap homebrew/science
$ brew install opencv
==> Downloading http://downloads.sourceforge.net/project/opencvlibrary/opencv-un
######################################################################## 100.0%
==> cmake -DCMAKE_INSTALL_PREFIX=/Users/nulltask/.brew/Cellar/opencv/2.4.6.1 -DC
==> make
...

あるはずの物がないのってちょっと焦る。

This was posted 10 months ago. It has 5 notes and 0 comments.

Heroku で 3 分で静的サイトを公開しよう

Heroku 使ってますか?便利なので使いましょう。Rails で有名な PaaS ですが、ここでは静的サイト公開用に特化して使う方法を紹介します。

記事の内容と被りますが、スクリーンキャストも参考にして下さい。

まず、Heroku のアカウントを持っていない人はサインアップしましょう。

サクッと以下のコマンドを入力して、ひな形からサイトを作ります。

$ git clone https://github.com/nulltask/heroku-static-provider.git my-site
$ cd my-site
$ heroku create
$ git push -u heroku master
$ heroku open

ブラウザが開いて “Hello world” と表示されれば OK です。ファイルを編集して git commit と git push して内容を更新します。

$ git commit -a -m 'some commit message'
$ git push heroku master
$ heroku open

Basic 認証もかけられます。

$ heroku config:set USER=username
$ heroku config:set PASS=password

タイトルは若干釣りですね。サインアップしてたら 3 分じゃ出来ないっていう……。

This was posted 1 year ago. It has 7 notes and 0 comments.

Socket.IO の同時接続数とレイテンシの関係 (EC2 編)

Socket.IO でちょっとしたアクション系のゲームを作ることになり特にレイテンシがシビアになりそうなので調査。

テストした条件などはこんな感じ。

  • アプリは Node.js 0.10.15 + Socket.IO 0.9.16 + Express 3.3.4 で実装
  • ディストリビューションは Amazon Linux AMI 2013.3
  • m1.{xlarge,large,medium,small} の 4 種類のインスタンスタイプを使用
  • クライアントは専用の m1.xlarge のインスタンスから接続
  • 同時接続数は 1, 10, 50, 100, 500, 1000, 1500, 2000, 3000, 4000, 5000 で調査
  • 同時接続数の確認は HTTPServer#getConnections で取得できる値で実施
  • /etc/security/limit.conf で同時にオープンできるファイル数に 65535 を指定
  • トランスポートは WebSocket のみ
  • クラスタリング無し

以下のようなクライアントをぶら下げた状態で、100 ステップの平均レイテンシを計測。コネクション確立後、ping, pong でループを作ってストレスを与え続ける (緑色の枠)

レイテンシの計測は guille の latency-io を改造したもので確認。

計測結果 (レイテンシの単位はミリ秒)

グラフ

わかったこと

  • 当然だがインスタンスタイプに応じてレイテンシが変わる
  • CPU 使用率に余裕がある状況だと接続数が増えてもレイテンシは変わらない
  • 1500 を超えたあたりから性能が劣化し始める
    • m1.small は 500 を超えたあたりから性能劣化

最悪値は大体平均値の二倍。アプリの処理分もあるのとポーリングが無いので、実際の値は平均値の三倍ぐらいってとこだろうか。

This was posted 1 year ago. It has 13 notes and 0 comments.

Apache Apollo と Node.js で MQTT を試してみる

ひょんなことから Apache Apollo と Node.js で MQTT 経由で pub/sub するかもしれなくなったので、その検証のメモ。

まず、Apache Apollo をインストールする。Homebrew から一発で。

$ brew install apollo

インストールが終わったら Broker というものを作る。

$ `brew --prefix`/Cellar/apollo/1.6/bin/apollo create `brew --prefix`/var/apollo

Broker を作ったら Apollo を起動する。

$ `brew --prefix`/var/apollo/bin/apollo-broker

apollo のコンソールが起動するので run と入力して broker を起動する。

http://localhost:61680/broker から Web のインタフェースが表示できるか確認する。デフォルトの ID は admin, Password は password でログイン出来る。

ここからは Node.js 側の準備。よくメンテナンスされていてドキュメントも豊富なので MQTT.js というライブラリを選択。npm からインストール。

$ npm install mqtt

Subscribe する側のコード。

var mqtt = require('mqtt');
var client = mqtt.createClient(61613, { username: 'admin', password: 'password' });

client.subscribe('message');

client.on('message', function() {
  console.log(arguments);
});

Publish する側のコード。一秒置きに適当なメッセージを送ってみる。

var mqtt = require('mqtt');
var client = mqtt.createClient(61613, { username: 'admin', password: 'password' });

setInterval(function() {
  client.publish('message');
  client.publish('message', 'foo');
  client.publish('message', Date.now().toString());
}, 1000);

publish すると subscribe している側でこんなログが出るはず。

{ '0': 'message',
  '1': '',
  '2': 
   { cmd: 'publish',
     retain: false,
     qos: 0,
     dup: false,
     length: 9,
     topic: 'message',
     payload: '' } }
{ '0': 'message',
  '1': 'foo',
  '2': 
   { cmd: 'publish',
     retain: false,
     qos: 0,
     dup: false,
     length: 12,
     topic: 'message',
     payload: 'foo' } }
{ '0': 'message',
  '1': '1374848125563',
  '2': 
   { cmd: 'publish',
     retain: false,
     qos: 0,
     dup: false,
     length: 22,
     topic: 'message',
     payload: '1374848125563' } }

こんな具合で出来た。MQTT なんて使ったことねーわーって感じでしたが、ライブラリが充実していたので簡単に出来ました。

This was posted 1 year ago. It has 3 notes and 0 comments.

Express とかで自動で grunt watch

Grunt と組み合わせて Node.js で Web アプリ作るときに、node とは別に grunt を起動するのが面倒なので、child_process を使って Node のアプリを立ち上げる時に grunt watch するようにしてみた。

var express = require('express');
var path = require('path');
var cp = require('child_process');

var app = express();
app.set('port', process.env.PORT || 3000);

// grunt watch
app.configure('development', function() {
  var cmd = path.join(__dirname, './node_modules/.bin/grunt');
  var args = ['watch'];
  var grunt = cp.spawn(cmd, args);

  grunt.stdout.pipe(process.stdout);
  grunt.stderr.pipe(process.stderr);
  
  function terminate() {
    grunt.kill('SIGKILL');
  }
  
  process.on('exit', terminate);
});

app.listen(app.get('port'), function() {
  console.log('listening on port %s', app.get('port'));
});

親プロセスが終了したときに、grunt にも SIGKILL を送信しておかないと、Ctrl+C とかで Node のプロセスを落としても grunt watch が残り続けてしまうので注意。

上のコードはミドルウェアの登録とかを端折っているので、いい感じに組み込んで使うといいと思います。

This was posted 1 year ago. It has 6 notes and 0 comments.

CSS3 でロールオーバーの transition を非対称に行う

CSS3 の transition を使ってロールオーバーを行うとき、ポインタが載った時と外れた時で非対称にアニメーションしたいとき。

ポイントは次の通り。

  1. :hover 擬似要素のセレクタにポインタが載った時の transition を指定
  2. 擬似要素なしのセレクタにポインタが外れた時の transition を指定

コードはこんな感じ。(vendor prefix とかは各自で良い感じで)

.box {
  width: 100px;
  height: 100px;
  background-color: red;

  transition-property: background-color;

  /* ポインタが外れた時 */
  transition-duration: 1s;
  transition-timing-function: ease-in;
}

.box:hover {
  background-color: yellow;
  
  /* ポインタが載った時 */
  transition-duration: 0.1s;
  transition-timing-function: ease-out;
}

実際のデモ (jsfiddle)

This was posted 1 year ago. It has 8 notes and 0 comments.