まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:css

むかーしから存在している技術なのですが、イメージマップという便利な機能があります(最近あまり使われなくなったという印象もあります)。これは HTML ページ内の画像内にクリッカブルな領域を複数定義し、いずれかの領域がクリックされたら何らかの処理を行う、というものです。クリックされる領域によって処理内容(このページにジャンプするとか、この JavaScript 関数を実行するとか、・・)を変えることができる、というものです。

例えばこんな感じで実現します。いらすとやさんの『ホワイト企業~ブラック企業のイラスト』を例に紹介します:



このイラスト画像は横650ピクセル、縦137ピクセルです。その中にホワイト企業~ブラック企業が5段階のイラストで表示されています。

画像の左上の座標を ( 0, 0 ) とすると、このうち一番左の企業は、そのビルの矩形部分は左上が ( 37, 13 ) から右下 ( 100, 127 ) という矩形でできています。いま、このビルの矩形部分がクリックされたら "white" というメッセージを表示するようにしてみます:
2019100701


同様にして、5つのビルそれぞれにクリッカブルな矩形部分を定義し、それぞれがクリックされた時に以下のようなメッセージが表示されるようにしてみます:
ビル番号(左から何番目のビル)矩形範囲クリックされた時のメッセージ
1 ( 37, 13 ) - ( 100, 127 ) white
2 ( 165, 13 ) - ( 227, 127 ) lightgray
3 ( 291, 13 ) - ( 355, 127 ) gray
4 ( 481, 13 ) - ( 483, 127 ) darkgray
5 ( 546, 13 ) - ( 610, 127 ) black


これをイメージマップで実現すると、 HTML の該当箇所は以下のようになります:
<body>
  <div class="container">
    <img src="https://1.bp.blogspot.com/-BmJohMucVBI/XYhOV_VMQmI/AAAAAAABVHA/sZF8lMjPedUWSkxBwUGZXmVri2OFKEZ4gCNcBGAsYHQ/s650/company_white_black_kigyou_5dankai.png" border="0" usemap="#image_map"/><br/>
    <map name="image_map">
      <area shape="rect" coords="37,13,100,127" href="javascript:alert( 'white' )"/>
      <area shape="rect" coords="165,13,227,127" href="javascript:alert( 'rightgray' )"/>
      <area shape="rect" coords="291,13,355,127" href="javascript:alert( 'gray' )"/>
      <area shape="rect" coords="418,13,483,127" href="javascript:alert( 'darkgray' )"/>
      <area shape="rect" coords="546,13,610,127" href="javascript:alert( 'black' )"/>
    </map>
  </div>
</body>

<img> タグの usemap 属性でイメージマップの名称を定義します。そして <map> タグを使って、対象(name 属性で指定)の <img> のクリッカブル領域と、クリックされた時のハンドラを href 属性に定義します。上記例ではハンドラを javascript 関数の実行にしていますが、普通に URL を記述すればリンクを作成することもできます。

これで各ビルをクリックした時に、(左から順に)"white", "lightgray", "gray", "darkgray", "black" というメッセージが表示されるようになります。元は1枚の画像でしたが、クリッカブルな領域を5箇所定義することができ、それぞれのクリックハンドラを個別に設定することもできました:

(左から2番めのビルをクリックした時の様子)
2019100703


ここまでが普通のイメージマップです。このイメージマップ、非常に便利な反面で「レスポンシブ対応が難しい」という難点がありました。要はクリッカブルな矩形領域をピクセル絶対値で指定しているため、画像がそのまま表示された場合はいいのですが、レスポンシブ対応などで画面サイズに合わせて拡大縮小されて画面いっぱいに表示された場合、縮尺が変更になってしまい、絶対値で指定していたイメージマップの定義がおかしくなってしまうのでした。

具体的に同じコードをスマホのシミュレーターで確認した結果、下の赤点線部が "lightgray" のクリッカブル矩形部分となっていて、本来の位置からズレていました。これをどうにかしたい、というのが今回のテーマです:
2019100702


