knowledge base

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

VagrantでSSIを有効にする

Boxにもよるのかもしれませんが、多くの場合はデフォルトで無効になっています。

まずSSIをするためのモジュールがインストールされているか、httpd.confにて確認。

下記がコメントアウトされていたらコメントを外します。

LoadModule include_module modules/mod_include.so

確認後、httpd.confのドキュメントルートディレクティブにある下記を変更

■変更前
Options Indexes FollowSymLinks
■変更後
Options Includes Indexes FollowSymLinks

Vagrantのキャッシュが強すぎる

 Vagrantの共有フォルダ機能を利用して環境を構築している場合、ブラウザをスーパーリロードしてもキャッシュが消去されない場合があります。
httpd.confのドキュメントルートディレクティブに以下を記述します。

EnableMMAP Off
EnableSendfile Off

 

インスペクタを用いたモバイル端末での検証方法まとめ

実機での挙動を実機で確認

モバイル用コンテンツの制作をする際、多くの場合はPC用ブラウザのエミュレータであったり、仮想環境にて挙動を確認します。
しかしごく稀に実機でしか起こり得ない崩れやバグが発生することがあります。
検証しなくてもある程度の目安をつけられるものならよいのですが、どうしてもインスペクタを起動して確認しなければ問題の原因を特定できないケースがあります。
以前はAdobe Edge Inspectというツールがありましたが、現在は入手することができません。
そこで2017年現在での検証方法をご紹介します。

Androidの場合

こちらの記事を参考にまとめました

  1. USBケーブルで端末とPCを接続し、Google Chromeを起動。
  2. アドレスバーにchrome://inspectを入力して開く。
    この時、スマホのほうも確認したいサイトをブラウザで表示しておくこと。
  3. chromeに開きたいページのURLを入力し、Openボタンを押下
  4. 開くことに成功したら、いくつかボタンが現れるので、そのうちのInspectボタンを押下。
  5. chrome上にインスペクタが現れるので検証が可能になります。

iOSの場合

こちらの記事を参考にまとめました

  1. ライトニングケーブルで端末とPCを接続し、Safariを起動。
  2. Safari「開発」メニューから「端末の名前」を選択。
    この時、スマホのほうも確認したいサイトをブラウザで表示しておくこと。
  3. 端末で開いているサイトのリストから任意のサイトをクリックします。
  4. Safari上にインスペクタが現れるので検証が可能になります。

Canvasのレスポンシブ対応

Canvasのレスポンシブ対応はなぜ難しいか

Canvasは複雑なグラフィックの描画から、マルチデバイスに対応したアニメーションの実装、3Dの描画まで幅広く対応できる優れものです。

ところがレスポンシブ対応となると格段に難易度が上がります。

その理由について整理してみると、

  • 属性値でサイズを指定するため、CSSでのサイズ指定ができない【こちらを参照】
  • 画像描画の場合、素材となる画像から切り出すサイズ・開始座標を絶対値で指定してCanvasに描画するという考え方である【こちらを参照】

主にこの2点により、レスポンシブデザインやRetina対応ではセオリーとなっている「width:100%」などといった、親要素を基準としたサイズ指定ができないことが難易度を高くしているゆえんとなっています。

ただし、いくつかのテクニックやメソッドを組み合わせることでこれらに対応することが可能となります。

ちなみにzoomやtransformをページ全体にかけるという方法も考えられますが、サイト特性によっては他の要素に縮小をかけられない場合もあるかと思いますので、ここではcanvasのみで実装が完結する方法をご紹介します。

レスポンシブ対応

先に、基本的な考え方からご紹介します。

  1. 素材となる画像と同じアスペクト比となる要素を準備し、その要素のサイズをCanvasの属性値として設定する
  2. 素材となる画像サイズと現在のCanvasサイズとの比率を算出し、その値にもとづいて画像をCanvas上に描画する。

