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

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

2017/11

jQuery を使って HTML の iframe の内側コンテンツを、iframe の外側の JavaScript からスクロールさせる方法を紹介します。

今回は2つの HTML ファイル(main.html と sub.html)を使います。main.html が iframe の外側(というか、iframe を記述する側)のファイルで、main.html の中の iframe に sub.html が指定されて埋め込まれて表示される、という関係だと想定します。sub.html はどんな内容でも構いませんが、強制スクロールさせて表示することを想定しているので、それなりに縦長の、普通に見ても縦スクロールさせないと全体が見えないくらいの大きさのページを使ってください:
2017113001

一方、iframe の外側である main.html が今回の主役で、main.html 内の JavaScript で sub.html をスクロールさせて表示させることが今回の目的です。実現するためには以下のような内容で記述します:
<html>
<head>
<script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script>
$(function(){
  $('#sub').load( function(){
    iframe_scroll( 100 );
  });
});

function iframe_scroll( top ){
  //. 「0.8 秒で 100 ピクセルスクロールし、0.2 秒待つ」を繰り返す
  $('body,html', $('#sub').contents() ).animate( {scrollTop: top}, 800, 'swing' );
  setTimetout( iframe_scroll, 1000, top + 100 );
}
</script>
<title>main</title>
</head>
<body>
<h1>main</h1>
<hr/>
<iframe id="sub" src="./sub.html" width="90%" height="50%"></iframe>
</body>
</html>

jQuery を使って、まず sub.html がロードされた直後に( $('#sub').load() 関数内で)iframe_scroll 関数を実行しています。この関数では iframe 要素のコンテンツを取り出し、animate 関数を使ってスクロールさせています。この例では 0.8 秒かけて 100 ピクセル下にスクロールし、0.2 秒(1000 ミリ秒 - 800 ミリ秒)止まって繰り返す、という例を実装しています。

