2011年12月3日土曜日

TwitterのTimeLineを簡易記録するWebブラウザ動作のTwitterクライアント作りました

TLRecという名前の、ChromeとSafari(iOSのではなくMac OS Xの)とFirefoxで動作する、Twitterクライアントを作りました。
(2011/12/16: Firefox対応)

TLRec - Personal TimeLine Recorder -

TLRecは自分のTimeLine(Home、Mention、List、Search)を日時指定予約して記録し、あとで再生できます。実況TL参加したかったのに「その時間会社だから無理」「深夜起きてらんない」「ニチアサ寝坊した…」なんてことがある人で、自宅はPC基本立ち上げっぱなし、みたいな人向けです。


時間とTLを指定して予約しておけばサーバー側で勝手にログ取っておいてくれると思った?残念さやかちゃんでしたー!のではなく、サーバー側はそういうことしません。TLの記録をするにはPCがネットワークに接続していること、ブラウザでTLRecにSign in後のサイトを開いていること(最小化しててもOK)が必要です。あ。PCの時刻設定にしたがって記録開始・終了を行うので、時間ずらしてる場合はそれを考慮して予約するか、PCの時刻設定直すか、してください。


要するに、ユーザー側環境で記録を行います。


ブラウザのlocalStorage機能を利用してTLを保存します。そのPCのそのブラウザ、にしかTLデータは残りません。サーバー側にはデータは残りません。共用PC等での利用はあまりオススメしません。そういう仕様なので、記録したTLの再生を行うには、記録したPC、ブラウザで、TLRecにSign inすることが必要です。

ちなみにTL再生中にツイートすると、その当時にツイートしたかのように表示します。そして、Twitterへ実際に投稿するか気分だけの偽ポストか選べます。基本は偽です。実況参加してる風ではあるけれど、リアルTLは違う流れですので。


localStorage容量はブラウザ仕様上たぶん5MBです。何ツイートぶんくらいか前に計算したけど忘れました。いっぱいになったらTL記録できませんので、適宜記録済みTL削除してください。んでもってそんな容量を鑑みて、予約・記録保持できるTLは3つまでです。


記録は見えないところで行われますので、その間普通に他のTLみたりできます。もちろんPostも。ただあまりアレコレやりすぎるとAPIアクセス制限値を越えてしまい、記録のためのTL取得もできなくなるので、ご注意ください。

記録以外の普通のTL閲覧では、90秒で表示中のTL自動更新です。ただしMentionは除く。Mentionは表示してなくても180秒に1度、新着あるか確認します。


DMはめんどいのでサポートしてません。

安定性に難、TL取りこぼし等あるかもしれませんが許してください。
万年ベータ版です、きっと。


TLRecは @piyotori の「"ニチアサ"実況にリアルタイムになかなか参加できないけれどあとで録画見るときに実況を楽しみたい」という動機で作ってます。

更新情報等は @tlrec_info でツイートすると思います。
そのうちFirefox対応くらいはしたいと考えてます。→対応しました(2011/12/16)

2011年4月18日月曜日

CSS桜ひらひらはこうして作りました

都内の桜ももう散りましたね。

先月、jsdo.itというJavascript, HTML5, CSSをブラウザ上でコード書いて共有できるサイトに、桜の花びらが舞うCSSアニメーションのコードをアップしました(Webkitブラウザ限定ですが)。

さーくらー ひらーひらー - jsdo.it

これのbookmarklet版のコードも公開してます。
桜ひらひらbookmarklet - jsdo.it

今SafariかChromeを利用している方なら、下記のリンクをクリックすればこのページ内を桜の花びらが舞います。

桜ひらひらbookmarklet

これをどうやって実現しているか今日は書きたいと思います。
まあ僕がやることはいつもたいしたことじゃありません。多少手間がかかることをちまちまやってるだけです。


■花びら
まず花びら。半分ずつ作ります。
下の画像はdivを加工していく過程で、左から右にどんどん適用style増えていきます。(見やすさのために実際の2倍の大きさにしてます)


左端は、width: 9px, height: 6px のdivを作りまして、position: absolute にしておき、そしてborder-radius: 6px 0px 0px 0px として左上だけ丸めた状態です。
次に -webkit-transform: skew(-30deg) でちょっと歪めてます。
さらに、-webkit-transform: skew(-30deg) scale(1.2, 1)で横方向1.2倍、縦方向1倍、要するに横にちょびっと引き伸ばします。
さらにさらに、-webkit-transform: skew(-30deg) scale(1.2, 1) rotate(12deg) で少し傾けます。
これに background: -webkit-gradient(linear, left top, right bottom, from(rgba(224, 176, 192, 1)), to(rgba(255, 240, 240, 1))) で色着けます。
見やすさのために付けてたborder外したのが一番右の画像。
これを左半分とします。