1.2を画像ロード時に、2をリサイズ時に行うことでレスポンシブ対応を実現することができます。

素材と同じアスペクト比を持つ要素を準備する

基本的なロジックは以下の記事と同様です。

shinimae.hatenablog.com

透過画像を用いてもよいのですがソース的に美しくないため、padding-topを用いた方法を採用します。

 

■HTML

<div id="canvas-container">
  <canvas id="canvas"></canvas>
</div>

CSS

#canvas-container{
    position: relative;
    height: 0;
    overflow: hidden;
    padding-top: 56.25%;
}
  #canvas{
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
  }

div#canvas-containerはリサイズしてもアスペクト比16:9を維持し続けます。

canvasはスタイル上ではこのDIV要素と同サイズになるよう指定していますが、先述したとおりCSSでサイズを指定できないので、ここから先はJavascriptを用いて実装してゆきます。

親要素のサイズにもとづいてCanvasのサイズを指定する

下記ソースの通りです。ドキュメント上に描画された要素のサイズはclientWidth/clientHeightで取得することができるため、それをCanvasのDOMオブジェクトのプロパティに代入します。

var container = document.getElementById('canvas-container');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

//親要素のサイズをCanvasに指定
canvas.width = container.clientWidth;
canvas.height = container.clientHeight; 

素材となる画像を描画

画像の描画はdrawImageメソッドを用います。

drawImageメソッドは9個の引数によって画像の指定した範囲をCanvas上に描画することができるのですが、今回は特にクロップなどは行わないため、引数を3つのみ指定します。

第1引数は素材となる画像のオブジェクトを、第2・第3引数には描画範囲のx座標・y座標を指定します(画像を丸ごと描画するならば両者とも0を指定します)。

詳しくはリファレンスをご覧になったほうがより理解しやすいかと思いますので、併せてご覧ください。

先ほどのソースに追記してゆきます。

var container = document.getElementById('canvas-container');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

//イメージオブジェクトを生成
var img = new Image();
img.src = "/path/to/image/hogehoge.png";

//親要素のサイズをCanvasに指定
canvas.width = container.clientWidth;
canvas.height = container.clientHeight; 

//画像読み込み後にdrawImageメソッドでCanvas上に描画する
img.onload = function(){
    ctx.drawImage(img, 0, 0);
}

画面サイズに合わせて、Canvasを(内部的に)縮小させる

CSSでの「width:100%」に相当する描画ロジックを作ります。setTransformメソッドを用いることでCanvasを変形します。

setTransformメソッドについては後述しますので、まずは先ほどのソースに追記をします。

var container = document.getElementById('canvas-container');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

//イメージオブジェクトを生成
var img = new Image();
img.src = "/path/to/image/hogehoge.png";

//親要素のサイズをCanvasに指定
canvas.width = container.clientWidth;
canvas.height = container.clientHeight; 

//画像読み込み後に、setTramsformメソッドで適切な比率に縮小
//その上で、drawImageメソッドでCanvas上に描画する
img.onload = function(){
    var scale = canvas.width / img.width;
    ctx.setTransform(scale, 0, 0, scale, 0, 0);
    ctx.drawImage(img, 0, 0);
}

setTransformメソッドは、CSSでのtransform同様に、scale/skew/translateのすべてを行うことができます。

6つの引数をとり、それぞれの引数に値を指定することで、これらの変形を同時に行うことができます。

6つの引数の意味については、以下の通りリファレンスより引用します。

setTransform(a, b, c, d, e, f)メソッドのそれぞれの引数は、 setTransform(伸縮x, 傾斜y, 傾斜x, 伸縮y, 移動x, 移動y)となります。 何も変形しない場合の引数の値は、setTransform(1, 0, 0, 1, 0, 0)となります。

今回のケースでは、単に伸縮のみを行うため、算出した伸縮率を第1引数・第4引数に指定しています。

リサイズ時

ここまでの一連の流れを、リサイズ時に再描画することで、レスポンシブ対応が可能になります。

