かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

iOS javascript clickイベントが効かないにはまる。

レスポンシブなサイトを作成していてモバイルの時によくある、メニューアイコンを押したら全画面にメニューが表示されてメニュー以外をクリックするとメニューが閉じる機能を作っていました。
Chromeの開発ツールでは意図したとおりに動作していたのですが、iPhone実機で確認した所メニュー以外の部分をタップしたらメニューを閉じる機能が動作していませんでした。

確認環境

clickイベントが効かないパターンがあるっぽい

動作しなかった例

$(document).on('click', 'div.hitarea', function(){ /*... 処理 */ });

※ 対象の要素を解りやすくするために敢えてdiv.hitareaと書いています

どうやら$(document).on$('body').on のようなイベントの登録のしかたで、イベント対象がaタグではない時にクリックイベントが無視されてしまうようです。

解決方法

1. クリック対象となる要素にcursor: pointerというスタイルを付ける

クリック対象の要素にcursor: pointerというスタイルがあれば、aタグでなくてもクリックイベントが効くようになるようです。(イベントがdocumentやbodyといったものに登録されている場合イベントターゲットがpointerな要素ならタップ可能な要素だとiOSが判定するような仕組みっぽいです。)

今回の例だと次のようにスタイルを加えるとクリックイベントが動作するようになりました。

div.hitarea {
  cursor: pointer;
}

ボタンのように使いたい機能だった場合はこの方法が簡単です。
しかし、今回のようなレスポンシブなサイトで、ボタンエリア以外をクリックしたらメニューを閉じたいというような場合、モバイルで見てる時は良いのですがPCで見るとモーダルのメニュー上どこにマウスを持っていってもカーソルがクリック可能なポインターになってしまうので、ちょっと問題がありました。

2. document, body にイベントを登録しなければOK

iOSでクリックイベントが動作しないのは、documentまたはbodyにイベントが登録されている かつ 対象の要素がaタグでない(pointerのスタイルがない) とき ですので、$(document).on$('body').onとしなければ問題なく動作します。

例えば次のようなHTML構造で

<body>
  <div class="container">
    <div class="hitarea"></div>
  </div>
</body>

単純に最初から画面にある該当要素だけでクリックイベントが効けば良いのであれば

$(document).on('click', 'div.hitarea', function(){ /*... 処理 */ });

ではなく

$('div.hitarea').on('click', function(){ /*... 処理 */ }); 

とすればOKです。

後から追加される要素にもクリックイベントが効く必要があるときは、

$('.container').on('click', 'div.hitarea', function(){ /*... 処理 */ }); 

上記のようにdocumentbodyではなく対象となる要素が追加される親要素にイベント登録をすれば、後から追加されるdiv.hitarea要素にもクリックイベントが効くようになります。

 
Chromeの開発ツールで見た目と動作問題ないなーと思っても実機だと挙動が異なることがあるってのを改めて実感しました。(iOSで見てたブラウザもデフォルトのSafariChromeとも異なりますからそりゃ違いもありますよねw)
モバイルサイト製作時はめんどくさがらずに、ケーブル繋いで実機のブラウザで見て確認・デバックをしなきゃなーって思いました。(でも、ちょっとめんどくさい...

過去にaタグだけどclickイベントが効かない事があったのですが、href属性を付けていなかったので、cursor: poinrerのスタイルが無い扱いになってたから、動作しなかったんじゃないかなって今回の件でナゾが判明した気がしました。


[参考]

CSS Fixedしたコンテンツでスクロールさせたい

モバイルのメニューとかposition: fixedでデザインしたけど、メニューが長かったりデバイス回転させたりしたらメニューが見切れるからfixedしたモーダル内でスクロールを表示させたい時の実装方法のメモ

top, left, right, bottom と overflow を利用する

.modalNav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  overflow-y: auto;
}

position: fixedと一緒に top: 0, bottom: 0, left: 0, right: 0 を使用するとコンテンツがエリア全体に伸びるので、そこにoverflowの指定があればfixedしたコンテンツであってもスクロールを表示させることができるようです。
縦方向のスクロールを発生させるだけならtop: 0;bottom: 0;の指定があればleftrightの指定は特に無くても問題ないようでした。

サンプル

See the Pen Scroll in Fixed modal by KIKIKI (@chaika-design) on CodePen.


HTML&CSSとWebデザインが 1冊できちんと身につく本

HTML&CSSとWebデザインが 1冊できちんと身につく本