色々と調べましたが、答としては jQuery RWD Image Maps という jQuery プラグインを使うことで解決できました:
2019100700



以下に具体的な対応手順を紹介します。

まず HTML 内でレスポンシブのための宣言をしておきます。例えば以下のようなコードが既に含まれているものと仮定します:
  :
<meta name="viewport" content="width=device-width,initial-scale=1"/>
  :


次に jQuery と jQuery RWD Image Maps をロードします。以下の例では CDN からそれぞれをロードしています(jQuery RWD Image Maps の方を後にロードする必要があります):
  :
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery-rwdImageMaps/1.6/jquery.rwdImageMaps.min.js"></script>
  :

CSS で画像サイズの調整を行います。この例ではイメージマップを用いる画像の横幅を 100% に、高さは自動調整するように指定しています:
  :
<style>
img[usemap]{
  max-width: 100%;
  height: auto;
}
</style>
  :

そして最後に JavaScript で該当部分に RWD Image Maps を適用します:
  :
<script>
$(function(){
  $('img[usemap]').rwdImageMaps();
});
</script>
  :

これでスマホでおなじページにアクセスした時でもイメージマップの矩形部分がズレることなく利用できるようになりました:
2019100704




OpenStreetMap とその API を使って簡易ドライブゲームを作って公開しました。

OpenStreetMap は誰でも無料&自由に使える地図や地理情報のオープンデータプロジェクトです。API も公開されており、位置情報アプリケーション開発時に利用することができます:
2019081707


この OpenStreetMap API を使ってドライブゲームを作って公開してみました。GitHub リポジトリはこちらです:
https://github.com/dotnsf/openmap_drive/

2019081708


実質的に index.html ファイル1つだけのプロジェクトなので GitHub Pages で公開もしています。実際に遊ぶには PC のブラウザでこちらを参照してください:
https://dotnsf.github.io/openmap_drive/

2019081702


パラメータを付けずにアクセスすると、デフォルトで三重県鈴鹿市の鈴鹿サーキットのメインストレートの一部が OpenStreetMap でブラウザ画面いっぱいに表示され、その中心に赤い車(この車でドライブします)が表示されます。

キー操作は以下の3つのみです:
 矢印キーの「下」: スタート/ストップ
 矢印キーの「左」: 左旋回
 矢印キーの「右」: 右旋回


とりあえず「下」を押すと動き出します(もう一度押すと止まります)。初期状態では5時の方角を向いているので右下方向に向かって移動を始めます(実際には車の位置は変わらず、地図部分がスクロールしていきます)。矢印の「左」や「右」を押して、なるべくコース上を走るようにしてみましょう:
2019081703
(↑ 1990年にセナとプロストが接触した1コーナー)


ぶっちゃけコースアウトしようが、何かと接触しようが車は動き続けます。なので「細かいことは忘れて」コースを走り続けましょう:
2019081704
(↑ 1989年にセナとプロストが接触したトライアングルシケイン、ここはこの赤い車でも難しい)


起動時に URL パラメータを与えることで初期位置と初期角度を変えることもできます:
 lat: 緯度
 lng: 経度
 anglelevel: 角度(何時の方向、という時計の短針の方角を示す。0~11 の整数)

下図は
 https://dotnsf.github.io/openmap_drive/?lat=43.733009&lng=7.4228981&anglelevel=4
にアクセスした時の様子です。画面下に有名なレストラン "La Rascasse(ラスカス)" が見えていますが、ここは F-1 モナコグランプリ市街地コース上です。これから最終コーナーに向かうラスカス・コーナーのあたり:
2019081705


地図で表示するのは必ずしもサーキットである必要はありません。こちら
 https://dotnsf.github.io/openmap_drive/?lat=35.6828558&lng=139.7596134&anglelevel=1
は日本の皇居前、内堀通りを走る状態からスタートします:
2019081706


