しるてく

技術的な話をします

一部のAndroid4.x端末でtouchstartでe.preventDefault()してもclickイベントが発火する

Galaxy Nexus(Android 4.2.2)とか他のAndroid4.1.2の端末で、e.preventDefault()しているにも関わらずclickイベントが呼ばれてしまうバグ?

背景

iScrollを使用しているサービスを一部のAndroid 4.2.2, 4.1.2端末で見ると、clickイベントが2回呼ばれてしまう。
どうも、iScroll内では、touchstartでe.preventDefault()して、touchendでclickイベントを発火しているのだが、デフォルトのclickと、iScrollのclickの二回呼ばれるらしい。

問題の端末 Galaxy Nexus(android4.2.2)
SO-01E(android4.1.2)
Nexus S(android 4.1.2)
対象のブラウザ WebView, 標準ブラウザ (Chromeは問題なし)
使用ライブラリ iScroll 4

SO-04E(android4.1.2)、SC-04E(android4.2.2)や(4.0|2)系端末だと問題は起きない。もちろんiPhoneも。

コード

iScrollでは、touchendで、ダブルタップ検出のために250ms待ってからclickイベント発火させるみたいなことしているので、擬似的なのを実装。

<div id="click">ここをクリックすると</div>
<div id="result"></div>
 
<script>
  (function() {
    $('#click').on('touchstart', function(e) {
      e.preventDefault();
    }).on('touchend', function() {
      $(this).trigger('click', ['clickされたで']);
    }).on('click', function(e, message) {
      message = message || 'こいつぁ。。。';
      $('#result').append('<div class="child">' + message  + '</div>');
    });
  })();

DEMO

スマホしか対応してないけど。


動作

PCとか普通のブラウザの場合

クリックすると「clickされたで」だけが表示。10回クリックすれば10個表示。

問題の端末

クリックすると「clickされたで」「こいつぁ。。。」の要素が表示される。

対策

どうしようかな…。

フラグ的な何かで管理するとか?
  (function() {
    $('#click').on('touchstart', function(e) {
      e.preventDefault();
      $.data(this, 'is_preventdefault', 1);
    }).on('touchend', function() {
      $(this).trigger('click', ['clickされたで']);
    }).on('click', function(e, message) {
      if ($.data(this, 'is_preventdefault') === 0) return;

      $.data(this, 'is_preventdefault', 0);
      message = message || 'こいつぁ。。。';
      $('#result').append('<div class="child">' + message  + '</div>');
    });
  })();
iScroll 5にするとか

iScroll5だと大丈夫なんだけど、コードが違いすぎて、何が原因でなおってるのか分からん…。
e.stopImmediatePropagation(), e.stopPropagation()あたりかなと思いつつ、上のコードに仕掛けてみたけどダメだった。

まとめ

e.preventDefault()をtouchstartで呼ぶと、clickイベントが発火しないって認識だったんだけど、違うのかな。他の端末では認識通りの挙動したけど、単純にブラウザによって違うだけ?それともバグ?
よく分からんが、こういうケースに出会いましたよってメモで。

手元のトピックブランチをmasterにマージしたか確認する

いろんなブランチさわっていると、どれをmasterにマージしたか忘れてしまって、マージしてないのに終わった気になってgit branch -dとかしちゃいそうなので、まるっと確認するコマンド考えた。

% git co master
% git branch | grep -v '*' | xargs -t -I% git --no-pager log --oneline master..%

手元のブランチ名全部取ってきて、トピックブランチにはあるけどmasterにはないcommitがあるかどうかを一覧してくれる。

xargsの-tは実行前にコマンドラインを標準エラー出力に表示してくれるやつ。


マージされたやつだけを探すなら次のコマンドで十分ですね。

% git co master
% git log --oneline --merges

と思ったら

ふつーーーーーにこんなのがあった!!!

% git branch --no-merged
% git branch --merged

悲しい。最初にヘルプ見ろって話ですね!

compassでcssスプライトするときの出力をできるだけ少なくする試み

概要

Sprite layouts | Compass Documentation

