スマホサイト作っててハマったことメモ(2013/06/13時点)

window.scrollTo(0, 1);でアドレスバーが隠れない

以下のようにJavaScriptからスクロール位置をY軸方向に1px動かすことで、アドレスバーを引っ込めることができますが、
これが動くことは動くんですが、すぐに隠れずに、4〜5秒ぐらいしてからヒョコッと隠れる。

自分で作った他のサイトでは、すぐに引っ込むのになんでだろう、、、と

(function () {
  $(window).bind("load", function (e) {
    setTimeout(doScroll, 100);
  });

  $(window).bind("orientationchange", function (e) {
    // e.orientation === "portrait" ? "縦" : "横"
    setTimeout(doScroll, 100);
  });

  function doScroll() {
    if (window.pageYOffset === 0) {
      window.scrollTo(0, 1);
    }
  }

  window.doScroll = doScroll;
})();

そもそもjQueryMobileを使っていると内部的に↑のようなことはしてくれるので、自分で実装する必要はないのですが、
jQueryMobileを使ってもアドレスバーが引っ込むタイミングが遅い!

早く引っ込むサイトとそうでないサイトを見比べると、Mobile Safariのアドレスのところに「リーダー」というのが出てる。
(あやしい、、、)

そこで、このリーダーを消す作業をしました。

  1. JavaScript疑う
  2. CSSを疑う
  3. 心が折れる
  4. htmlを削り始める

この最後でbodyの中身をスッカラカンにすると、リーダーがなくなり、最終的にarticleタグが全体を囲っているとどうもリーダーが出ちゃう可能性が高い
という結論に到達しました。
とりあえずdivにしたんですが、それでもダメな場合があるらしく、そのあたりは以下の記事がわかり易かったです。

リーダーのせいでmobile safariのアドレスバー隠しが遅れる – < /gecko >

jQueryMobileでTransitionが効かない場合がある

jQueryMobileの特徴として、次の画面をAjaxで取得しDomに追加し、アニメーションしながら遷移できる。
というのがありますが、ここでの醍醐味は、アニメーションで遷移できる点かなーと思っています。

このアニメーション効果、つまりTransitionがつかずに遷移する場合があったので、ここにメモ。

Transitionがつかないと、Ajax遷移には間違いないんですが、一瞬で次の画面に行くので、
普通に画面遷移しちゃったのかな?と思うぐらいよくわからない。

そしてこれが起きる原因は、サイトの全体の高さがある一定以上を超える場合に起きる可能性が高い。

以下にjQueryMobileのコードから抜粋してみました。

Line: 3652

Math.max( $.mobile.window.scrollTop(), toScroll ) &gt; $.mobile.getMaxScrollForTransition()

Line: 3771

defaultGetMaxScrollForTransition = function() {
  return $.mobile.getScreenHeight() * 3;
};

Line: 3797

// Set the getMaxScrollForTransition to default if no implementation was set by user
$.mobile.getMaxScrollForTransition = $.mobile.getMaxScrollForTransition || defaultGetMaxScrollForTransition;

$.mobile.getScreenHeight() * 3;ここの部分が該当の箇所だと思うのですが、
スクロール位置が、画面のScreenの高さを3倍したものを超えた状態でAjax遷移をするとTransitionが効かないという感じです。

実際にconsole.logで出力しながらやってみたら、まさしくそのとおりでした。

ここは割り切るか、画面の高さを縮めるか、という感じですかね。
個人的にはこの配慮はパフォーマンスを意識して書かれていると思うので、Transitionが効かないときがあってもいいのかなーと思っています。
普通の画面遷移よりはどのみち速いので。

jQueryMobileのローディングにアニメーションgifが表示されない

CoreなCSSだけあればよかったので、「jquery.mobile.structure-1.3.1.css」を読み込んでいたのですが、
どうもアニメーションgifの部分のCSSクラスが定義されていなく、ローディングが一瞬出るのですが中身のグルグルがない状態でした。

なので、本体のCSSから以下をコピーしてきました。

/* loading icon */
.ui-icon-loading {
  background: url(images/ajax-loader.gif);
  background-size: 46px 46px;
}

これで動いたけど、みんなどうしているのかな?

jQueryMobileとanimate.cssの相性が悪い