同じようにもう半分を作りますが、skewとscaleの縦方向の値の符号を反転したものを作ってあげます。
width: 9px, height: 6px, position: absolute, border-radius: 6px 0px 0px 0px のdivは同じですが、 -webkit-transform: skew(30deg) scale(1.2, -1) rotate(12deg) とします。色もグラデの向きがちょっと違い、 background: -webkit-gradient(linear, left bottom, right top, from(rgba(224, 176, 192, 1)), to(rgba(255, 240, 240, 1))) です。まあこの辺は、向きとか形とか実際に確認しながらやってます。



これら半分ずつを合わせて1枚の花びらにしたいので、同じdivの中に2つを入れます。
position: absoluteが効いてて見事に重なってくれてますので、少し位置を合わせるために、下半分の div を top: 4px 指定します。
これに色着けた状態でborder外したのが左から3番目のものです。
見やすさのために倍の大きさにしてますが、本来の大きさは一番右のものです。



さて、ここまでを整理して、style設定をいくつかのclassに分けて、divで花びら作るhtmlはこうなります。

コード:
<style type="text/css">
/* 花びら(parent) */
.petal {
width: 15px;
height: 10px;
}

/* 花びらの半分基礎 */
.petalBase {
border-radius: 6px 0px 0px 0px;
position: absolute;
top: 0px;
left: 3px;
width: 9px;
height: 6px;
}

/* 花びらの左半分になるところ */
.petalLeft {
background: -webkit-gradient(
linear, left top, right bottom,
from(rgba(224, 176, 192, 1)),
to(rgba(255, 240, 240, 1))
);
-webkit-transform: skew(-30deg) scale(1.2, 1) rotate(12deg);
}

/* 花びらの右半分になるところ */
.petalRight {
top: 4px;
background: -webkit-gradient(
linear, left bottom, right top,
from(rgba(224, 176, 192, 1)),
to(rgba(255, 240, 240, 1))
);
-webkit-transform: skew(30deg) scale(1.2, -1) rotate(12deg);
}
</style>


<div class="petal">
<div class="petalBase petalLeft"></div>
<div class="petalBase petalRight"></div>
</div>



■花びら回転
花びらを回転させましょう。
さきほど花びらを作ったとき、半花びら2つを内包する親divを作りましたので、そいつをCSSアニメーションで動かせばいいのです。
アニメーションの設定はこんな感じで、まずは3D-X回転のみです。

コード:
<style type="text/css">
/* 花びら回転 */
.petalRotate {
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
-webkit-animation-duration: 1s;
-webkit-animation-name: rotateX;
}

@-webkit-keyframes rotateX {
0% {-webkit-transform: rotate(0deg) rotateX(0deg);}
100% {-webkit-transform: rotate(0deg) rotateX(360deg);}
}
</style>


<div class="petal petalRotate">
<div class="petalBase petalLeft"></div>
<div class="petalBase petalRight"></div>
</div>


petalRotateクラスではアニメーションは終わりなしの無限Loop、動作の加速度はリニア、アニメーション1回にかかる時間は1秒、アニメーションの細かい設定名はrotateX、という指定です。
keyフレームは、アニメーションがn%進んだ位置でのアニメーションしたい属性とその値を書きます。間の値は補完されます。今回はwebkit-transformで2次元の回転(rotate)と3次元縦方向回転(rotateX)を指定して、rotateは値変えてないので実質アニメーションしませんが、3D縦方向は1sでくるくるします。

このアニメーション設定クラスpetalRotateを親divのclassに追加すればくるくるしだします。rotateは後で使います。
(すいません、実サンプル載せてなくて。)


■花びらの移動
回転できたら回転する花びらを移動させたいところです。ひらひら舞い落ちるんで。
しかし回転のアニメーションは1sです。一緒のclassに移動するアニメーション設定まで突っ込んだら、1sで移動開始点から終了点まで動くことになり、風情がない落ち方になります。

「ねぇ、知ってる?秒速5センチなんだって。桜の花の落ちるスピード。」

ということで、回転とは独立したアニメーションを設定したいわけですが、そのために「回転する花びら」をdivで囲んで、そいつにアニメーションを適用します。
花びらの構造がこんなふうになるわけです。
<div class="parentPetal petalTrans"> <!-- 移動アニメーション用-->
<div class="petal petalRotate"> <!-- 回転アニメーション用 -->
<div class="petalBase petalLeft"></div> <!-- 花びら半分A -->
<div class="petalBase petalRight"></div> <!-- 花びら半分B -->
</div>
</div>


CSS含むコードはこんな感じ:
<style type="text/css">
/* 花びら(parent) */
.petal {
width: 15px;
height: 10px;
}

/* 花びらの半分基礎 */
.petalBase {
border-radius: 6px 0px 0px 0px;
position: absolute;
top: 0px;
left: 3px;
width: 9px;
height: 6px;
}

/* 花びらの左半分になるところ */
.petalLeft {
background: -webkit-gradient(
linear, left top, right bottom,
from(rgba(224, 176, 192, 1)),
to(rgba(255, 240, 240, 1))
);
-webkit-transform: skew(-30deg) scale(1.2, 1) rotate(12deg);
}