先ほどのソースに追記します。

var container = document.getElementById('canvas-container');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

//イメージオブジェクトを生成
var img = new Image();
img.src = "/path/to/image/hogehoge.png";

//親要素のサイズをCanvasに指定
canvas.width = container.clientWidth;
canvas.height = container.clientHeight; 

//画像読み込み後に、setTramsformメソッドで適切な比率に縮小
//その上で、drawImageメソッドでCanvas上に描画する
img.onload = function(){
    var scale = canvas.width / img.width;
    ctx.setTransform(scale, 0, 0, scale, 0, 0);
    ctx.drawImage(img, 0, 0);
}
//リサイズ時
window.onresize = function(){
    var scale = 0;
    //再描画のため必ずCanvasの描画領域をクリアする
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    canvas.width = container.clientWidth;
    canvas.height = container.clientHeight; 
    scale = canvas.width / img.width;
    ctx.setTransform(scale, 0, 0, scale, 0, 0);
    ctx.drawImage(img, 0, 0);
}

こちらをまとめます。

var container = document.getElementById('canvas-container');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var isInit = false; var img = new Image(); img.src = "/path/to/image/hogehoge.png";
//描画 var render = function(){ var scale = 0;
if(isInit){ ctx.clearRect(0, 0, canvas.width, canvas.height);
}else{
isInit = true;
} canvas.width = container.clientWidth; canvas.height = container.clientHeight; scale = canvas.width / img.width; ctx.setTransform(scale, 0, 0, scale, 0, 0); ctx.drawImage(img, 0, 0); } //メイン var main = function(){ img.addEventListener('load', render, false); window.addEventListener('resize', render, false); } //メイン実行 main();

こうすることで、Canvasでもレスポンシブ対応をすることができます。

以下がそのサンプルです(画面をリサイズして、画像が拡縮されることを確認してください)。

See the Pen Canvas Demo by Shin Imae (@shinimae) on CodePen.

ビューポートのサイズに合わせてフォントを拡縮する

iOSではzoomが非対応に

レスポンシブデザインにおいて、画面サイズに合わせてフォントサイズも拡縮しないとデザインを大きく崩してしまう場合があります。

そのようなときは、こちらjQueryプラグインのような挙動を実装する必要があります。
これまでは、そのものずばりjQueryプラグインに頼ったり、またzoomプロパティが強力な手段として使われていました。

これまでは、zoomプロパティがそんなケースに対応する強力な手段でした。 zoomプロパティを用いた方法について言及しますと、基本的なロジックは下記記事で扱われているケース同様、基準となる画面サイズ(たいていはブレイクポイントやviewportのサイズ)と、現在の画面サイズとの比率を算出して、Javascriptでzoomプロパティをスタイル指定することで、先のjQueryプラグインと同様の挙動を実装します。

 

shinimae.hatenablog.com

ところが、iOSではバージョン8よりzoomプロパティが非対応となってしまい、拡縮させることができなくなってしまいました。

jQueryプラグインによる対応方法も残されていますが、ここではよりモダンな方法として、CSS3より登場した新しい単位のひとつ、vwを用いたフォントサイズの指定を紹介します。

vwとは

vwとは、ビューポートの幅に対する単位です。

vhや、vmin、vmaxという単位も存在します。 1vwはビューポートの1/100となる幅です。ですので、ビューポートと同じ幅は100vwとなります。

下記ページにて、もろもろ詳細がまとめられていますので、併せてご参照ください。

developer.mozilla.org

さて本題に戻りますと、ここでは例として、1200pxのデザインデータにおいて、テキストのフォントサイズが16pxであった場合を想定することにします。

要件は、画面サイズ1200px以下は比率を保ったまま縮小する。

基本的な考え方は先述したzoomプロパティの値と同様です。基準となるビューポートの幅(1200px)に対するフォントサイズ(16px)の比率を算出し、その比率をCSSにてフォントサイズの値として指定します。