animate.cssに書かれている以下の内容がどうもダメっぽく、Transitionでアニメーションしたあとに少しチラつきがでちゃいます。

body { /* Addresses a small issue in webkit: http://bit.ly/NEdoDq */
  -webkit-backface-visibility: hidden;
}

なので、animate.cssはインクルードせずにSassを使って以下のように組み込みました。

@include keyframes(bounceIn) {
  0% {
    @include transform(scale(.3));
    @include opacity(0);
  }
  60% {
    @include transform(scale(1.05));
    @include opacity(1);
  }
  80% {
    @include transform(scale(.9));
  }
  100% {
    @include transform(scale(1));
  }
}

Sass楽ちん!

iOSのWebViewで外部リンクをネイティブブラウザで開く場合の注意点

以下のメソッド内で外部リンクかどうか判断し、外部リンクの場合はネイティブブラウザを開くようにできるのですが、iOSのWebViewはややこしいことにiframeのリクエスト時もここに入ってきてしまいます。

  - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:  (NSURLRequest *)request navigationType:  (UIWebViewNavigationType)navigationType {-

これだと計測用のiframeのリクエストもこのメソッド内に入るので、外部URLだった場合にネイティブブラウザで開くという挙動を入れると、このiframeのリクエスト時にも反応してしまいます。

そこで方法としては、クリックした場合にのみ外部リンクかどうかを判断するようにするという方法があります。

シンプルなサンプルですが、以下のような雰囲気。

  NSString *absoluteUrl = request.URL.absoluteString;

  // 外部リンクをクリックした場合には、デバイスのデフォルトブラウザを起動する
  NSRange range = [absoluteUrl rangeOfString:@'domain'];
  if (range.location == NSNotFound) {
      if (navigationType == UIWebViewNavigationTypeLinkClicked) {
          [[UIApplication sharedApplication] openURL:request.URL];
          return NO;
      }
  }

そして、WebView側のコードに以下のようにクリックイベントをfireするコードで置き換えます。
もしもアンカーリンクなどをクリックする場合は以下のコードは不要ですが、何かしらJavaScriptでURLを作ってlocation.hrefに入れている場合には置き換えが必要になります。

  $("#hoge")[0].dispatchEvent(click());

  function click(){
    var event = document.createEvent("HTMLEvents");
    event.initEvent('click', true, false);
    return event;
  };

AndroidのWebViewはiframeなどはshouldOverrideUrlLoadingメソッドの中に入ってこないので、楽なのですが、
iOSは結構ややこしいですね。

WebStorm6のFile WatchersとIDE Improvementsがいいね!

TypeScript Support in WebStorm 6 | WebStorm & PhpStorm Blog

File Watchers

File Watchers機能が増えたので、コンパイル系のAltJSやCSSプロセッサーのSassやLessも保存時にコンパイルしてくれるので、だいぶ楽ちんになってきました。
(とはいえ、ぼくはGrunt.jsを使うので、あまりFile Watchers自体は使わないかもですが)

前のバージョンでは、External Toolsでコマンドを登録していましたが、今は保存するタイミングで自動でコンパイルしてくれます。

ためしにCoffeeScriptのFile Watchersを登録してみます。
「Settings → File Watchers」から+ボタンを押して、CoffeeScriptを選んでみます。

こんな感じで、入力しておきます。

どうもEnvironment Variablesにnodeのパスを設定しないとうまく動かなかったので、入れています。

あと、Output pathsは入れても入れなくても動きました。
指定してもその通りにならなかったので、うまく機能していないのかな?
なので、Argumentsのところで–outputで.jsファイルの保存先を指定しています。

該当のファイルのみをコンパイルしているので、Grunt.jsを使うよりも速いですね。
(Grunt.jsにいろいろ書きすぎ!)

IDE Improvements

この機能は、外観をかっちょいい感じにしてくれます。

メニューの「View → Quick Switch Scheme」よりSwitch Look and Feelを選択し、Darculaを選択するとこの画像のようになります。

こうゆう機能ぼく好きだなー。

そういえばアイコンもやわらかい感じになりました。

その他アップデート内容は、
WebStorm 6 — turbo-charge your cutting edge web dev workflow with time-saving tools
よりご覧ください。

風雲~ストームライダーズ [DVD]
ポニーキャニオン (2003-06-18)
売り上げランキング: 46,754

WebStormショートカット・機能メモ

今まではVimJavaScriptを書いていたんですが、WebStormも結構使っていて、ここ最近はWebStorm一本でJavaScriptを書き始めているので、
良かったなーというショートカットや機能をメモっていこうと思います。

ただやはりVimならこれできるのにーとモジモジすることは多々あります。

またショートカットの設定は、WebStormの設定にあるKeymapからできます。

WebStormってなあに?という方はWebStorm指南書こちらのすばらしいスライドを一読されると、おーーってなると思います。

Command + J

LiveTemplate一覧

TextMateのようにタブで保管できる候補が一覧で出てきます。
あれ、どんな候補あったっけ?というときに特に便利です。

Command + W (独自コマンド)

タブを閉じる

WebStormはデフォルトでこのコマンドでタブが閉じないので設定した。

Keymap Editor TabsのClose

Command + Shift + ], Command + Shift + [ (独自コマンド)

タブの移動

これもよく使うので設定した、Chromeとかと同じ方法での移動。

Keymap Editor TabsのSelect Next Tab
Keymap Editor TabsのSelect Previous Tab

Shift + Option + ↑ (独自コマンド)

Caret内の文字を選択、ダブルコートの中身とか、HTMLのタグの中身とか。
Vimっぽい機能としてこれは必須でした。
複数回実行すると選択の範囲が大きくなっていく。
なので、ハッシュの中身を選択もできる。

Eclipseと同じ操作のショートカットにした。

Keymap Select Word at Caret

Shift + Option + ↓ (独自コマンド)

Caret内の文字を選択の解除
選択が行き過ぎた場合にこれで戻れる。

Shift + Option + ↑の逆版

Keymap UnSelect Word at Caret

Command + Shift + O (独自コマンド)

ブラウザの起動

書いているHTMLをすぐに開きたいときに、わざわざブラウザのほうにHTMLファイルをドラッグとかしたくないので設定した。

Keymap ViewのOpen in Browser

Option + F2

どのブラウザで選ぶかのリストが出て、エンターでそのブラウザを起動

Command + Shift + Oでも十分なんですが、他のブラウザでも見たい場合に便利。
設定のWeb BrowsersでActiveにしとかないとリストに出てきてくれないので注意。

Keymap ViewのWeb Preview

Command + E

Recent Files

最近開いたファイルをリストで表示してくれるので、これも結構使います。

Keymap ViewのRecent Files

Command + 1

Project Tool Windowを表示

つまりファイルエクスプローラーのペインに移動。
目的のファイルでエンターを押し、Escでエディターペインに移動できる。
Recentのほうでは探せなさそうなときはこっちを使っている。

Keymap ViewのTool WindowのProject

Command + 7

Structure Tool Windowを表示

これもよく使うコマンドで、ファイル内のStructure情報を表示してくれる。
エンターでその定義メソッドなどに飛べて、さらにこのペイン内ではインクリメントサーチができるので、飛びたいメソッド名がわかる場合は少しタイプするだけで目的の場所に飛べる。

Keymap ViewのTool WindowのStructure

Command + Shif + A

アクション一覧を表示

操作したいアクションのコマンドを覚えていない場合は、これで一覧表示エンターで起動できる。
これすごく便利、TextMateにもSublime Textにもありますね。

Keymap HelpのFind Action

Command + B

定義場所に移動

もっともよく使うコマンドかもしれない。
さらに、これがWebStormの強みでもある。
メソッドやプロパティの定義場所に移動してくれる、さらにファイルもまたがっていてもOKだし、プロジェクト内のJavaScriptファイルはインデクシングされているので、jQueryなどの
ライブラリにももちろん飛べる。

そして、候補がいっぱい出てきたときは、ファイル名をタイプすればインクリメントサーチしてくれる!

Keymap NavigateのDeclaration

Option + F7

関数変数の利用箇所を検索

これもWebStormの便利機能、このメソッドどこで使われているのかなーというときに使います。

Keymap FindのUsages

Shift + F6

Rename

よく使うリファクタリングのRenameです。
WebStormの協力なのはこのリファクタリングがすごいですよね。

後になって、やっぱりこのメソッド名嫌だなーと思った場合、他のエディターで直すのは大変ですが、WebStormなら一発!

Keymap RefactorのRename

Command + Option + T

クイックタグ編集

選択範囲を囲むようにHTMLタグを生成できます。
もちろんZenCodingも可能。

Surround.vimのようにダブルコートとかで囲えたらいいのになー

Keymap CodeのSurround With

Command + Option + L

コードフォーマッター

一発でコードをキレイにしてくれるので、これはコード書いては実行しています。

Keymap CodeのReformat Code

Command + Shift + Enter

現在いるステートメントで最適なコードを保管してくれる

ifやforとタイプしてこのコマンドを打つと()やら{}を保管してくれる。
ただ普段はCoffeeScriptを書いているので、このコマンドはあんまり使わなくなってきてしまいました。

Keymap Complete Current Statement

Option + Enter

へんてこなコードのところには電球のマークが出ますが、ここでこのコマンドを実行するといい感じに修正してくれる。
AppCodeではほんとこのコマンドを多様する。

F11

ブックマーク

Shift + F11でブックマーク一覧が出てきます。
AngularやBackboneなんかを今コードリーディングしているんですが、いろんな箇所でブックマークしてあとから見ることが多々あるので重宝します。

Open URL

Command + Shift + AでOpen URLと入力すると速いが、入力したURLのHTMLをWebStorm内に表示してくれるので、
サクッとWebStorm上で目的のサイトのHTMLを見たいときに便利。

LiveTemplateを自作して速くコーディングする

High-speed coding with Custom Live Templates | WebStorm & PhpStorm Blogここの記事を読むとだいたいわかりますが、TextMateのようなタブで保管する機能をもっと自分オリジナルなもの追加したい場合にLiveTemplateを使います。

キーボードリファレンス

WebStorm_ReferenceCard.pdfからpdfをダウンロードしとくといつでもショートカットが一覧で見れる。
ありがたい!

AppとWebViewの相互の機能の呼び方、そしてhistory.back()について(iPhone・Android)

history.back()が出来る状況が毎度毎度分からなくなってしまうので、ここにまとめておきます。
また、AppからWebViewの機能を呼ぶ、WebViewからAppの機能を呼ぶということもどうゆう状況だとできるのかもまとめておきます。
むしろこっちのほうが忘れやすい。

history.back()

ローカルHTMLファイルの場合

iPhone 動かない
Android 動く

リモートHTMLファイルの場合

iPhone 動く
Android 動く

なので、history.back()を前提にしているjQueryMobileを使うサイトは、ローカルではなくリモートを参照したほうがいいでしょう。


Titanium.App.fireEvent

ここはTitaniumのfireEventが動く範囲をまとめてみました。

ローカルHTMLファイルの場合

iPhone 動く
Android 動く

リモートHTMLファイルの場合

iPhone 動かない
Android 動かない

iPhoneとWebView

ここからはネイティブの話しになります。

iPhoneでアプリ側からWebViewに対してJavaScriptを実行する方法

「App → WebView」

WebViewをもつViewControllerのヘッダファイルにて、UIWebViewDelegateプロトコルを指定しておく。

@interface ViewController : UIViewController <UIWebViewDelegate>

すると以下のメソッドたちが使えるので、準備しておく。

- (void)webViewDidStartLoad:(UIWebView *)webView {
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    return YES; // Return YES to make sure regular navigation works as expected.}
}

あとはstringByEvaluatingJavaScriptFromStringを呼び出すだけ。

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    NSString *title = [self.webView stringByEvaluatingJavaScriptFromString:@"$.method()"];
    NSLog(@"title - %@", title);
}