/* 花びらの右半分になるところ */
.petalRight {
top: 4px;
background: -webkit-gradient(
linear, left bottom, right top,
from(rgba(224, 176, 192, 1)),
to(rgba(255, 240, 240, 1))
);
-webkit-transform: skew(30deg) scale(1.2, -1) rotate(12deg);
}

/* 花びら回転 */
.petalRotate {
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
-webkit-animation-duration: 1s;
-webkit-animation-name: rotateX;
}

@-webkit-keyframes rotateX {
0% {-webkit-transform: rotate(-58deg) rotateX(0deg);}
100% {-webkit-transform: rotate(-58deg) rotateX(360deg);}
}

/* 花びら移動 */
.animationArea {
position: relative;
overflow: hidden;
width: 640px;
height: 640px;
border: 1px solid red;
}

/* 移動アニメ適用div */
.parentPetal {
position: absolute;
}

/* 花びら落下 */
.petalTrans {
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
-webkit-animation-duration: 10s;
-webkit-animation-name: transPetal;
}

@-webkit-keyframes transPetal {
0% {top: -5%; left: 90%;}
5% {top: -5%; left: 90%;}
95% {top: 105%; left: 10%;}
100% {top: 105%; left: 10%;}
}
</style>


<div class="animationArea">
<div class="parentPetal petalTrans">
<div class="petal petalRotate">
<div class="petalBase petalLeft"></div>
<div class="petalBase petalRight"></div>
</div>
</div>
</div>


ひらひらアニメーションする「エリア」を設けるために、さらに親divをひとつ作ってます。この領域内を花びらが舞い、領域の外では見えなくなるようにするために、overflow:
hiddenです。実際のjsdo.itのコードではこの領域をJavaScriptで画面いっぱいになるようにブラウザ幅等取得して調整してます。

移動アニメーション適用のためのdivにもposition:
absolute指定付けときます。そしてアニメーションさせるstyleとして、positionのtop, leftを用います。

アニメーション時間は10秒間にしました。視界の外から入って外に出て行く、ために、top: -5% 位置から入って、top:
105%位置に抜けてるようにしてます。leftは90%からスタートして10%位置へ。右上から入って左下に抜ける道筋になります。

アニメーション進行時間の0-5%、95-100%が同じ位置です。これは、実際のアニメーションは5-95%の間で、前後は画面外で静止しているようにしてます。次から次へと同じルート上を落ちてくるとせわしないかな、ということで、ちょっとしたWaitが入ってるようなもんです。

ついでに、ちゃっかり回転の方のアニメーション設定の、rotate(rotateXではない)を -58 deg
に設定しています。これは花びらの落ちるコースの角度に合わせたもので、topとleftの移動距離を使ってatan(アークタンジェント)で計算して出せます。


■ひらひら
ここまでは花びら1枚の、作成方法とアニメーションの設定について書きました。
複数の花びらがひらひら舞うようにするには、JavaScript使って花びらの量産し、その1枚ずつにそれぞれ回転・道筋のアニメーション設定、をしています。
このあたりはJavaScriptの説明になりさらに長くなるので、詳細は割愛しますが、ざっと以下のような手順です。
  1. 花びら1枚ずつにid振って、たくさん作ります(jsdo.itのコードでは48枚)
    回転用divと移動用divにそれぞれid振ります。
  2. それをアニメーション領域のdivの子として追加します
  3. アニメーション設定(回転と道筋)をJavaScriptで48個分書き出します
    まず先に道筋を、topは-5%スタートの105%エンドで固定で、leftを、スタートは20-150%くらいの範囲、エンドを0-130%くらいの範囲で、乱数用いて決定します。
    これでアニメーション領域の幅(px数)と%で、縦横の移動距離わかるので、atan使って、花びらのコースに沿った角度を求めて、回転の方のアニメーション設定に活かします。
    (今回は面倒だったので、これらを<style>に生テキストとして突っ込んでます)
    この段階ではまだアニメーションしてません。
    まだkeyframeを用意しただけで、それぞれの花びらに対して、-webkit-animation-なんとか、のstyleを適用してません。
  4. すべての花びらのアニメーション時間設定を、これも多少のバリエーション持たせたstyleを用意します。
    回転は0.8s - 1.2s、移動は9 -13sの範囲からランダムに設定されます。これで他よりややゆったり落ちる花びら、他より多めにくるくる回ってる花びら、などが生まれます。道筋も、基本的に右上方面から左下方面、ってだけで角度はバリエーションありますので、1枚ずつは同じ道筋・回転の速さでループしていても、複数枚が舞うことで、すぐには飽きないようにというのを狙ってます。
  5. 最後にすべての花びらに生成したstyleを適用します。