compassでのcssスプライトを試してみたのでメモ。
sprite-backgroundっていうmixinは結構色々な人がブログに書いてるんだけど、retina対応していなかったり、無駄な処理が入っていたりしてなんかしっくりこなかったので自分で実装してみた。

完成品

以下のファイルは使い回せるので、mixins/_sprite.scssみたいにプロジェクトに依存しないファイルとして置いておく。

@mixin sprite-background-size($name, $sprites, $device-pixel-ratio: 2) {
  $_width:  image-width(sprite-file($sprites, $name)) / $device-pixel-ratio;
  $_height: image-height(sprite-file($sprites, $name)) / $device-pixel-ratio;
  height: $_height;
  width: $_width;
  background-size: $_width auto;
}

@mixin sprite-background-position($name, $sprites, $device-pixel-ratio: 2) {
  $_pos: sprite-position($sprites, $name);
  $_x: round(nth($_pos, 1) / $device-pixel-ratio);
  $_y: round(nth($_pos, 2) / $device-pixel-ratio);
  background-position: $_x $_y;
}

@mixin sprite-background-image($name, $sprites, $sprites-image, $display: block) {
  display: $display;
  background-image: $sprites-image;
  background-repeat: no-repeat;
}

// shortcut
@mixin sprite-background($name, $sprites, $sprites-image, $display: block, $device-pixel-ratio: 2) {
  @include sprite-background-image($name, $sprites, $sprites-image, $display);
  @include sprite-background-position($name, $sprites, $device-pixel-ratio);
  @include sprite-background-size($name, $sprites, $device-pixel-ratio);
}

使用方法

単純に色々な画像突っ込んだsprite画像の場合、またはCSS大きくても気にしないよって場合

scss
$sprites-all: sprite-map("all/*.png", $layout: smart);
$sprites-all-image: sprite-url($sprites-all);

.star {
  @include sprite-background('star', $sprites-all, $sprites-all-image);
}
.banner {
  @include sprite-background('banner', $sprites-all, $sprites-all-image);
}
.logo {
  @include sprite-background('logo', $sprites-all, $sprites-all-image);
}
css
.star {
  display: block;
  background-image: url('/img/all-s5858cd14d5.png');
  background-repeat: no-repeat;
  background-position: 0 0;
  height: 16px;
  width: 16px;
  background-size: 16px auto;
}

.banner {
  display: block;
  background-image: url('/img/all-s5858cd14d5.png');
  background-position: -16px 0;
  background-repeat: no-repeat;
  height: 30px;
  width: 80px;
  background-size: 80px auto;
}

.logo {
  display: block;
  background-image: url('/img/all-s5858cd14d5.png');
  background-position: 0 -16px;
  background-repeat: no-repeat;
  height: 40px;
  width: 120px;
  background-size: 120px auto;
}

アイコンとかhoverとか同じサイズの画像が並んだsprite画像の場合

width, heightとか余計にアレするの嫌なので、そういうのは共通のクラスでやって、個別にはpositionしか書かない。

scss
$sprites-pager: sprite-map("pager/*.png", $layout: smart);
$sprites-pager-image: sprite-url($sprites-pager);
@mixin sprite-background-pager-base($name) {
  @include sprite-background-image($name, $sprites-pager, $sprites-pager-image);
  @include sprite-background-size($name, $sprites-pager);
}
@mixin sprite-background-pager-position($name) {
  @include sprite-background-position($name, $sprites-pager);
}