このメソッドの便利なところは、returnがあるところ、後述するがAndroidのほうだとreturnが返ってこないので、少し強引に取得する必要があったりする。

WebViewからiPhoneアプリ側に処理を通知する方法

「WebView → App」

こっちはURLのリクエスト時に、自分宛、つまり自分のアプリ内で処理して欲しいURLを渡して、それを監視している。
結果、ルールにのっとったURLの場合は、適切な処理を呼べば良い。

面白いところは、自分宛のリクエストはreturn NOを返してなかったことにしている。
これをしないと、真っ白な画面に遷移してしまう。

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
  NSString *requestString = [[request URL] absoluteString];
  NSArray *components = [requestString componentsSeparatedByString:@":"];

  if ([components count] > 1 &&
        [(NSString *) [components objectAtIndex:0] isEqualToString:@"myapp"]) {
    if ([(NSString *) [components objectAtIndex:1] isEqualToString:@"myfunction"]) {
      // ここで何か処理
      NSLog(@"%@", [components objectAtIndex:2]); // param1
      NSLog(@"%@", [components objectAtIndex:3]); // param2
    }
    return NO;
  }

  return YES; // Return YES to make sure regular navigation works as expected.}
}

WebViewからの呼び出し方。

location.href = "myapp:" + "myfunction:" + param1 + ":" + param2;