最初の方にも書きましたが、やってることは別に難しいことでもなんでもなくて、ちまちまと多少面倒なことを積み重ねているだけです。
もし気になることなどあれば、jsdo.itの方には質問等かくとこあるので、そちらへ。

2011年4月15日金曜日

icotileのロゴデザインをしました -icotile のどのあたりが HTML5 なのか? 番外編-

少し前に、icotile(http://icotile.ogaoga.org/)のロゴデザインやその他機能の実装等をやりました。おかげさまでicotile、「第 0 回 HTML5 プログラミング&クリエイティブ・コンテスト」にて優秀作品賞をいただきました
(って書くと作成し終わったかのようですが、icotile自体は今もまだアップデート続いてます)

icotileとは:
Twitter のフォローしている人/されている人やリストを iTunes のような操作感で管理でき、また各ユーザーに対して自分だけのメモも残せる Web アプリケーション。Developed by @ogaoga / Designed by @piyotori
ということですが、僕はCSS3まわりやビジュアル的なデザイン面以外でも普通にJavaScriptのロジック部分も書いたりしてますし、むしろogaogaさんが総合的な意味でデザインやってたりします。


さて、icotile企画者にてUI含めほとんどの部分を作ったogaogaさんが、「icotile のどのあたりが HTML5 なのか? #html5j」を書いてます。その中でCSS3まわりについては piyotori がそのうち書くでしょう、って振られてたのですが、なかなか書けずにいましたが、重い腰を上げて書くことにします。


が、CSS3じゃなくてまずはロゴデザインの話します (キリッ


まず最初に書いておきますが、僕はデザイナーを生業とするものではありません。普段はhtml5はもとよりデザインもWebも関係ないような仕事をしています。ので、そんな風にデザインやってんのかよ的なところ多々あると思いますが、ご容赦ください。

初めに。icotile一緒に作りましょうという話をogaogaさんからいただいのたのが今年1月中旬くらいで、html5コンテストは2/10締め切り。あまり期間がない(ある方か?)けれども、ロゴだけでもまずはなんとしてでも作らねば状態でした。が、Webサービスのコンセプトを聞いた後、さらにはロゴこんな感じと、既存フォントで作ったイメージも提供いただいたので、イメージを掴みやすかったです。

そのときいただいたロゴイメージ:


このイメージと、「icotile」という名前がそもそも「アイコンがタイル上に並ぶ」ことに由来していることから、I, C, O, T, I, L, Eのアルファベットを1文字ずつタイルに描かれた文字みたいに並べてロゴにしようと思いました。
で、iPadですらすらっとお絵描きしてみたのがこれ。
…と思ったらあれ?画像がない。だいぶ前に消しちゃったっぽい。
すいません。
なかったことにして話を進めますが、ラフな絵を描いた段階で頭の中では、タイルの1辺を a pxとして、文字の幅は a/4 pxで、カッチリ計算したフォントみたいなの作ろうと思ってました。


で、計算により描画する、ということで、はいここでhtml5のcanvasです。強引。


っつーわけで僕は canvas 使って ロゴに使う1文字ずつの画像を作成することにしました。

まず正方形のcanvas を用意し、1辺のpxを指定して、そこからすべて計算により図形を描画して文字を作っていきます。
簡単な I を例にすると、仮に1辺が120pxだとして、下図のような、緑の点を基点とした、横幅が 120/4 = 30、高さが120 の長方形描けばいいのです。


緑の点の座標は、まずXは、canvasのセンターが120 / 2 = 60なので、そこから文字幅 120 / 4 = 30をさらに 2 で割った値をマイナスしてあげた、X=45、Yはcanvasの一番上なので0、となります。

JavaScriptはこんな感じ。
    var cnvs = document.createElement("canvas");
cnvs.width = 120;
cnvs.height = 120;
cnvs.id = "id_cnvs_I"
$("body").append(cnvs);

var ctx = document.getElementById("id_cnvs_I").getContext('2d');
ctx.fillStyle = "black";
ctx.fillRect(45, 0, 30, 120);

これで、120x120のcanvasの真ん中に棒が一本みたいなのができます。

でもこれだとカッチリカクカクな棒なので、角丸になるように、半径 r の円弧を使って角丸boxを書く関数を用意します。これはこちらの記事を参考にさせていただきました。

[javascript]canvasで円や角丸の矩形を描画する - 週末ラボ

これでこんな「I」の文字ができます(外枠は本当はありません)。


この文字を一回り大きいサイズの角丸四角で囲みたくなります。「タイル」をイメージするんで。

しかし。僕自身このエントリで書いてるようにロゴ文字を作っていったので、さて外枠描画しようかなってときに、canvasサイズ小さいじゃん!ってなりました。まあいろいろな対応が考えられますが、とりあえずcanvasをひとまわり大きいサイズにするにはしますが、こんな手順で外枠付けました。

0.例では120x120のcanvasだったが128x128のcanvasを最初に用意することにする(外枠マージン上下左右4pxずつ)
1.外枠のマージン分canvasを移動させて文字を描画する
2.canvasを大きくする前と同じままのコードで文字を描画する
3.外枠のマージン分移動したcanvasを元に戻す
4.外枠描く

「canvasの移動」には translate を使います。translateはちょっと混乱しますが、画用紙にひたすら同じ図形を描いているロボットアームがあったとして、画用紙をずらしたらずれた位置に同じ図形描きますよねってのをイメージしていただくとよいかと思います。
実際に I を描いてさらに外枠付けるコードはこんな感じです。
    var cnvs = document.createElement("canvas");
cnvs.width = 128;
cnvs.height = 128;
cnvs.id = "id_cnvs_1"
$("#font_draw_area").append(cnvs);

var ctx = document.getElementById("id_cnvs_1").getContext('2d');
ctx.fillStyle = "black";

pi = Math.PI;
/* 角丸boxパス作成 */
var makeRoundRectPath = function(ctx, x, y, w, h, r){
ctx.beginPath();
ctx.arc(x + r, y + r, r, - pi, - 0.5 * pi, false);
ctx.arc(x + w - r, y + r, r, - 0.5 * pi, 0, false);
ctx.arc(x + w - r, y + h - r, r, 0, 0.5 * pi, false);
ctx.arc(x + r, y + h - r, r, 0.5 * pi, pi, false);
ctx.closePath();
}

/* 文字描画 */
ctx.translate(4, 4); /* canvas移動 */
makeRoundRectPath(ctx, (120 / 2) - (30 / 2), 0, 30, 120, 6);
ctx.fill();
ctx.translate(-4, -4); /* canvas移動戻す */

/* 外枠描画 */
ctx.strokeStyle = "black";
makeRoundRectPath(ctx, 0, 0, 128, 128, 6);
ctx.stroke();

/* 画像オブジェクトに */
var imgObj = new Image(128, 128);
var imgSrcData = document.getElementById("id_cnvs_1").toDataURL("image/png");
imgObj.src = imgSrcData;
$("body").append(imgObj);
$("#id_cnvs_1").remove();
最後の方が妙なことやってますが、後で説明します。
これで、以下のような I の画像ができあがります。


今回説明する都合上単純化しており外枠はstrokeで済ませました。でも実際にはroundRectをさらに改造したもの、を組み合わせて、外枠描画するようにしてます。


「I」は簡単でしたが「T」はどうでしょうか。

まず横棒(X=0, Y=0, W=120, H=30の長方形)と縦棒(X=45, Y=0, W=30,
H=120の長方形)を描画します。(わざと半透明色にしてます)


これでもいいのですが、Tの「脇」が気になります。以下の赤いところ。


今回はここも丸みを持たせるデザインにしようと思います。そこで、角丸boxの外側を描画する関数を用意します。下図のようなイメージで、指定した領域(うす緑)の4つの角を描画するものです。


実際には、どこの角を塗るか指定するMaskもつけて、こんな感じの作りました。

/* 角丸Outerパス作成 */
// mask: bit3=lefttop, bit2=righttop, bit1=rightbottom, bit0=leftbottom
function makeRoundRectOuterPath(ctx, x, y, w, h, r, mask){
ctx.beginPath();
if(mask & 0x08){
ctx.moveTo(x, y);
ctx.arc(x + r, y + r, r, - pi, - 0.5 * pi, false);
ctx.lineTo(x, y);
ctx.lineTo(x, y + r);
}

if(mask & 0x04){
ctx.moveTo(x + w - r, y);
ctx.arc(x + w - r, y + r, r, - 0.5 * pi, 0, false);
ctx.lineTo(x + w, y);
ctx.lineTo(x + w - r, y);
}

if(mask & 0x02){
ctx.moveTo(x + w, y + h - r);
ctx.arc(x + w - r, y + h - r, r, 0, 0.5 * pi, false);
ctx.lineTo(x + w, y + h);
ctx.lineTo(x + w, y + h - r);
}

if(mask & 0x01){
ctx.moveTo(x + r, y + h);
ctx.arc(x + r, y + h - r, r, 0.5 * pi, pi, false);
ctx.lineTo(x, y + h);
ctx.lineTo(x + r, y + h);
}
ctx.closePath();
}
これで両脇を丸くしてやってTができます(外枠付与前)。



こんな感じで残りの、C、O、L、Eも一文字ずつちまちまと計算しながらデザインしました。
Eは、真ん中の横棒を浮かせるか根元に付けるか試して、最終的には浮かせることにしました。




さて、こんな風にすべてcanvasで描画できるのなら、そのままサイトに組み込んでしまえ、と最初はやりました。
canvasそのまま組み込んでもいいのですが、canvas上の描画された絵をimageとして使いたいので、「I」の字描画コードの最後でしてたようなことをやります。

しかし、ロゴというなるべく早く読み込まれて然るべきもの(と思う)をこんな処理してるのもどうかと思ったので、そうやって作った画像を名前をつけて保存してサーバにアップして画像として最初から読み込むようになってます。


こうして1文字ずつの画像はできましたが、今のicotileのsign in後に右上に出てるロゴなんかは、7文字繋がった1枚の画像になってます。
それもcanvas利用して作成してます。
 1.文字の画像をcanvasで描画
 2.image objectに
 3.横長canvasに2のobjectを適切な座標に読み込む
 4.1~3を ICOTILE ぶん繰り返す
 5.横長canvasを image object にして 最後はブラウザ上から「名前を付けて保存」
で、作ってます。

実際のものとは違いますが、たとえば上で作成した「I」の字を128pxじゃなくて36px版にして、7文字連結するコードはこうなります。
    var cnvs = document.createElement("canvas");
cnvs.width = 36;
cnvs.height = 36;
$("body").append(cnvs);

var ctx = cnvs.getContext('2d');

pi = Math.PI;
/* 角丸boxパス作成 */
var makeRoundRectPath = function(ctx, x, y, w, h, r){
ctx.beginPath();
ctx.arc(x + r, y + r, r, - pi, - 0.5 * pi, false);
ctx.arc(x + w - r, y + r, r, - 0.5 * pi, 0, false);
ctx.arc(x + w - r, y + h - r, r, 0, 0.5 * pi, false);
ctx.arc(x + r, y + h - r, r, 0.5 * pi, pi, false);
ctx.closePath();
}

/* 文字描画 */
ctx.translate(2, 2); /* canvas移動 */
makeRoundRectPath(ctx, 12, 0, 8, 32, 2);
ctx.fill();
ctx.translate(-2, -2); /* canvas移動戻す */

/* 外枠描画 */
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
makeRoundRectPath(ctx, 0, 0, 36, 36, 4);
ctx.stroke();

/* 画像オブジェクトに */
var imgObj = new Image(36, 36);
var imgSrcData = cnvs.toDataURL("image/png");
imgObj.src = imgSrcData;
$("body").append(imgObj);
$(cnvs).remove();

/* 横長canvas */
var cnvs = document.createElement("canvas");
cnvs.width = (36 * 7) + 6;
cnvs.height = 36;
$("body").append(cnvs);

ctx = cnvs.getContext('2d');

/* imageをcanvas上の指定位置に読み込む x 7回 */
for(var i = 0; i < 7; i++){
var x = (36 + 1) * i;
var y = 0;
ctx.drawImage(imgObj, x, y);
}
$(imgObj).remove();

/* 画像オブジェクトに */
var imgObj = new Image((36 * 7) + 6, 36);
var imgSrcData = cnvs.toDataURL("image/png");
imgObj.src = imgSrcData;
$("body").append(imgObj);
$(cnvs).remove();
これで258x36の右クリック保存可能なlogo画像ができました。


だいたいこのようにしてicotileの(sign in後の)ロゴ画像は作られました。
実際には、タイルの1辺のサイズを指定すればあとは各文字の描画位置を比率で計算してすべて描画されるようにスクリプトは書いています。
※canvas上に描く図形の座標がキリがいい数字になる(ことが多くなる)ように設計されてないと、意に反したクオリティの画像になる場合があります。

(sign in前のページのロゴ(Tの字がくるくるするやつ)の話は、またicotileのどこがhtml5か -CSS3アニメ編-としてでも書こうと思います)



まあ何面倒なことやってんだってことでもあるのですが、開発段階ではcanvasそのまんまでサイトに組み込んでみることで、文字の色とかサイズとか微調整だとかやりながら、デザインも行えてしまうという、(若干こじつけの)メリットがあります。
また、ブラウザとテキストエディタがあればデザインが行えるというメリットもあります。イラレやフォトショなどなくてもいいのです。

というわけで、「icotile のどのあたりが HTML5 なのか?」の番外編として、ロゴデザインワークが実はcanvasにて行われていた、というお話でした。


番外編な上にさらに番外:
勢い余って、ICOTILEフォントっぽいもの、作りました。すべての文字のための関数用意するのは時間かかりました。
そして案の定、使いどころないです。

2011年3月3日木曜日

Firefoxで display: -moz-box 指定が効かないときの回避策

CSS3では、3カラムレイアウトなんかが簡単にできる、子要素が横並びに並んでくれるdisplay: boxってのがあります。

参考サイト: CSS3 でのレイアウトで使える box 系プロパティのまとめ | CSS Lecture

例えばこんなCSS指定とタグを書くと、
<style>
#container {
width: 100%;
display: -webkit-box;
display: -moz-box;
}

#left {
border:1px solid red;
width: 100px;
}