Wordpress CMB2 1行text(input)フィールドでHTMLタグを入力できるようにしたい。

WordPressにコードベースでカスタムフィールドを作成することが出来るCMB2。

とても便利なのですが、
1行textフィールド(通常のinput[type="text"])はデフォルトだとHTMLタグを保存時に除去(sanitization)する仕様になっていました。(以前はサニタイズされてなかった気がするのだけど...)
HTMLタグが入るなら最初からtextareaにしとけば良いだけなのですが、運用段階でやっぱりこのフィールドにHTMLタグ入れたいんだよね。とかが発生する可能性は無きにもあらずです。

例えば、こんな感じで改行いれたいとか。
f:id:kikiki-kiki:20171110203347p:plain

sanitization_cb を使って独自のサニタイズ処理を適応する

sanitization_cb
Bypass the CMB sanitization (sanitizes before saving) methods with your own callback. Set to false if you do not want any sanitization (not recommended).

サニタイズを変更したいフィールドのを作成している箇所にsanitization_cbプロパティを作成し、値にデータ保存前に値を処理するコールバック関数名を指定します。

<?php // function.php
// <中略>

$cmb->add_field([
  'id'     => $fieldID,
  'name'   => '発売日',
  'type'   => 'text',
  // sanitaize callback
  'sanitization_cb' => 'cmb_allow_html_cb',
]);

// <中略>

/**
 *  Sanitization Handler: Allow HTML tag
 * @param  mixed      $value      The unsanitized value from the form.
 * @param  array      $field_args Array of field arguments.
 * @param  CMB2_Field $field      The field object
 *
 * @return mixed                  Sanitized value to be stored.
 */
function cmb_allow_html_cb($value, $field_args, $field) {
  // 保存する値を返す
  return wp_kses_post( $value );
}

コールバック関数は最後に保存する値をreturnします。
上の例では、値を保存する前に行われるコールバック関数でwp_kses_post()を使ってWordPressの投稿コンテンツと同じHTMLタグが使える様にしています。

これで、この1行テキストの中では改行<br>を始めとするHTMLタグを使うことが出来るようになりました!

加工したい形式ごとに処理関数作成する手間がありますが、デフォルトの処理関数をバイパスできるので自由度が高いです!

公式リファレンスには同じような使い方のescape_cbというプロパティもありましたが、こちらはサニタイズ処理が終わった後で呼び出されるっぽく、既にHTMLが除去された値が$valueで渡されてきたので、HTMLタグを残したい場合には適していませんでした。


[参考]

PHP 配列のキー・Objectのキー(プロパティ)の有無をチェックしたい。

配列のキーの有無のチェック

<?php
// return: bool
array_key_exists( 'key_name', $array );

array_key_exists
指定した key が配列に設定されている場合、 array_key_exists() は TRUE を返します。 key は配列添字として使用できる全ての値を使用可能です。
注意:
array_key_exists() は、最初のレベルのキーだけを捜します。 多次元配列のネストされたキーは、この関数では見つけられません。

isset()との違い

isset() は未定義またはNULLの時 false になるので、配列の値がnullの時、配列にキーが存在してもfalseになってしまう。
ex

<?php
$arr = [
  'str'        => 'text',
  'blank_str'  => '',
  'no'         => 1,
  'zero'       => 0,
  'bool_true'  => true,
  'bool_false' => false,
  'null'       => null,
];

👇 array_key_exists

<?php
array_key_exists("str", $arr);        // => true
array_key_exists("blank_str", $arr);  // => true
array_key_exists("no", $arr);         // => true
array_key_exists("zero", $arr);       // => true
array_key_exists("bool_true", $arr);  // => true
array_key_exists("bool_false", $arr); // => true
array_key_exists("null", $arr);       // => true
array_key_exists("no_key", $arr);     // => false ... 存在しないキーとの時だけfalse

isset

<?php
isset($arr["str"]);        // => true
isset($arr["blank_str"]);  // => true
isset($arr["no"]);         // => true
isset($arr["zero"]);       // => true
isset($arr["bool_true"]);  // => true
isset($arr["bool_false"]); // => true
isset($arr["null"]);       // => false ... 値がnullだとキーが存在してもfalse
isset($arr["no_key"]);     // => false

オブジェクトのプロパティの有無のチェック

<?php
// return: bool
property_exists( $object, 'property_name' );

property_exists
この関数は、与えられたプロパティ property が 指定されたクラスに存在するかどうかを確認します。
注意:
isset() とは対照的に、 プロパティの値が NULL の場合でも property_exists() は TRUE を返します。