比較的にシンプルにできるが、ディレクトリトラバーサルなど、セキュリティに気をつける必要がある。


AndroidとWebView

Androidでアプリ側からWebViewに対してJavaScriptを実行する方法

iPhone型と違って、view.loadUrlにjavascriptスキームを渡してもreturnが得られません。
なので、ハックに近いですが、あえてalertを実行させ、そのアラートが出る前のイベントonJsAlertでメッセージから情報を取得します。
そのあとに、result.confirm()をすることで、アラートがなかったことにできるようです。

なかなか面白いですね。

WebView webview = (WebView) findViewById(R.id.webView1);
webview.getSettings().setJavaScriptEnabled(true);

webview.setWebViewClient(new WebViewClient() {
  @Override
  public void onPageFinished(WebView view, String url) {
    view.loadUrl("javascript:alert($.method())");
  }
});
webview.setWebChromeClient(new WebChromeClient() {
  @Override
  public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
    try {
      System.out.println("onJsAlert - " + message);

      return true;
    } finally {
      // アラートが出ないようにしている
      result.confirm();
    }
  }
});

webview.loadUrl("http://lab.hisasann.com/appEval/");

WebViewからAndroidアプリ側に処理を通知する方法 – その1

WebViewからAndroidアプリ側に情報を渡すのは以下の感じで、iPhone側とすごく似ていますね。
ただ、Androidのほうは直接JavaScriptコードからAndroid側のアプリをキックできるようです。

