knowledge base

マークアップ/フロントエンドエンジニアのWEB制作における備忘録です。平日はWEB屋、休日は社会人劇団の主宰・劇作家をしています。

FireFoxでinline-blockがカラム落ちする

inline-blockとletter-spacingを同時指定すると崩れる

たとえば下図のようなレイアウトを実装すると想定します。

f:id:ShinImae:20160107205014j:plain

リストを横並びにした、シンプルなレイアウトです。

これをinline-blockを用いて横並びにすると、なぜかFireFoxだけカラム落ちしてしまいました。

f:id:ShinImae:20160107205029j:plain

実はこれ、inline-block間の隙間を削除するためにlette-spacingで調整をしています。

HTMLは下記の通りです。

<ul class="menu">
    <li class="menu__item">Nav Lorem</li>
    <li class="menu__item">Nav Lorem</li>
    <li class="menu__item">Nav Lorem</li>
    <li class="menu__item">Nav Lorem</li>
</ul>

そして、CSSは下記の通りです。

.menu{
    letter-spacing: -0.4em;
}
  .menu__item{
      display: inline-block;
      letter-spacing: normal;
  }

inline-blockを横並びにすると、改行コードが原因で存在しないはずの隙間が生まれてしまいます。

そのため、letter-spacingを利用して要素同士を隙間なく接するようにするテクニックが存在します。

ところがinline-blockのみの指定では崩れず、どうやらletter-spacingと併用するとこのようなカラム落ちが発生してしまいます。

ブラウザによってinline-blockの実装方法が違うという説もありますが、実際のところ明確な理由ははっきりしていないそうです。

そこで用いられるのが、word-spacingというプロパティです。

単語の間隔を指定するためのプロパティなのですが、letter-spacingと同時に指定することで、崩れを防ぐことができるようです。

指定するのは0.1emでなくてもよいです。とりあえず正の値を指定すれば崩れることがなくなるようです。

.menu{
    letter-spacing: -0.4em;
    word-spacing: 0.1em;
}
  .menu__item{
      display: inline-block;
      letter-spacing: normal;
  }

もし同時に指定することに抵抗があれば、FireFoxのみ有効なCSSハックを用いて指定すればよいでしょう。

.menu{
    letter-spacing: -0.4em;
}
  .menu__item{
      display: inline-block;
      letter-spacing: normal;
  }
@-moz-document url-prefix() {
  .menu{
      word-spacing: 0.1em;
  }
}

transitionのコールバックを実装する

transitionが終了したら実行されるようにしたい

CSS3のプロパティであるtransitionを用いてアニメーションをさせるとき、transition終了時にコールバックを実装したいときがあります。

jQueryのanimateメソッドならば、簡単にコールバック関数を指定できるのですが、transitionを用いた場合はどのようにコールバックを実装したらよいでしょうか。

スタイルシート上では設定できませんが、JavaScriptにて制御ができます。 transitionendというイベントにバインドさせることで、それが可能です。

下記は、水平方向に300px移動した後にアラートを出力するプログラムです。(ベンダープレフィックスを取得するgetPrefix関数を独自に作成していると仮定します)

/* ベンダープレフィックスを得る */
var prefix = getPrefix();

/* cssメソッドにわたすスタイルを格納 */
var animObj = {};
animObj[prefix + 'transform']  = 'translate3d(300px, 0px, 0px)';
animObj['transition'] = prefix + 'transform 400ms swing';

/* アニメーション */
$(ELEMENT).css(animObj);

/* transition終了時 */
$(ELEMENT).on('transitionend', function(){
   alert('moved');
});

このイベント、ブラウザによって実装が異なるため、より確実にバインドしたいならば、以下のように各ブラウザでのイベント名を列挙してしまったほうが無難です。

webkitTransitionEnd 
MozTransitionEnd 
mozTransitionEnd 
msTransitionEnd 
oTransitionEnd 
transitionEnd 
transitionend

ちなみに、別の文脈において同じ要素に複数transitionが指定されているときは注意が必要です。

他の文脈でtransitionが終了したときも同じ処理が実行されてしまうため、以下のようにコールバック処理の中でいちどアンバインドさせてください。