なお、sub.html をローカルで用意する代わりに外部コンテンツ(http://www.xxx.com/xxxxx.html など)を指定した場合、iframe 内に表示はされますが、クロスドメイン制約にかかって iframe 内の要素にアクセスすることができず、スクロールは行われません。ご注意ください。


jQuery を使うと、スクロールイベントを簡単に取得したり、スクロールイベントに対するハンドリング処理を実現できます:
$(window).scroll( function(){
  var scroll_top = $(this).scrollTop();  //. スクロール位置
    :
  (スクロール時の処理)
  console.log( 'top = ' + top );
    :
});

ただ、少し取扱が難しい面もあります。上記の内容のままだとスクロールを感知する毎にイベントが発生し、その全てをハンドリングすることになります。要するに一回のスクロールの中で何度もこの処理を繰り返すことになります。

これを「スクロールが一段落したらハンドリング処理を行う」ように改良してみます。考え方として一定時間(以下の例では 200 ミリ秒)スクロールイベントが発生しなかったらハンドリングする、という考え方で擬似的に実装してみました:
var timer = false;
$(window).scroll( function(){
  if( timer !== false ){
    clearTimeout( timer );
  }
  timer = setTimeout( function(){
    var scroll_top = $(this).scrollTop();  //. スクロール位置
      :
    (スクロール時の処理)
    console.log( 'top = ' + top );
      :
  }, 200 );
});

青字が追加した部分です。考え方としてはスクロール発生時に 200 ミリ秒のタイマーを設定し、200 ミリ秒以内に同じイベントが発生したらタイマーを再度設定し直しています。そして200ミリ秒間同一のイベントが発生しなかった場合にハンドリング処理を行う、というアルゴリズムです。

これで擬似的にスクロールエンドのイベントに対するハンドリング処理を実現できました。

(参考)
http://www.web-labo.jp/archives/963

Node.js / Express を使って API を作成する場合は、こんな感じの記述で実装することになります(3000 番ポートで listen する POST /mypost を実装する場合の例):
var express = require( 'express' );
var app = express();

  :

app.post( '/mypost', function( req, res ){
   :
   :
});

  :

app.listen( 3000, function(){
  console.log( 'server running on port 3000...' );
});

これで POST /mypost を 3000 番ポートで待ち受ける API ができました。同一アプリケーション内の HTML などからであれば、こんな感じでリクエストを受けることができるようになります:
(jQuery を使って AJAX でポストする例)
  :
$.ajax({
  type: 'POST',
  url: '/mypost',
  data: { a: 1, b: 2, c: 3 }
}).done( function( result ){
  console.log( result );
});
  :

さて、ではこの POST /mypost を外部アプリケーションの HTML からリクエストするにはどうすればいいでしょうか?深く考えずに URL にサーバー名やポート番号も指定して、
(jQuery を使って AJAX でポストする例)
  :
$.ajax({
  type: 'POST',
  url: 'http://servername:3000/mypost',
  data: { a: 1, b: 2, c: 3 }
}).done( function( result ){
  console.log( result );
});
  :

こんな感じ↑にすればいいかというと、たしかに AJAX 部分はこれでいいのですが、結論としてはまだ不充分です。これを実行すると、ブラウザのコンソールには以下のようなメッセージが表示され、期待通りの挙動にはなりません:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://servername:3000/mypost. (Reason: CORS header 'Access-Control-Allow-Origin' missing).

CORS(Cross Origin Resource Sharing) というメカニズムによって、このリクエストはブロックされている、というエラーメッセージです。つまり「 servername:3000 上で動いている POST /mypost という API を外部から呼ぶことはポリシーに反しているのでできない」ということになります。

この外部リクエストを許可する方法はないでしょうか? CORS の設定の問題なので理論上はできるはずですが、その中でも比較的簡単な方法が cors ライブラリを使う方法です:
2017112401


この cors ライブラリを使うと CORS のデフォルトポリシーを変更して、柔軟に API のクロスオリジン対応が可能になります。以下に例を紹介しますが、まずは cors をインストールしておきます:
$ npm install cors

実際にクロスオリジンリクエストを許可するには以下のようにします。まずは全ての API をクロスオリジンで許可する場合です。API の定義を行う前に app.use() で cors を指定します:
var express = require( 'express' );
var app = express();
var cors = require( 'cors' );

app.use( cors() );   //. ここから下に定義する全ての API にクロスオリジン実行を許可する

  :

app.post( '/mypost', function( req, res ){  //. クロスオリジン実行が許可される
   :
   :
});

  :

app.listen( 3000, function(){
  console.log( 'server running on port 3000...' );
});

また、特定の API に対して個別にクロスオリジン実行を許可する場合は、以下のように指定します:
var express = require( 'express' );
var app = express();
var cors = require( 'cors' );

  :

app.post( '/mypost', cors(), function( req, res ){  //. POST /mypost のクロスオリジン実行は許可される
   :
   :
});

  :

app.listen( 3000, function(){
  console.log( 'server running on port 3000...' );
});




 

今更感もありますが、久しぶりにウェブでクッキー(cookie)を使う機会があったのでまとめておきました。

普通の JavaScript だけで扱う場合と、jQuery Cookie を使って便利にする場合と、2通りで記載しています。



【JavaScript でクッキーを書き込む】
document.cookie に '名前=値' のペアを書き込む:

(例)
document.cookie = 'name1=value1';


名前にはセミコロン、カンマ、空白文字は利用できない。
値は UTF-8 で記載し、encodeURIComponent() でエンコードする。


複数の '名前=値' を書き込む場合は繰り返し実行する:

(例)
document.cookie = 'name1=value1';
document.cookie = 'name2=value2';  //. 最新の実行結果だけでなく、2つとも記憶される



パラメータで属性を指定することも可能:

(例)
document.cookie = 'name1=value1;path=/path1';   //. /path1/** 以下のパスにアクセスした場合だけ有効なクッキーを設定
document.cookie = 'name2=value2;max-age=3600';  //. クッキーの有効期限を秒数で設定(指定しないとブラウザ終了時に無効になる)
document.cookie = 'name3=value3;expires=Sun, 17 Dec 2017 01:27:14 GMT';  //. クッキーの有効期限をGMTで設定(指定しないとブラウザ終了時に無効になる)



【JavaScript でクッキーを取り出す】
document.cookie の値をそのまま参照して取り出す:

(例)
var cookies = document.cookie;  //. 有効な '名前=値' のペアが、セミコロン区切りで取得できる



【JavaScript でクッキーを削除する】
「クッキーを削除する」関数は用意されていないので、「強制的に無効にする」方法を使います:

(例)
document.cookie = 'name1=value1;expires=Sat, 1 Jan 2000 00:00:00 GMT';



【jQuery Cookie とは?】
jQuery を使ってクッキーを便利・簡単に扱えるようにしたライブラリです。このライブラリを使う前に jQuery ライブラリを読み込んで置く必要があります:

(例)
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>


【jQuery Cookie でクッキーを書き込む】
$.cookie を使って名前と値を書き込みます:

(例)
$.cookie( 'name1', 'value1' );



オプションを指定することも可能です:

(例)
$.cookie( 'name1', 'value1', { expires: 7, path: '/path1' } ); //. /path1 以下のパスを指定して7日間保持



【jQuery Cookie でクッキーを取り出す】
$.cookie を使って、名前を指定してクッキーを取り出します:

(例)
var cookie = $.cookie( 'name1' );




【jQuery Cookie でクッキーを削除する】
$.removeCookie() を使って削除します:

(例)
$.removeCookie( 'name1' );


マンホールマップなど、地図や位置情報を使ったアプリを何度か作ったことがあります。地図を扱うためのライブラリはいくつかありますが、leaflet.js というオープンソースのマップクライアントライブラリを使う機会がありました:
2017110901


leaflet.js 自体はオープンソースで提供された JavaScript によるマップ操作クライアントライブラリです。ここで扱うマップは OpenStreetMap だったり、国土地理院のものだったり、(プラグインを使えば)Google MAPs だったりを選ぶことができます。

使い方も簡単で、まずは(CDN などから)leaflet の css と javascript をロードしておきます(以下の例では leaflet 1.2.0 を使っています。また後で使うので jQuery もロードしています):
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet.js"></script>

HTML 内にマップを表示する部分を定義します。この例ではブラウザ画面全体に広がる <div id="demoMap"></div> を定義しています:
<style>
html, body  {
	width: 100%;
	height: 100%;
	padding: 0px;
	margin: 0px;
}
#demoMap {
	width: 100%;
	height: 100%;
}
</style>
  :
  :