それは後述します。

webview.setWebViewClient(new WebViewClient() {
  @Override
  public boolean shouldOverrideUrlLoading(WebView view, String url) {
    String[] components = url.split(":");

    // 特定のURLの場合、ダイアログを表示する等
    if (components[0].length() > 1 && "myapp".equals(components[0])) {
        if ("myfunction".equals(components[1])) {
          System.out.println(components[2]);
          System.out.println(components[3]);

          return true;
        }
    }

    return false;
  }
});

WebViewからの呼び出し方。

location.href = "myapp:" + "myfunction:" + param1 + ":" + param2;

WebViewからAndroidアプリ側に処理を通知する方法 – その2

先ほどのwebview変数にJavascriptInterfaceをセットします。

webview.addJavascriptInterface(new JavascriptAdapter(), "android");

そのアダプターの中で、JavaScriptから呼び出されるメソッドを定義しておきます。

package com.example.sample_webvieweval;

import android.os.Handler;

public class JavascriptAdapter {
  private Handler handler = new Handler();

  // JavaScriptから呼び出されるメソッド
  public void getFromJS(final String s) {
    handler.post(new Runnable() {
        public void run() {
          System.out.println(s);
        }
    });
  }
}

WebViewからの呼び出し方。

// Javaのメソッドを呼び出す
window.android.getFromJS("called getFromJS");

するとこのようにwindowオブジェクトの中にJavascriptInterfaceの名前空間が追加されているので、そこから呼びたいメソッドをコールできます。

すごく簡単!
すごく簡単なんだけど、やっぱりこれもセキュリティをかなり意識しないといけないですね。

参考リンク

本格アプリを作ろう! Androidプログラミングレシピ
Dave Smith Jeff Friesen
インプレスジャパン
売り上げランキング: 13499

CoffeeScript・Sassをgruntでコンパイルする

コンパイルしたりconcatしたりminifyしたりといたれりつくせりなgruntをためしてみました。
今後これでやっていこうと思います。