プロパティの値が無くても、プロパティが存在していれば true になる

変数が何の型なのか調べる

<?php
// return: string
gettype( $var );

gettype
PHP 変数 var の型を返します。 型のチェックには、この関数ではなく is_* 関数を使います。


[参考]

初めてのPHP

初めてのPHP

お知らせ。「放射線について考えよう」

こちらのWEBサイトのイラストを担当させていただいております。

多田将先生のわかりやすい文章で章ごとにステップを積むように理解が深まっていくサイトです。 各章5~15分程度で読めてしまうと思いますので、チラッとでも読んでみてください!!

以前に多田先生の本について書いた記事がりますのでこちらもよろしければ。

以上、宣伝でした。


宇宙のはじまり (イースト新書Q)

宇宙のはじまり (イースト新書Q)

ニュートリノ (イースト新書Q)

ニュートリノ (イースト新書Q)

Stylus プロパティ名に変数を使って一緒に"(ダブルコーテーション)や半角スペースを出力したい

Stylus

例えばこんな感じのCSSを出力したくて

[class^="icon-"],
[class*=" icon-"] {
  font-family: 'webfont-icon' !important;
  ... 
}

icon- の部分をを変数にしたい様な時。
変数と一緒に"(ダブルコーテーション)や(半角スペース)を出力させるのが結構落とし穴でした。

Stylusでプロパティ名に変数を使う場合は .color-{$type} の様に{ で変数名を囲ってあげればOKです。(※ Stylusで変数を定義する際に先頭に$は不要ですが、個人的に見やすいので変数名は$始まりにしています。)
[参照] Stylus プロパティの値に変数名を変数で指定したい。 - かもメモ

NG集

変数をそのまま書く
$prefix   = 'icon-'
[class^="{$prefix}"], [class*=" {$prefix}"]
  font-family: 'webfont-icon' !important;

👇

[class^="{$prefix}"],
[class*=" {$prefix}"] {
  font-family: 'webfont-icon' !important;
}

なぜか変数名がそのまま表示される...

一部を文字列結合にしてみる
[class^=" + {$prefix} + "], [class*=" + {$prefix} + "]
  font-family: 'webfont-icon' !important;

👇

[class^=" + {$prefix} + "],
[class*=" + {$prefix} + "] {
  font-family: 'webfont-icon' !important;
}

`"' で囲まれた範囲が、そのまま表示されるっぽい?

プロパティ名全体を文字列結合にしてみる
'[class^="' + {$prefix} + '"]', '[class*=" ' + {$prefix} + '"]'
  font-family: 'webfont-icon' !important;

👇

Plumber found unhandled error:
 ParseError in plugin 'gulp-stylus'

コンパイルエラー

全体が文字列になるのが良くない?
['class^="' + {$prefix} + '"'], ['class*=" '+ {$prefix} + '"']
  font-family: 'webfont-icon' !important;

👇

['class^="'+ icon- + '"'],
['class*=" '+ icon- + '"'] {
  font-family: 'webfont-icon' !important;
}

違うそうじゃない。

{ の中に" を書いてみる
[class^={"$prefix"}], [class*={" $prefix"}]
  font-family: 'webfont-icon' !important;

👇

[class^=$prefix],
[class*= $prefix] {
  font-family: 'webfont-icon' !important;
}

半角スペースは表示されたので惜しい気がする?

{ の中に書いた"\エスケープしてみる
[class^={\"$prefix\"}], [class*={\" $prefix\"}]
  font-family: 'webfont-icon' !important;

👇

[class^="],
[class*="] {
  font-family: 'webfont-icon' !important;
}

違うそうじゃない。。。

{ の中に書いた"\(エスケープ)して文字列結合にしてみる
[class^={\" + $prefix + \"}], [class*={\"  + $prefix + \"}]
  font-family: 'webfont-icon' !important;

👇

[class^="icon-"],
[class*="icon-"] {
  font-family: 'webfont-icon' !important;
}

必要な半角スペースが消えてしまっただけで、非常に惜しい!!

{の中の半角スペースも\(エスケープ)して文字列結合にしてみる
[class^={\" + $prefix + \"}], [class*={\"\ + $prefix + \"}]
  font-family: 'webfont-icon' !important;

👇

[class^="icon-"],
[class*="] {
  font-family: 'webfont-icon' !important;
}

ちょっ、おま……(´・ω・`)