#center {
border:1px solid green;
-moz-box-flex: 1;
-webkit-box-flex: 1;
}

#right {
border:1px solid blue;
width: 150px;
}
</style>

<div id="container">
<div id="left">left</div>
<div id="center">center</div>
<div id="right">right</div>
</div>

対応ブラウザでは以下のようになるわけです(生でタグ書いてるので対応してればきれいに横並びになってるはずです)。


left

center

right



しかし、これがFirefoxでうまくいかないときがあります。
次のように、親要素にposition指定をしてみます。メニューバーをdisplay: boxで作ってposition: fixedで、なんて使い方とかあると思います。

上で出した例の#containerにposition: fixedを入れてみます。
#container {
width: 100%;
position: fixed; /* add */
display: -webkit-box;
display: -moz-box;
}


すると、先ほどは横に揃っていた子要素達が、Firefoxではこうなります(今度は生タグじゃなくて画像)。

SafariやChromeの場合、横並びのままなんですが、Firefoxだとdisplay: -moz-boxが効いてません。

理由はわかりませんが、Firefoxでは親要素(「僕の子らは同じ1つのbox(すなわち親である私)内に収まりますよ」と指定しているやつ=本エントリでは"id=container")にrelative以外のposition指定をすると、boxとして揃ってくれません。Firefox 3.6.14でも、Firefox 4 beta2でも、この現象はでます。