実は以前に同様のゲームを Google MAPs API で作って教育目的に使っていたことがありました。他の地図 API と比較して多くの機能を持ち、今でも便利に使えると思ってはいるのですが、Google MAPs API は当時と異なり現在では API キーの取得が必要になってしまい、オープンな教材としては使いにくくなってしまったのでした。


という背景もあって、今回は Google MAPs API の API キーを使うのではなく、地図 API としてはOpenStreetMap API を、leaflet.js ライブラリを併用して使う形で作り直しました。加えて車の画像も「いらすとや」で公開されているものを直接参照し、CSS の transform で角度を変えて表示するように改良しました(この辺り、詳しくは index.html の内容を直接参照してください)。結果として(画像ファイルは不要になり)index.html ファイル1つだけで完結できるようになって、GitHub Pages で公開できる形にもなった、という副産物的なメリットも生まれました。


今回公開したファイルはあくまでテンプレートというか、「これを元に改良するための素」のつもりです。このゲームは上述のように3つのキーだけで(3種類の信号だけで)操作できるので、以前に紹介したキータッチと組み合わせて体感ゲームにしたり、IoT 的な要素を使ってデバイスからの信号を受け取れるようにしたり、身近なものをリモートコントローラー代わりにして使えたら面白いかな、と思っています。あと少し技術方向性は異なるけど、複数台の車を同時に走らせたりとかも面白そう。


jQuery の animate 関数を使う機会があります。

この animate 関数を使うと HTML 要素に比較的簡単にアニメーション要素を加えることができます。サンプルとして 200 x 200 の矩形の左下から右上に <p> 要素を移動させる例を紹介します("animate1" ボタンをクリックすると移動します):

 




このアニメーションのために記述した CSS および JavaScript は以下になります:
<style>
div.sample{
  position: relative;
  border: 1px solid #ccc;
  width: 200px;
  height: 200px;
}
div.sample p.point{
  position: absolute;
  left: 0px;
  top: 180px;
  margin: 0;
  border-radius: 50%;
  width: 20px;
  height: 20px;
  background-color: #d00;
}
</style>
<script>
function animateStraight(){
  var $point = $('#point1');

  var startAnimate1 = function(){
    $point.animate( { left: 180, top: 0 } );
  };

  startAnimate1();
}

</script>

<div  class="sample">
  <p  id="point1" class="point"> </p>
</div>
<button  onclick="animateStraight();">animate1</button>

</body>

初期状態で { left: 0, top: 180 } (矩形の左下)に設置されている id='#point1' の <p> 要素に対して animate() 関数を実行し、そのパラメータに { left: 180, top:0 } (矩形の右上)を指定しています。これだけで現在位置から目的位置までの間を(直線的に)移動するアニメーションが実現できます。

#細かく説明すると、#point1 の <p> 要素の left パラメータを 0 から 180 へ、top パラメータを 180 から 0 へ少しずつ移動させることで実現できています。

ここで紹介したのは位置に関するパラメータでしたが、位置以外でも数字で指定できる要素を使って現在の値から目的の値までアニメーション処理させるだけであれば同様に実現できます。


さて、本ブログエントリで紹介するのは曲線的な軌道でアニメーション移動処理させたい場合の方法です。例えば左下から右上まで、円軌道でアニメーション処理させたい場合にどうすればよいか、という場合の方法を紹介します。

この実現のために animate() 実行時のパラメータの中で step 関数を使います:
    $point.animate(
      { count: 1 },
      {
        step: function( current ){
             :
        }
      }
    );

上の例では値を変化させるパラメータに count を指定しています。値を(初期値の 0 から)1 へ変化させるという指定ですが、この count 自体は位置には関係ありません(つまりこの animate 関数自体では画面上のアニメーションの処理は行っておらず、単に count 変数を 0 から 1 へ変化させているだけです)。