[class^={\" + $prefix + \"}], [class*={\"\s + $prefix + \"}]
  font-family: 'webfont-icon' !important;

※ コレでも同じ 👇

[class^="icon-"],
[class*="] {
  font-family: 'webfont-icon' !important;
}

 

うまくいく方法

1. 別の変数を作ってしまう

$prefix   = 'icon-'

$str1 = '"' + $prefix + '"'
$str2 = '" ' + $prefix + '"'
[class^={$str1}], [class^={$str2}]
  font-family: 'webfont-icon' !important

👇 出力されるCSS

[class^="icon-"],
[class^=" icon-"] {
  font-family: 'webfont-icon' !important;
}

あまりイケてませんが目的は果たせます。

2. { } の中で文字列結合する方法

{ の中での計算は許されているようなので、下記のように囲ってしまえばOKです。

$prefix   = 'icon-'
{'[class^="' + $prefix + '"]'}, {'[class*=" ' + $prefix + '"]'}
  font-family: 'webfont-icon' !important;
$prefix   = 'icon-'
[class^={'"' + $prefix + '"'}], [class*={'" ' + $prefix + '"'}]
  font-family: 'webfont-icon' !important;

👇 出力されるCSS

[class^="icon-"],
[class*=" icon-"] {
  font-family: 'webfont-icon' !important;
}

3. \(エスケープ)を使う方法

プロパティ名内に変数があるとき、\(エスケープ)があるとその直後の文字列はそのまま表示されるようです。

$prefix   = 'icon-'

[class^=\"{$prefix}\"], [class*=\"\ {$prefix}\"]
  font-family: 'webfont-icon' !important

👇 出力されるCSS

[class^="icon-"],
[class*=" icon-"] {
  font-family: 'webfont-icon' !important;
}

これはイイ感じです!
半角スペースの前にも\を付けるのがポイントです。エスケープされてないと半角スペースが消えます(´・ω・`)
 

Stylusでプロパティ名の文字列展開を行う時"など消えてほしくない文字の前には\を書くか、{}の中で計算させると覚えておけば良さそうです!


PugでPHPタグを出力したい

pugはHTMLテンプレートエンジンなのですが gulp などで拡張子をリネームしてしまえば PHPのテンプレートファイルにする事もできます。
ex:

var gulp    = require("gulp"),
    plumber = require("gulp-plumber"),
    rename  = require('gulp-rename'),
    pug     = require("gulp-pug");

gulp.task('pug', function(cb) {
  let options = {
        pretty: true
      };
  return gulp.src( files )
    .pipe( plumber() )
    .pipe( pug(options) )
    .pipe( rename({
      extname: '.php'
    }) )
    .pipe( gulp.dest( destPath ) );
});

こんな時にpugで生成するファイルにPHPタグを出力する方法のメモ。

pugの機能であるHTMLタグなどをエスケープしない Plain Text を利用するとPHPタグを出力することができます。(エスケープされないので、この方法を使うとPHPタグに限らず<script>タグなども出力できます。)

Plain Text
Pug provides four ways of getting plain text
...
And because plain text is not escaped, you can also include literal HTML.
出典: Plain Text – Pug

HTMLのタグ内にPHPを出力する

1行で出力する時はタグの後スペースを置き直接 PHPタグを書く ( Inline in a Tag )

// pug
div <?php the_content(); ?>

// コンパイル後
<div><?php the_content(); ?></div>

 
複数行で出力する場合

1. タグ名 の後に .を置き改行&インデントしてPHPを書く ( Block in a Tag )
// pug
div.
  <?php
    $id = get_the_ID();
    echo get_the_title( $id );
  ?>

// コンパイル後
<div><?php
  $id = get_the_ID();
  echo get_the_title( $id );
?></div>
2. タグ名を書き改行・インデントして行毎に | +スペースに続けてPHPを書く ( Inline in a Tag )
// pug
div
  | <?php
  |   $id = get_the_ID();
  |   echo get_the_title( $id );
  | ?>

// コンパイル後
<div><?php
  $id = get_the_ID();
  echo get_the_title( $id );
?></div>

hrefaltなどの属性の値にPHPを出力する

pugの属性の値はエスケープされるので Inline in a Tag のように書いても上手くコンパイルされません。

// pug
p(href="<?php the_permalink(); ?>") Link Text

// コンパイル後  < > がエスケープされてしまう
<a href="&lt;?php the_permalink(); ?&gt;">Link Text</a> 
!= を使うと属性にPHPタグが出力できる

Using != disables HTML-encoding in attributes.
出典: node.js - Jade: Why && is compiled to &amp; when used in angularjs in Jade template? - Stack Overflow

属性の場合、pugの変数を属性に設定する時と同じように != の後にスペースを開けて"の括った中に出力したいPHPタグを書けばOK。PHPタグを " で囲うことでタグを含む文字列としてそのまま出力させることができます。

// pug
a(href!= "<?php the_permalink(); ?>") Link Text

// コンパイル後
<a href="<?php the_permalink(); ?>">Link Text</a>

HTMLタグ外に直接PHPタグを出力する

PHPファイルの先頭のコメントなど、HTMLタグの外にPHPタグを出力させる方法。
複数行で出力する場合と同じ方法を、先頭にHTMLタグ名無しでも使用することができるようです。

1. . の後に改行したインデント内に記述する方法 ( Block in a Tag )
// pug
.
  <?php
    /**
     * hoge
     */
  ?>

// コンパイル後
<?php
  /**
   * hoge
   */
?>
2. | + スペースで記述する方法 ( Piped Text )
// pug
| <?php
|   /**
|    * fuga
|    */
| ?>

// コンパイル後
<?php
  /**
   * fuga
   */
?>
3. < で始まるタグはプレーンテキストとして解釈される事を利用する ( Literal HTML )

Literal HTML
Whole lines are also treated as plain text when they begin with a left angle bracket (<), ...
出典: Plain Text – Pug

pugは < から始まる行をプレーンテキストとして出力するので、PHPの開始タグに関してはこの方法を利用することができます。

//pug
<?php
.
   /**
    * Mofu
    */
  ?>

// コンパイル後
<?php /**
  * Mofu
  */
?>
//pug
<?php
  |
  | /**
  |  * Bar
  |  */
  | ?>

// コンパイル後
<?php
/**
 * Bar
 */
?>

先頭の <?php だけ Literal HTML で出力し、あとの部分は . block か | を使う必要があるので、全部 . block か | で書いてしまった方が簡単だと思いますが...  

まとめ

pugでPHPタグやhtmlタグを直接出力する時は

  • HTMLタグ内/外 の場合は
    1. . の後に改行しインデントしてタグをそのまま記述する
    2. | に続けて出力するタグをそのまま記述
  • 属性の値として出力する時は != "<?php 出力するコード ?>" の形式で記述

と覚えておけば良さそうです!


[参考]

Pug 変数を返すMixinをつくりたい。


HTMLプリコンパイラのpugを使っていて、引数から計算をして値を返すMixinを作ろうとした所

mixin getCalendarRow(dayNum, start)
  return Math.ceil( (dayNum + start - 1) / 7 );

// 呼び出し側
div(class="row"+ +getMonthRow(30, 6))

👇 次のようなエラーになってしまいました。

Plumber found unhandled error:
 TypeError in plugin 'gulp-pug'
 getCalendarRow is not a function

どうやら、pugのmixinはHTMLとかblockを出力するものでないと、mixinを呼び出した際にエラーになってしまうっぽいです...

function にしてしまえば、変数を返すmixinが作れる!

mixinキーワードでなく-の後に改行とインデントをしてfunctionとして作成すると変数を返すmixinが作成することができるみたいです!!

-
  function getCalendarRow(dayNum, start) {
    return Math.ceil( (dayNum + start - 1) / 7 );
  }

// 呼び出し側 呼び出しは通常の mixin と同じ
div(class="row"+ +getMonthRow(30, 6))

👇 出力

<div class="row5"></div>

(๑˃̵ᴗ˂̵)و ハッピー!

関数を作成する時に - を書いて改行&インデントをするのがポイントです。
- の後にインデントした範囲は普通のjavascriptとして認識されるのかもです。


[参考]

PHP A~Zを簡単に出力したい。

range() 関数を使う

range
range — ある範囲の整数を有する配列を作成する

array range ( mixed $start , mixed $end [, number $step = 1 ] )

<?php
$list = range('A', 'Z');

👇

array(26) {
  [0]=> string(1) "A"
  [1]=> string(1) "B"
  [2]=> string(1) "C"
  [3]=> string(1) "D"
  [4]=> string(1) "E"
  [5]=> string(1) "F"
  [6]=> string(1) "G"
  [7]=> string(1) "H"
  [8]=> string(1) "I"
  [9]=> string(1) "J"
  [10]=> string(1) "K"
  [11]=> string(1) "L"
  [12]=> string(1) "M"
  [13]=> string(1) "N"
  [14]=> string(1) "O"
  [15]=> string(1) "P"
  [16]=> string(1) "Q"
  [17]=> string(1) "R"
  [18]=> string(1) "S"
  [19]=> string(1) "T"
  [20]=> string(1) "U"
  [21]=> string(1) "V"
  [22]=> string(1) "W"
  [23]=> string(1) "X"
  [24]=> string(1) "Y"
  [25]=> string(1) "Z"
}

range()関数はASCIIコードを元に範囲を作るらしいので、
大文字小文字の[A-z]を作ろうと思って、range('A', 'z') としても Za の間に記号が入るので注意が必要っぽい。


[参考]

おしえて A to Z

おしえて A to Z

Javascript Chromeでページトップに戻る(scrollTop)が効かなくなってた件。

Chromeで以前作ったサイトを見ていて、jQueryで実装していたページトップに戻るが効かなくなっているのに気づいてしまいました...

スペック

  • Mac OSX
  • Chrome v61.0.3163.91

$('body').scrollTop() が効かなくなっていた。

今までは、FireFoxIE系はhtmlタグで、webkit系はbodyタグでscrollTopが動作していたので、$('html, body') とするとコールバックが2回呼ばれてしまうのが嫌だったので👇 の様な感じでブラウザ判別してscrollTopを使用するタグを切り替えていました。

var scrollTag = ( window.chrome || 'WebkitAppearance' in document.documentElement.style )? 'body' : 'html';

$(scrollTag).animate({
  scrollTop: 0
}, 'fast', function() {
  console.log('callback');
});

Chromeでは上記の判定で scrollTagbodyになっているので、$('body').scrollTop() が効いているか調べてみると...

// Chrome
$('body').scrollTop(); // => 0 ※常に0が返る

Chromeのアップデートで仕様変更になったようで、Firefoxを同じでhtmlタグでscrollTopが動作するようになり、$('body').scrollTop() が常に0になってしまうようになっていた為に「ページトップに戻る」が動作しなくなっていたようです... (いつのアップデートで変更になってたんだろう???

ブラウザごとの scrollTop の効くタグを調べてみる。

改めて、どのブラウザがbodyで動作するのか、htmlで動作するのかを調べてみました。
IE系が手元に無いので、調べ次第追加します。

👇 codepen に簡単なHTMLとscriptを作成して、各ブラウザでチェックしてみて動作する方のタグを調べます。にちょっとしたチェック用のHTMLとかJSをcodepenに作っておくと緊急時に割りとお役立ちなのです!

See the Pen scroll test HTML or BODY? by KIKIKI (@chaika-design) on CodePen.

ブラウザ バージョン html body
Chrome 61.0.3163.91 ×
Safaei 10.1.2 ×
Firefox 55.0.3 ×
Vivaldi 1.12.955.36 ×
Opera 47.0.2631.83 ×
Safari (iPhone) 10.0 ×

以前調べた時と違って、ChromeVivaldi では body タグではscrollTopが効かなく、htmlタグで効くようになっていました。

scrollTopを使用するタグのブラウザ別判定を変更する。

使用していたscrollTopを使用するタグを決めるブラウザ判定のスクリプトを変更します。

IE・Edgeは調べてません

var ua = navigator.userAgent;
var scrollTag = ( (!window.chrome && 'WebkitAppearance' in document.documentElement.style) || ua.indexOf('OPR') !== -1 )? 'body' : 'html';

Operaが少し厄介で。window.chrome オブジェクトを持っているけど、body タグにしなければならないので、Operaの判定のためだけにユーザーエージェントを利用して ua.indexOf('OPR') !== -1 なら常にbodyになるようにしてあげる必要がありました。

Operaをサポートしないのであれば下記で問題ないように思います。

var scrollTag = ( !window.chrome && 'WebkitAppearance' in document.documentElement.style )? 'body' : 'html';

 
調べていると body, html {height: 100%}というCSSが原因?という記事も出てきたのですが、私の確認できる範囲ではこれを削除してもうまく動作せず、変更時のサイトへの影響範囲が大きいのでjavascriptの判定の方を変更する事にしました。

また、いつブラウザの仕様が変わってしまうか解りませんので、その時はまた調べて変更する必要がありそうです。(メンドー
昔作ったサイトでは色々と動かなくなってそう...


[参考]

トップをねらえ2! Blu-ray Box

トップをねらえ2! Blu-ray Box