これを回避するには問題となってるposition指定を外すことです。でもそれだと本来のposition(fixedとかabsoluteとかで指定したかった位置)が意味なくなります。
そこで、こんな風にさらに親要素をひとつつけてやって、それにpositionの役割を果たしてもらうことで、うまくいくようになります。


<style>
#parentBox {
position: fixed;
}

#container {
width: 100%;
display: -webkit-box;
display: -moz-box;
}

#left {
border:1px solid red;
width: 100px;
}

#center {
border:1px solid green;
-moz-box-flex: 1;
-webkit-box-flex: 1;
}

#right {
border:1px solid blue;
width: 150px;
}
</style>

<div id="parentBox">
<div id="container">
<div id="left">left</div>
<div id="center">center</div>
<div id="right">right</div>
</div>
</div>


…で済めばいいんですが、サイトのデザインによっては、さらにいろいろ微調整が必要になることも考えられます。widthとか。そこはまあがんばってください(投げ)。


icotileのFirefox対応検討中、この現象に遭遇しました。何が影響してるか検証した結果positionと判明したので、ならばと回避策を考えてみた次第です。

icotileとは:
Twitter のフォローしている人/されている人やリストを iTunes のような操作感で管理でき、また各ユーザーに対して自分だけのメモも残せる Web アプリケーション。Developed by @ogaoga / Designed by @piyotori
ってことで、最近icotileのデザイン周りでCSS3あれこれ調べたり、ビジュアル的なデザイン面以外でも普通にJavaScriptのロジック部分も書いたり、やってます。(そしてCSS3楽しーってなると勢いあまってシャルロッテとか作るわけです(前回エントリ参照))