アニメーション処理を行っているのは animate 関数実行時の2つ目のパラメータである step 関数です。この関数は count 関数の値が少しずつ変化するたびに、その値を引数(current)にして実行されるます。なのでこの step 関数の中で current の値を使って要素の移動位置となる x 座標および y 座標を計算して css で位置移動する、という方法によってアニメーション処理を実現します。

具体的にはこのようになります("animate2" ボタンをクリックすると移動します):

 





<style>
div.sample{
  position: relative;
  border: 1px solid #ccc;
  width: 200px;
  height: 200px;
}
div.sample p.point{
  position: absolute;
  left: 0px;
  top: 180px;
  margin: 0;
  border-radius: 50%;
  width: 20px;
  height: 20px;
  background-color: #d00;
}
</style>
<script>
function getXY( t ){
  var r = 180;

  var x = r * t;
  var y = Math.sqrt( ( r * r ) - ( x * x ) );

  return { x: x, y: y };
}

function animateCurve(){
  var $point = $('#point2');

  var current = 0;
  var startAnimate2 = function(){
    $point.animate(
      { count: 1 },
      {
        step: function( current ){
           var point = getXY( current );
           $point.css( { left: point.x, top: point.y } );
        }
      }
    );
  };

  startAnimate2();
}
</script>

<div  class="sample">
  <p  class="point" id="point2"> </p>
</div>
<button  onclick="animateCurve();">animate2</button>

step 関数の中で current の値をパラメータに指定して getXY() 関数を実行しています。getXY() 関数では x2 + y2 = 1802 (中心座標 [0, 0] 、半径 180 の円)上において、[ 0, -180 ] から [ 180, 0 ] まで移動する間の [ x, y ] の軌跡を計算して返しています(※)。また、この getXY() 関数によって返された値を使って、<p> 要素の位置を変える、という処理を加えています。これによって animate() 関数で count の値を 0 から 1 まで直線的に移動させながら、<p> 要素の位置を円曲線に沿って移動させる、という処理を実現しています。

※この円における x 座標は 0 から 180 まで変化します。したがって、x = current * 180 で求めることができます。また x2 + y2 = 1802 より y = (1802 - x2 )の平方根なので、この数値を計算して求めています。これが getXY() 関数の中身です。

この方法を応用することで(曲線の軌道計算式がわかっていれば)animate() 関数で要素を曲線軌道でアニメーション移動させることができます。



わけわからないタイトルになってしまいました。これは
  • ウェブブラウザの JavaScript を使わずに、HTML と CSS でマウスの軌跡を追跡する方法が発見された
  • 実際に確認できるサンプルが Go 言語で実装されていたので、JavaScript に移植してみた
ということです。

まず、元ネタはこれです:
Researcher Finds CSS-Only Method to Track Mouse Movements

spyware


セキュリティの研究者である Davy Wybiral 氏の以下のデモ動画付きツイートを紹介する形で伝えられていました。このデモでは JavaScript が無効にされた Tor ブラウザが使われていますが、確かに左画面でのマウスの動きが右画面で確認できています。左の画面をユーザーが使っていて、右画面ではそこでのマウスの動きがほぼリアルタイムに再現されています:




もともとウェブページにおいては JavaScript を利用することでマウスの動きを検知することができるようになっています。移動したり、クリックしたり、クリックが開放されたり、といったタイミングや、そのイベントが発生したときのマウス位置を知ること自体はこれまでも可能でした。

ただし、それには JavaScript が有効になっている、という条件があります。多くのウェブブラウザにおいて、JavaScript ははじめから有効になっているもので、あえて無効になるよう設定しない限りは有効なままです。また最近のウェブページも JavaScript が有効になっている前提で作られているものが多いので、JavaScript が有効であることが特別に危険ということはないと思っています。 一方で JavaScript を使ったイタズラページが存在していることも事実で、あえて JavaScript を無効にしてから利用する、というシーンが(それによって思い通りに動かない、ということはあるかもしれませんが)ないわけではありません。