/* ベンダープレフィックスを得る */
var prefix = getPrefix();

/* cssメソッドにわたすスタイルを格納 */
var animObj = {};
animObj[prefix + 'transform']  = 'translate3d(300px, 0px, 0px)';
animObj['transition'] = prefix + 'transform 400ms swing';

/* アニメーション */
$(ELEMENT).css(animObj);

/* transition終了時 */
$(ELEMENT).on('webkitTransitionEnd MozTransitionEnd mozTransitionEnd msTransitionEnd oTransitionEnd transitionEnd transitionend',function(){
   alert('moved');
   $(this).off('webkitTransitionEnd MozTransitionEnd mozTransitionEnd msTransitionEnd oTransitionEnd transitionEnd transitionend');
});

Android 2.3でスクロールを実装する

Android2.3デフォルトブラウザにはiframeに関連するバグだけでなく、他にもスクロールにまつわるバグがあります。

今回はoverflow:scrollにまつわる現象について、その解決方法をご紹介したいと思います。

要素からあふれた部分を表示するoverflowプロパティ

有名な話ですが、Android2.3でoverflow: hiddenは未対応です したがって要素からあふれた部分は問答無用で非表示になり、大きくユーザビリティを損なってしまいます。

スマートフォン表示時の構成を再考するのが抜本的な解決策ですが、たとえばモーダルウィンドウ内のように限られたスペース内でスクロールさせなければいけない場合も少なくはないでしょう。

そのようなときは、JavaScriptの力を借りれば辛うじて対応が可能です。

touchstart/touchmoveを利用する

幸いなことに、Android 2.3でもtouchイベントには対応しているため、これを利用します。

タッチしたときの位置と、スワイプ中の位置を常に取得することで、疑似的なスクロールを生み出します。

※ここではスクロールさせたい要素(本来ならばoverflow:scrollを指定する要素)に「overflowArea」というIDを付与しています。

var scroll_start_x = 0;
var scroll_start_y = 0;
$('#overflowArea').on({
   'touchstart': function(e) {
      scroll_start_x = e.originalEvent.touches[0].pageX;
      scroll_start_y = e.originalEvent.touches[0].pageY;
   },
   'touchmove': function(e) {
      var scroll_end_x = e.originalEvent.touches[0].pageX;
      var scroll_end_y = e.originalEvent.touches[0].pageY;
      $(this).scrollTop($(this).scrollTop() - (scroll_end_y - scroll_start_y));
      $(this).scrollLeft($(this).scrollLeft() - (scroll_end_x - scroll_start_x));
   }
});

これでスクロールは実装できるのですが、ところが上記のコードだけでは動きがどことなく固くなってしまいます。

どうやらtouchmoveイベントにはクセがあるため、その調整をする必要があります。

こちらの記事で解説されているとおり、連続的に発生すると思われているtouchmoveイベントはAndroidでは一回しか発生しないそうです

 

FFBlog Webサイトでスマホ・タブレットのスワイプ動作(touchmove)イベントを確実に取得するには | 株式会社KSK フレックス・ファームビジネスユニット

 

そこで、もともとのスワイプ動作を抑止する必要があるため、上記コードのtouchmove時の処理を次のように変更します。

var scroll_start_x = 0;
var scroll_start_y = 0;
$('#overflowArea').on({
   'touchstart': function(e) {
      scroll_start_x = e.originalEvent.touches[0].pageX;
      scroll_start_y = e.originalEvent.touches[0].pageY;
   },
   'touchmove': function(e) {
      e.preventDefault();
      var scroll_end_x = e.originalEvent.touches[0].pageX;
      var scroll_end_y = e.originalEvent.touches[0].pageY;
      $(this).scrollTop($(this).scrollTop() - (scroll_end_y - scroll_start_y));
      $(this).scrollLeft($(this).scrollLeft() - (scroll_end_x - scroll_start_x));
   }
});

このようにすることで、スムーズなスクロールが実装可能です。

ちなみにAndroid4からはoverflow:scrollに対応しているため、できればバージョンが4以下のときのみ上記の処理を実行するようにすると、なおよいかと思います。