.prev-pager {
  @include sprite-background-pager-base('prev');
  @include sprite-background-pager-position('prev');

  &:hover {
    @include sprite-background-pager-position('prev-hover');
  }
  &.disable {
    @include sprite-background-pager-position('prev-disable');
  }

css

.prev-pager {
  display: block;
  background-image: url('/img/pager/pager-s3354cd445.png');
  background-repeat: no-repeat;
  height: 40px;
  width: 70px;
  background-size: 70px auto;
  background-position: 0 0;
}

.prev-pager:hover {
  background-position: 0 -40px;
}

.prev-pager.disable {
  background-position: 0 -80px;
}

特徴

optionで表示をカスタマイズしやすく

  • sprite画像使う要素のdisplayはblockにしたいときとinline-blockにしたいときがあったのでoptionで渡せるようにした
  • retina対応用にdevice-pixel-ratioを渡せるようにした
    • 普通に"/ 2"とかやると、何やってるのかわかりづらいので変数名で何やっているのかわかりやすくした

mixinを細かく分けて出力をカスタマイズしやすく

  • positionだけ渡したいなと思って、positionだけ吐くmixinを用意してsprite-backgroundで@includeするようにした
    • 細かくimage, size, positionに分けたのでさらに頑張りたい場合は、それぞれの画像サイズが違うspriteの場合でもimageの部分だけ共通クラスにあてることもできる

その他細かいところ

  • sprite-urlを@mixinの外でやっているのはコンパイルの高速化のため
  • sprite-mapで$layout: smartすると余白を埋める感じで画像作ってくれる

Galaxy S2でiframe内の高さを動的に変えるとタップ位置がおかしくなる

あまり検証していないのでメモ書き程度で。
余裕があれば、ちゃんとDEMO用意したいところ。

問題

iframe内でJSを使って動的にiframeの内容物の高さを変更したときに、Galaxy S2(以外もあるかも)で画面をタップすると、高さが変わる前にその位置にあった要素をタップした扱いになってしまう。

確認

とりあえずタップした位置をJSで取得してその位置にマークつけて確認。
こんな感じ。

javascript

// debug用
$(document).on('click', '#wrapper', function(e) {
  $('#tap-point').css({ top: e.pageY, left: e.pageX });
});

html

<div id="tap-point" style="position: absolute; z-index: 999; width: 30px; height: 30px; background-color: #fff;"></div>

対策

Androidではiframe内のコンテンツサイズによってiframe自体が自動拡大するんだけどそれがなんかおかしいのかなと思って、iframeの内容量に変更があったらiframeの高さを明示的に変更するjs書いたらなおった。

もしかしたらもっとよい(というかシンプルな)対策あるかも。

参考

Android4.0とiframeに書いてある以下の記述と同じかな。

js等で要素を位置移動しても、要素ないのタップ領域は移動前の位置に残る

http://blog.webcreativepark.net/2012/07/20-010104.html

Facebookでシェアするときに表示される情報のチェックやキャッシュの削除について

一度ページを公開して、誰かにシェアされたとする。
そのあと、og:imageとかog:titleなどを変更してシェアし直そうとしても、古いデータが表示されてしまうという問題がある。


そんなときは、デバッガー( https://developers.facebook.com/tools/debug
)からURLを入力するとキャッシュを削除してくれるようだ。

デバッガー

f:id:ofsilvers:20130520135147p:plain
ここになんか入力する。

URLを入力したところ

f:id:ofsilvers:20130520135151p:plain
キャッシュを削除するだけじゃなく、もちろん登録情報とかも確認できるので便利。

こんな感じで近況アップデートで確認するのは筋が悪い

f:id:ofsilvers:20130520135404p:plain

まとめ

ちゃんと提供されているデバッグツールをつかいましょう!

iPhoneの辞書機能が使えなくなったときは再起動するとよい

タイトルで完結してるんだけど、先日とつぜん、辞書に登録した用語の変換ができなくなった。
読みを変えてみたり、辞書をいったん削除してみたりしたけどどうにも変換できない。

で、試しに再起動したら変換できるようになった。

めでたしめでたし。

minifyしたファイルをbinary扱いしてやる

minifyしたファイルをgit管理していると、少し書き換えただけでgit log -pしたときにすごいだらだらとdiffがでてきて邪魔くさいなーと思って、

$ cat .gitattributes
*.min.js binary
*.min.css binary

みたいに、minifyしたファイルをbinary扱いしたらdiffがでてこなくてすっきり!

……という使い方は合っているのか分からないけど、まー自分の環境だけに置いとくだけで大分見通しがよくなったので良し。