・・・という中での今回のニュースです。安全性を高める目的でウェブブラウザの JavaScript を無効にしていても、マウスの動きがウェブページ提供側に知られてしまう可能性(というか方法)があった、というものでした。上述の Davy 氏が Go 言語で作成したサンプルも公開されています(みるとわかりますが、このシンプルなコードだけで実現できるという明瞭さ!):
https://gist.github.com/wybiral/c8f46fdf1fc558d631b55de3a0267771


で、自分がこれを参考にしてサーバーサイド JavaScript である、Node.js 向けに移植したサンプルを作ってみた、というものでした。なのでサブジェクトは正確には
 『クライアント JavaScriptを使わずにCSSでマウスを追跡する方法』をサーバーサイド JavaScript で実装してみた
という感じになりますかね。

ちなみに移植したコードはこちら:
https://github.com/dotnsf/noscript-tracking.js


で、(ブラウザ側の)JavaScript を使わずにどうやってマウスの座標を調べるのか、という話です。具体的には CSS の :hover 疑似クラスと、その background 属性に画像を指定することで、特定のエリアに入ったことを知らせるリクエストをサーバー側に発生させる、という方法によって実現しています。

上記移植コードを見ると、2つのページが定義されています。1つはコンテキストルート( "/" )へのリクエストがあった際に表示される index ページで、そのテンプレートは views/index.ejs です。もう1つは "/watch" へのリクエストがあった際に表示される watch ページ(テンプレートは views/watch.ejs)です。index ページには JavaScript は一切使われていないのですが、このページの上でマウスを動かすと、その軌跡がほぼリアルタイムに watch ページから確認できるようになる、という内容のサンプルです:
2019051501
(↑左が index ページ、右が watch ページ。index ページ上でマウスを動かす様子が watch ページ上に表示されている)


では JavaScript を使わずに、どうやって index ページから watch ページへマウスの軌跡を知らせているかを説明します。今回ブラウザの JavaScript は使いませんが、サーバーサイドのロジックは必要になります。要はスタンドアロンでどうにかできる、というものではないということです。

まず index ページについて、index ページにアクセスすると画面には格子状(今回の例では 50x50)のブロックが表示されます。個々の格子は <p> タグで構成されています。格子は見えないスタイルにすることも可能ですが、今回は視覚的にわかりやすいようにあえて枠線を表示することで見やすくしています。

そしてスタイルシートを使って、個々の格子(<p>)に :hover 疑似クラスと、その時に背景画像が設定されるよう指定します(つまり各格子の上にマウスが来ると、その格子に背景画像が表示されるように設定します)。

これが CSS だけでマウス軌跡を追跡する方法の肝になります。つまり「ある格子の上をマウスが通過した時に、画像を表示するようなリクエストがサーバー側に送られ」ます。そしてサーバー側はそのようなリクエストに対してエラー処理を行い(つまり画像は表示されない=何も変わらない)、画像を表示する変わりにリクエスト内容を記録します。これによってどの格子の上をマウスが通過したのか、という情報をサーバー側に溜めることが可能になります:
2019051603


そしてもう1つの watch ページ側では上記の処理によってサーバー側に記録されたマウスの軌跡情報ごと取得し、index ページと同様の格子ブロックを描画します。ただしその際にマウスが通過した格子だけには背景色が設定され、マウスが通過していない格子と視覚的に違いがわかるようにしています:
2019051604


この2つのページを横に並べて表示し、index ページ(下図左)上でマウスを動かすと、その情報が watch ページ(下図右)上で確認できる、ということが実現できています。なるほどね~。
2019051501


※僕のコードでは watch ページをリロードする機能までは実装していないので、F5 キーや Ctl+R などで watch ページを定期的に更新する必要がありますが、リアルタイムでサーバー側には記録されている、ということがわかると思います。



マンホールマップでも使っている、 jqPuzzle を使って任意画像をスライドパズル化する方法を紹介します。