Androidのユーザーエージェントは以下のとおりなので、ここからバージョンを取得します。

Mozilla/5.0 (Linux; Android 4.4.2; ・・・

Androidという文字列から8文字分取得し、比較演算子によるバージョン比較をしやすいように文字列を数値に変換します。

var ua = navigator.userAgent.toLowerCase(); 
if(ua.indexOf('android') != -1 ){
   var version = parseFloat(ua.slice(ua.indexOf('android')+8));
   if(version < 4){
       return true; 
   }
}

上記コードにおいてtrueを返している箇所に、さきほど紹介した疑似スクロールのコードを書くことで、デバイスのOSバージョンが4以下のときのみスクロールを実装できるようになります。

iScrollは使用しない

最後に余談ですが、iOS4以下で固定表示やスクロールを実装することで有名なiScrollというJavaScriptプラグインがあります。

Android2.3の実機で使用した際にブラウザがクラッシュしてしまったことがあるので、ご利用は自己責任でお願いいたします。

Android 2.3で画面がフリーズする

iframeを使っていませんか

レガシーなIEと同じくらい不具合の多い旧式Androidデフォルトブラウザですが、まれにファーストビューのまま画面がフリーズしてしまうことがあります。

そのようなときはまず、iframeを使用していないか疑ってください。

iframeを使用していると、次のいずれかの条件でスクロールが固まってしまうそうです。

  • iframeそのものが、cssで非表示になっている。
  • iframeを包含する要素が、cssで非表示なっている。
  • iframeそのものが、JSによりロード時に非表示にされている。
  • iframeを包含する要素が、JSによりロード時に非表示にされている。

たとえば複数のiframeをボタン押下で表示切り替えするときなど、このバグが発現する格好の条件になってしまいます。

解決策は、display:noneやhide()を使用しないことです。

やりかたは様々ですが、非表示にする代わりにユーザーから見えない場所に配置する等すれば、このバグは解決できます。

水平方向のスクロール量を取得する

jQueryを用いた場合

垂直方向はscrollTopで取得できますが、水平方向のスクロール量はscrollLeftによって取得します。

var scrollLeft = $(ELEMENT).scrollLeft();

引数に数値を与えれば、その数値だけ水平方向にスクロールします。

たとえば左に200pxスクロールさせたい場合は次のように指定します。

$(ELEMENT).scrollLeft(200);

ページ全体のスクロール量も、overflow:scrollを指定された要素のスクロール量も取得できます。

ちなみにページ全体のスクロール量を取得するにはdocumentに対してメソッドを実行します。

$(document).scrollLeft();

バニラJavaScript用いた場合

jQueryのようなメソッドはないのですが、DOMオブジェクトのプロパティを参照することでスクロール量の取得/設定が可能になります。

var ELEMENT = document.getElementById('element');
var scrollLeft = ELEMENT.scrollLeft;

ページ全体に対してスクロール量を取得/設定する際にブラウザごとに実装が違ったため(documentのscrollLeftを参照できる場合とそうでない場合がある)、jQueryを用いることをおすすめします。

空白・全半角を判定するロジック

※inputValという変数に、チェック対象の文字列が代入されていると仮定します。

空白チェック

空文字、半角スペース、全角スペース、タブを検知。

if(inputVal == '' || inputVal.match(/^[\s\u00A0\u3000]+$/g)){
    console.log('空白');
}else{
    console.log('空白ではない');
}

全半角チェック

文字列を一文字ずつ検証し、ASCII文字コードで全角または半角を検査します。

for (var i=0 ; i < inputVal.length; i++){
    var code = inputVal.charCodeAt(i);
    if ((48<=code && code<=57) || (65<=code && code<=90) || (97<=code && code<=122)) {
	  console.log('半角文字');
    }else{
	  console.log('全角文字');
    }
}

現在開いているページの URLを出力

本来ならばページの種別ごとに条件分岐をすることで、WordPressのテンプレートタグを利用して取得できます。

もし条件分岐をせず、ワンライナーで書きたい場合は以下の方法で可能です。

<?php echo("http://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]); ?>