今回の例では画面サイズ1200pxに対して文字サイズは16pxなので

16 ÷ 1200 = 0.013333....

という算出結果となります。CSSには

p {
  font-size: 1.33vw;
}

と指定します。

このように指定することで、jQueryプラグインを使用しなくても、画面サイズに合わせてフォントサイズも拡縮することができます。文字サイズのみが変化するので、描画領域そのものには影響がありません。

ちなみに無限にフォントサイズが小さくなるわけではなく、下記記事でも言及されているとおり、ブラウザの仕様上、10pxで頭打ちとなります。

shinimae.hatenablog.com

ちなみに先の記事で利用されているtransformを用いた方法は、文字サイズだけでなく描画領域までも縮小してしまうため、手間がかかる上に保守性に欠けるため使わないほうがよいでしょう。

YouTubeの埋め込み再生で、警告メッセージが出る

バイルでは自動再生は行わない

YouTube Iframe Player APIを使用して、埋め込み動画を動的生成したところ、「すぐに再生が開始しない場合は、端末を再起動してください。」というメッセージが表示されるとともに、動画のローディングが終わらないという現象が起こりました。

f:id:ShinImae:20160928145647p:plain

ちなみに、このメッセージが表示されるのは、スマートフォンまたはタブレット端末においてのみです。 

どうやら調べてみたところ、下記記事のとおりJavascriptで自動再生するよう制御していたことが原因であったようです。

iframe埋め込み版 YouTube Player API | YouTube API プログラミング解説

実のところこの案件はレスポンシブ対応でして、さらに申しますとPCのときは自動再生をするという要件でした。その設定がモバイルでも生きてしまったのが要因であったようです。

ということで下記のように、モバイル端末でのアクセス時は自動再生を止めるようロジックを組むことで対応するのがセオリーなようです。

if(isDesktop) ytplayer.playVideo();

※変数isDesktopにはユーザーエージェントを判別した真偽値が、変数ytplayerにはYouTube iframe APIを用いて生成されたオブジェクトが格納されているという前提です。

そもそもモバイル端末では、YouTubeや埋め込みVideoはブラウザの仕様上、自動再生ができません。それなのに(結果的に)無理やり自動再生を行うようなことになってしまい、今回のようなことになってしまったものと思われます。

2016年現在、必ず再生ボタンを押さないと再生できないようになっているらしいので、その点はクライアントにもご理解いただく必要があります。

埋め込み動画のインライン再生について

先ほど、自動再生ができないと書きましたが、実のところインライン再生もできません

もっと厳密に言いますと、iOS Safariでのみインライン再生は不可です(必ず別窓が開いて再生されます)。
このような記事があり、currentTimeを制御することで再生しているように見せかけていますが、検証したところAndroid端末では動作しませんでした(おそらくブラウザの仕様によるものかと思います)。

qiita.com


ということで、動画のインライン再生をどうしても行いたい場合は、iOSAndroidかでロジックを分ける必要がありますが、Windows系やBlackberryは?という範囲にまで話が及んでしまうと検証の工数が膨大になってしまうので、 結論として動画をコマ撮り画像に変換し、Canvasを用いたアニメーションにて実装すればおおむねどの端末でも狙い通りの挙動が実装できると思います。

 

※ちなみにスプライトアニメーションでも対応はできますが、iOS Safariでは2メガ以上の画像を背景として読み込めないという問題があります。また、GIFアニメも最近では表示できるブラウザが増えてきましたが、まだまだすべての端末で対応できている状況ではなく、そもそも対応していても動きがカクついている場合が多いです。そのように考えると、Canvasによる実装が最も堅実であるという結論に達しています(個人的な主観ですが)。

shinimae.hatenablog.com

YouTube Iframe Player APIの利用について

YouTube Iframe Player APIの利用方法についても、備忘録として以下にまとめます。