このスライドパズルは「15パズル」とも呼ばれていて、僕くらいのオッサンは↓こんなのがおもちゃ売り場で売られているのをよく目にしました。「懐かしいゲーム」の1つです:
2018083000



この jqPuzzle を使ったスライドパズル機能はマンホールマップ内の全てのマンホール画像で遊べます。例えばこのマンホール画像ページの「スライドゲームに移動」をクリックすると:
2018083001


紹介されているマンホール画像がこんなスライドパズルに早変わり:
2018083002


"shuffle" ボタンをクリックするとランダムにシャッフルされます。16 が空いた状態でパズルスタートです:
2018083003


空いたピースの上下左右にあるピースをクリックすると、そのピースが空いた部分にスライドして移動します。これを繰り返して 1 から 15 までが正しい位置にくる完成を目指す、というものです。個人的な印象としては1、2、3までは簡単だけど、4を揃える所あたりからコツが必要になってくると思ってます:
2018083004


こんな楽しい機能を提供する jqPuzzle は jQuery を併用して、画像にスライドパズルのインターフェースを追加してくれる CSS および JavaScript のセットです。なお、jqPuzzle が対応する jQuery は 1.x までの模様なので、この点のみ注意が必要です:
2018083001


jqPuzzle を使うには公式サイトから zip ファイルをダウンロード&展開して使います(CDN は見当たりませんでした)。なお jqPuzzle の提供ライセンスは 以下の通り、GPL と MIT のデュアルライセンス、だそうです:
2018083002


利用にあたっては jQuery 1.x をロードした後に CSS と JavaScript をロードします。これで準備完了(以下の例では jQuery v1.6.2 を指定しています。また jqPuzzle の両ファイルはこれを記述する HTML と同じ階層に存在しているものとします):
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
<link rel="stylesheet" type="text/css" href="jquery.jqpuzzle.css"/>
<script type="text/javascript" src="jquery.jqpuzzle.packed.js"></script>

一番簡単な利用方法は <img> タグに jqPuzzle クラスを指定する方法だと思います。実はこれだけでその画像は 4x4 のスライドパズル化されて表示されます:
<img src="sample.jpg" class="jqPuzzle"/>

2018083005


ちなみに "Original" ボタンをクリックすると完成形が、"Numbers" をクリックすると各ピースの数字の表示/非表示が切り替わります。数字表記がないと難易度は一気に上がります。


カスタマイズの要素を加えることも可能です。例えば以下の例では 4x4 で 16 番目の駒を抜くことは変えずに、ボタンの文字を日本語化し、最初からシャッフル済みになるようにしています(というわけでシャッフルボタンも不要なので非表示にしました):
  :
<script type="text/javascript">
var settings = {
  rows: 4,
  cols: 4,
  hole: 16,
  shuffle: true,
  numbers: true,
  language: 'ja',
  control: {
    shufflePieces: false,
    confirmShuffle: true,
    toggleOriginal: true,
    toggleNumbers: true,
    counter: true,
    timer: true,
    pauseTimer: true
  },
  success: {
    fadeOriginal: false,
    callback: undefined,
    callbackTimeout: 300
  },
  animation: {
    shuffleRounds: 3,
    shuffleSpeed: 800,
    slidingSpeed: 200,
    fadeOriginalSpeed: 600
  },
  style: {
    gridSize: 2,
    overlap: true,
    backgroundOpacity: 0.1
  }
};
var texts = {
  shuffleLabel: 'シャッフル',
  toggleOriginalLabel: '元画像',
  toggleNumbersLabel: '数値表示/非表示',
  confirmShuffleMessage: 'シャッフルしてよろしいですか?',
  movesLabel: '回',
  secondsLabel: '秒'
};

$(function(){
  var t = $('img.jqPuzzle');
  t.jqPuzzle( settings, texts );
});
</script>
 :
2018083006


ちょっとした息抜き機能を追加するのに便利なライブラリです。


このページのトップヘ