ぼくはなぜかgruntjs/grunt-contribがうまくロードできなくて、

.zshrcで

npm_dir=${NVM_PATH}_modules
export NODE_PATH=$npm_dir

とやっているのに、モジュールが見つからないと!

なので、taskは自作してそれを呼び出しています。

task

追加したタスクたち

  • coffee
  • sass
  • compass

grunt.js

/*global module:false*/
module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    // ------------------------------------------------------------------------
    meta: {
      version: '0.1.0',
      banner: '/*! PROJECT_NAME - v<%= meta.version %> - ' +
        '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
        '* http://PROJECT_WEBSITE/\n' +
        '* Copyright (c) <%= grunt.template.today("yyyy") %> ' +
        'hisasann; Licensed MIT */'
    },
    // ------------------------------------------------------------------------
    coffee: {
      app: {
        src : ['coffee/index.coffee'],
        dest : 'Resources/js/'
      },
      sample: {
        src : ['coffee/sample/*.coffee'],
        dest : 'coffee/sample/'
      }
    },
    // ------------------------------------------------------------------------
    compass: {
      sample: {
        src : ['scss'],
        dest : 'Resources/css'
      }
    },
    // ------------------------------------------------------------------------
    concat: {
      sample1: {
        src: ['<banner:meta.banner>', 'coffee/sample/sample1.js'],
        dest: 'coffee/sample/sample1.js'
      },
      sample2: {
        src: ['<banner:meta.banner>', 'coffee/sample/sample2.js'],
        dest: 'coffee/sample/sample2.js'
      },
      sampleall: {
        src: ['coffee/sample/*.js'],
        dest: 'coffee/sample/sample-all.js'
      }
    },
    // ------------------------------------------------------------------------
    min: {
      sample: {
        src: ['coffee/sample/sample-all.js'],
        dest: 'Resources/js/sample/sample-all.min.js'
      }
    },
    // ------------------------------------------------------------------------
    watch:{
      coffee:{
        files:['coffee/**/*.coffee'],
        tasks:'coffee concat min'
      },
      compass:{
        files:['scss/**/*.scss'],
        tasks:'compass'
      }
    },
    // ------------------------------------------------------------------------
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        boss: true,
        eqnull: true,
        browser: true
      },
      globals: {}
    },
    uglify: {}
  });

  // Default task.
  grunt.registerTask('default', 'coffee compass concat min');

  // --------------------------------------------------------------------------
  //
  // register custom tasks and helpers.
  //

  var log = grunt.log;
  var exec = require('child_process').exec;

  grunt.registerHelper('exec', function (opts, done) {
    var command = opts.cmd + ' ' + opts.args.join(' ');
    exec(command, opts.opts, function (code, stdout, stderr) {
      if (!done) return;
      if (code === 0) {
        done(null, stdout, code);
      } else {
        done(code, stderr, code);
      }
    });
  });

  var handleResult = function handleResult(err, stdout, code, done) {
    if (err) {
      log.writeln(stdout);
      done(false);
    } else {
      log.writeln('complete!');
      done(true);
    }
  };

  // task: coffee
  (function (grunt) {
    grunt.registerHelper('coffeec', function (fromdir, dest, done) {
      var args = { cmd:'coffee', args:[ '--compile', '--output', dest, fromdir ] };
      grunt.helper('exec', args, function (err, stdout, code) {
        handleResult(err, stdout, code, done);
      });
    });

    grunt.registerMultiTask('coffee', 'compile CoffeeScript', function () {
      grunt.helper('coffeec', this.data.src, this.data.dest, this.async());
    });
  }(grunt));

  // task: sass
  (function (grunt) {
    grunt.registerHelper('sassc', function (from, dest, done) {
      var args = { cmd:'sass', args:[ from + ':' + dest] };
      grunt.helper('exec', args, function (err, stdout, code) {
        handleResult(err, stdout, code, done);
      });
    });

    grunt.registerMultiTask('sass', 'compile sass', function () {
      grunt.helper('sassc', this.data.src, this.data.dest, this.async());
    });
  }(grunt));

  // task: compass
  // gem install compass
  (function (grunt) {
    grunt.registerHelper('compassc', function (fromdir, destdir , done) {
      var args = { cmd:'compass compile', args:[ '--sass-dir ' + fromdir + ' --css-dir ' + destdir + ' --boring' ] };
      grunt.helper('exec', args, function (err, stdout, code) {
        handleResult(err, stdout, code, done);
      });
    });

    grunt.registerMultiTask('compass', 'compile sass', function () {
      grunt.helper('compassc', this.data.src, this.data.dest, this.async());
    });
  }(grunt));

};