2011年3月2日水曜日

CSS3でまどか☆マギカのシャルロッテを描いた

CSS3(webkitのみ)でまどか☆マギカのシャルロッテを作る、の続きです。

今朝未明のエントリでは力尽きて耳というか羽というかまでは作れませんでしたが、あとちょっとだしということで、付けました。

この完成版で、background-colorやらborderやらの指定を取り除き、border:1px solid whiteのみ適用してみると、こうなります。


だいたい、border-radiusを2角にだけかけたdivを、rotateして位置はabsoluteでチマチマ合わせてます。
目はradialタイプのgradientで色分け。
ひんまがった口には、border-radiusめいっぱいかけたdiv=円のborderを2辺だけ表示にして半円にし、それにskewやらscaleやらrotateやら適用してます。たぶん。


汚いソース見たい奇特な方は下記ページを開きブラウザの「ソースを表示」等からどうぞ。
実際のページ
※リンク先ページ内オブジェクトクリック危険

CSS3でまみまみしたい

CSS3で描いたよ!canvasとか使ってないよ!
まどか☆マギカ見たいと思ってるけどまだ見てなくて実はよく知らないよ!


汚いソース見たい奇特な方は下記ページを開きブラウザの「ソースを表示」等からどうぞ。
実際のページ
※リンク先ページ内オブジェクトクリック危険
※webkit限定

羽みたいのはもう疲れたから付けない。
付けました(2011/03/02 22:30)

2010年10月22日金曜日

Twitterbot移設メモ2

前回はGAE(Python)からPost成功したところまででした。

これからカーチャンbotのメッセージ選択アルゴリズムを移植します。

まずJavaScript動作のときの概要をちょっと書いておきます。

カーチャンbotは、全部で70種類くらいの定型文を用意しています。
お昼や夕食等の定刻用メッセージ、文中にユーザー名、商品名、などが入るものも含まれます。それら定型文自体をDOM要素として持たせていました。また、置換アイテムも同様です。

大雑把に書くと、
・定型文、置換アイテム、followerリストをデータとして持っている
・そしてそれらを使用したかどうかフラグを管理している(ストレージ等に書き出さないRAM上のもの)
・毎時ランダムに未使用のものを選ぶ
・定刻は定刻用定型文から引っ張ってくる
・通常時も、時間縛りあり定型文、時間縛りなし定型文がある
といった感じです。