大きく分けて2通りありますので、サイト特性に応じて使い分けるのがよいでしょう。

iframeを動的生成して利用する場合

①にてAPIを読み込んだのち、②でYTオブジェクトを生成します。onYouTubeIframeAPIReadyという関数名はAPI側より指定されているため、名前を変えることはできません。対象のIDを持つDIVが、そのまま同IDを持つiframeに置き換わりますので、動画を制御できるタイミング(onReadyイベント発火時)になったら③にて好きなように制御してください。

■生成前

<div id="player"></div>

■生成後

<iframe id="player" src="hogehoge"></iframe>

以下はJavascriptのソースです。

//プレイヤーオブジェクト格納用
var ytplayer = {};

//① YouTube Player API の読み込み
var tag = {}
var firstScriptTag = {};
tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);  

//②APIを読み込んだら動画ID、生成元のDOM(ID属性を第一引数にてわたしています)を指定してYT.Playerオブジェクトを生成
var onYouTubeIframeAPIReady = function(){
    var id = 'hogehoge';
    ytplayer = new YT.Player('player', {
        width: '640',
        height: '390',
        videoId: id,
        events: {
            'onReady': onPlayerReady
        },
        playerVars: {
            'modestbranding': 1,
            'autohide': 1,
            'controls': 1,
            'showinfo': 0,
            'rel': 0
        }
    });
}

//③動画の制御
var onPlayerReady = function(e){
    //またはe.target.playVideo()でも可
    ytplayer.playVideo();
}

詳細については、下記公式リファレンスも併せてご参照ください。

developers.google.com

ソース上のiframeにパラメータを渡して利用する場合

iframeの読み込み先のパラメータに「enablejsapi=1」という値を追加します。

<iframe id="player" src="https://www.youtube.com/embed/hogehoge?enablejsapi=1&rel=0&showinfo=0&controls=1&autohide=1&modestbranding=1" frameborder="0" allowfullscreen></iframe>

iframeの読み込み完了のタイミングで、当該iframeのID属性を引数にしてYT.Playerオブジェクトを生成します。

$(document).ready(function(){
  //プレイヤーオブジェクト格納用
  var ytplayer = {};
  $('#player').on('load', function(){
     var id = $(this).attr('id');
     ytplayer = new YT.Player(id);
     ytplayer.playVideo();
  });
});

YouTube APIには動画を制御するための豊富な機能が提供されています。これらの機能を使い分けて、柔軟な実装が可能となっています。

チェックボックスをreadonlyにする

チェックボックスは「見た目上」readonlyにならない

フォーム部品にはそれぞれreadonly属性がサポートされていて、これを指定すれば入力が不可能になります(disabledとは違います、念のため)。

ところがチェックすボックスやラジオボタンにreadonly属性をしていても、見た目上、値のON/OFFができてしまいます。

ただ、フォームのサブミット時に値の送信は行われないため、readonly属性が無効でないわけではありません。

要は見た目と実装に違いがあるだけなので、見た目上readonlyだと思わせる工夫をすればよいのです。

onclick属性を利用

まずは、見た目上、値のON/OFFができないように調整が必要になります。

実装はシンプルで、Javascriptの力を利用します。

<input type="checkbox" name="xxx" reaonly onclick="return false;" />

これにより、クリックをしても値のON/OFFが切り替わることがなくなります。

ところが、フォーカス時のスタイルが設定されていると、値が切り替わらないのにフォーカスしているため、ユーザーの混乱を招きます。

そこで、フォーカス時にはそのフォーカスを外れるように、属性を一つ追加します。

<input type="checkbox" name="xxx" reaonly onclick="return false;" onfocus="this.blur();" />

最後に、カーソルのスタイルを指定して、値の切り替えができないと思わせる装飾を施します。

<input type="checkbox" name="xxx" reaonly onclick="return false;" onfocus="this.blur();" style="cursor:default;" />

これにより、見た目上も編集付加であるとユーザーに思わせることができます。