gist

CoffeeScript・Sassをgruntでコンパイルする — Gist

これで、CoffeeもSassも楽しくいじれる!

watchしたかったら

grunt watch

いいね!

一応githubに全体をpushしておきました
hisasann/CoffeeSass-GruntCompile

追記:2012/12/21 17:42:24
grunt.loadNpmTasks(‘grunt-compass’);を使うほうに修正。

参考リンク

実践JS サーバサイド JavaScript 入門
井上 誠一郎
技術評論社
売り上げランキング: 42782

スマートなrequestAnimationFrameのクロスブラウザ対応

スマートかどうか微妙ではありますが、
またもCSS 3D Cloudsのコードを読んでて発見したコード。
いままで以下のようにコードを使っていたんですが、

window.requestAnimFrame = (function(){
    return  window.requestAnimationFrame       ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame    ||
            window.oRequestAnimationFrame      ||
            window.msRequestAnimationFrame     ||
            function( callback ){
              window.setTimeout(callback, 1000 / 60);
            };
  })();

こっちの書き方のほうがキレイだなーということで今後はこっちを使ってみます。
コードの雰囲気からすると↑のほうが把握しやすいですが。

(function() {
  var lastTime = 0;
  var vendors = ['ms', 'moz', 'webkit', 'o'];
  for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
    window.cancelRequestAnimationFrame = window[vendors[x] + 'CancelRequestAnimationFrame'];
  }

  if (!window.requestAnimationFrame)
    window.requestAnimationFrame = function(callback, element) {
      var currTime = new Date().getTime();
      var timeToCall = Math.max(0, 16 - (currTime - lastTime));
      var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
      lastTime = currTime + timeToCall;
      return id;
    };

  if (!window.cancelAnimationFrame)
    window.cancelAnimationFrame = function(id) {
      clearTimeout(id);
    };
}())

github

スマートなrequestAnimationFrameのクロスブラウザ対応 — Gist

Foundation HTML5 Animation With Javascript (Foundations Apress)
Billy Lamberta
friendsofED
売り上げランキング: 96806

JavaScriptからブラウザをフルスクリーン化してみる

こちらぼくできるの知らなかったのですが、
CSS 3D Clouds
こちらのコードを見ていたら発見したのでメモ。

サンプルコード

window.addEventListener('load', function() {
  var element = document.getElementById('fullscreenBtn');
  if(element) {
    element.addEventListener('click', function(e) {
      // webkit
      if(document.body.webkitRequestFullScreen) {
        document.body.onwebkitfullscreenchange = function(e) {
          document.body.style.width = window.innerWidth + 'px';
          document.body.style.height = window.innerHeight + 'px';
          document.body.onwebkitfullscreenchange = function() {
          };
        };
        document.body.webkitRequestFullScreen();
      }

      // mozilla
      if(document.body.mozRequestFullScreen) {
        document.body.mozRequestFullScreen();
      }

      e.preventDefault();
    }, false);
  }
});

webkitとmozillaの違いとしてはwebkitのほうは、onwebkitfullscreenchangeイベントにて高さを指定してあげないと
すごくちっちゃくなってしまうこと。

ためした対応ブラウザ

  • Mac – Firefox15
  • Mac – Chrome22
  • Mac – Safari6.0
  • Win – Firefox15
  • Win – Chrome22
  • Win – Safari5.1.2

どのブラウザで見ても、フルスクリーンになるときのモーションに違いがあったので楽しかったです。

サンプルURL

JavaScriptからフルスクリーン化

github

JavaScriptからフルスクリーン化 — Gist

Google Chrome OS ~最新技術と戦略を完全ガイド~
小池良次 中島聡 伊藤千光 太田昌吾 まえだひさこ 向井領治
インプレスジャパン
売り上げランキング: 343048