毎時00分に、定型文をランダムに選び、時刻縛りがある場合はそれにあわせて、置換する必要があればアイテムもランダムに選び、そして投稿する文章が完成します。post完了後には、次の00分00秒000までの時間を計算して、setTimeoutで次の00分にまた同じことをするよう、仕込んでおきます。それを繰り返しています。
(0時過ぎのおやすみpost、followerリスト更新、などはまだ移植してないので、触れないでおきます)


GAE移植にあたって、これらデータの使用/未使用管理をどうするか、です。JavaScriptではRAM上でフラグ管理してました(のでブラウザ再起動で初期化されます)が、今度GAE移植して、cronで毎時動作させるとなると、いくら定型文使用/未使用のフラグをRAM上で管理しても、1回動作が終われば消えますから、ストレージに書き出さないとなりません。当然ですが。(むしろ今までが特異な動作原理のbotだっただけで)

ということで、僕はDBは全くわからないので、csv形式の生データファイルを読み書きする方向で行きます。(※最後に書いてありますが、ちなみにこの方法ではうまくいきません)

たとえば、

0,10-17,%A おかあさんちょっと買い物行って来るからね\n

という感じで。先頭が使用/未使用(1/0)、次が時刻指定/時間範囲縛り設定(なし=N,19時用=19,10時~17時用=9-18といった感じ)、最後が本文です。%AはJ( 'ー`)しに後で置換します。
これがずらーっと並んだデータファイルを作っておきます。

で、実際にPythonで読み込むときはこんな感じに。


import os
import random

# ファイル読み書きクラス
class fileAccess:
def __init__(self, src):
self.src = src
self.lines = []

def readData(self):
try:
f = open(self.src, 'r')
except IndexError:
print 'Index error'
except IOError:
print '"%s" cannot be opened.' % self.src
else: # no error
tmpLines = f.read().split('\n') # \n を区切りとしてファイル内容を読み込む
f.close() # ファイルクローズ

length = len(tmpLines) # 読み込んだ行数
for cnt in range(0, length):
str = tmpLines[cnt]
if str != "": # 空行は除外
self.lines.append(str)

def modifyData(self):
if len(self.lines) == 0:
print 'input lines has no data.'
return

tmpfile = "tmp_" + self.src
try:
f = open(tmpfile, 'w')
except IndexError:
print 'Index error'
except IOError:
print '"%s" cannot be opened.' % tmpfile
else: # no error
length = len(self.lines) # 読み込んだ行数
for cnt in range(0, length):
if self.lines[cnt] != "": # 空行は除外
f.write(self.lines[cnt] + "\n") # 引数の文字列をファイルに書き込む
f.close() # ファイルを閉じる

# ファイル操作: 変更前backupコピーを作成し 変更後tmpを正式にrename
bakfile = "bak_" + self.src
if os.path.exists(bakfile):
os.remove(bakfile)
os.rename(self.src, bakfile)
os.rename(tmpfile, self.src)

# 読み込んだ行をカンマで分けるクラス
class messageStruct:
def __init__(self, str):
tmp = str.split(',')
self.used = tmp[0]
self.timespan = tmp[1]
self.msg = tmp[2]

def setUsedStatus(self):
self.used = "1" # ものぐさで、int()で数値化してないで文字で判定してる

def clearUsedStatus(self):
self.used = "0"

def getCombinedStr(self):
return self.used + ',' + self.timespan + ',' + self.msg

# main
# データ読み込み
messageSource = fileAccess("msg.csv.dat")
messageSource.readData()

found = False
while found == False:
# ランダム
rndMax = len(essageSource.lines) - 1
i = random.randint(0, rndMax)

# カンマ区切りを各パラメータに分解
str = messageStruct(messageSource.lines[i])

if str.used == "0": # 未使用なのでメッセージ確定
str.setUsedStatus() # 使用済みに更新
messageSource.lines[i] = str.etCombinedStr()
found = True

# 略しますが、
# 必要があればuser名など置換してpost処理

# データ更新
messageSource.modifyData()


本当はもっと細かく、現在時刻取得して定刻用メッセージにするかどうか判定したり、置換アイテムが必要か、どの種のアイテムか、などいろいろやりますが、基本はファイル読んでランダムに選んで未使用なら採用、ってやってます。

が、ここで困ったことが。
実はGAEではファイルを静的なデータソースとして置くことはできるっぽいのですが、それを動的に更新したりはできません(知らずにLocalでターミナルからPython実行して↑の仕組み作ってた)。ので、この方法は、ランダムポストだけするには使えますが、本来の目的のどの定型文をポストしたかの管理を行うには、これでは不十分です。GAEのデータストアの方法を知らねばならないようです。僕がさっぱり知らない領域、DBに首を突っ込まねばなりません。

続く