<body>
<div id="demoMap"></div>
</body>

そしてメイン部分。上記の demoMap 内に地図を表示します。以下の例では OpenStreetMap を使って千葉県船橋市役所周辺の地図を表示し、市役所の位置にマーカーを置いて、2秒おきにマーカーをランダムウォークさせる、というものです:
<script>
//. 船橋市役所の緯度経度(初期位置)
var lat = 35.69471100;
var lng = 139.98262100;

var map = null;
var marker = null;

$(function(){
  //. 船橋市役所を中心とした地図を OpenStreetMap データで表示
  map = L.map('demoMap').setView( [ lat, lng ], 15 );
  L.tileLayer(
    'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: 'Map data © <a href="http://openstreetmap.org/">OpenStreetMap</a>',
      maxZoom: 18
    }
  ).addTo( map );
  
  //. 初期状態で市役所にマーカーを設置
  marker = L.marker( [ lat, lng ] ).addTo( map );

  //. マーカーを2秒おきにランダムウォークさせる
  setInterval( randomWalk, 2000 );
});

function randomWalk(){
  //. マーカー位置をランダムに移動
  lat += Math.random() / 100.0 - 0.005;
  lng += Math.random() / 100.0 - 0.005;
  var latlng = new L.LatLng( lat, lng );
  marker.setLatLng( latlng );
}
</script>


完成品の HTML はこちらに用意しました。全体を確認したい方はこちらから HTML ファイルをダウンロードしてください:
https://raw.githubusercontent.com/dotnsf/samples/master/leafletjs.html


ダウンロードした HTML をウェブブラウザで表示すると、以下のように船橋市役所を中心としたマップが表示され、青いマーカーが2秒おきにその場所を少しずつ変えて移動する様子が確認できると思います:
2017110901


こういったライブラリを使って地図アプリを作っておくと、利用する地図を変える(例えば OpenStreetMap だったり、Google MAPs だったりに変える)のが楽になりますね。

このページのトップヘ