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

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

タグ:jquery

スマホのブラウザで touchstart や touchmove、touchend イベントなどを監視することで各種タッチ処理やドラッグ、そしてスワイプといった操作を感知して処理することができます。

が、これらを使ってスワイプ(横にスライドさせるような処理)を感知しようとすると結構面倒だったりします。機種ごとの機能差についてはもちろんのこと、touchstart 時の指の場所と時間を記憶して、touchend 時の指の場所と時間を記憶し、「一定時間以内にほぼ横向きに指が移動した」ことを検知できればスワイプとみなす、といった具合に処理するわけですが、この「一定時間以内」とか、「ほぼ横向き」とか、厳密に処理するわけにもいかず、かと言ってどの程度の猶予をもたせて判断するべきか迷う要素もあったりするわけです。

そんなスワイプ処理を比較的簡単にハンドリングできるのが jQuery Mobile 内の1ファンクションである jQuery Swipe です:
2019031100


実際に jQuery Swipe を使ってスワイプ判断をするサンプルを用意しました:
<html>
<head>
<meta charset="utf8"/>
<title>swipe</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script type="text/javascript" src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>

<script>
$(function(){
  $('#myimg').on( 'swipe', mySwipeHandler );
  function mySwipeHandler( evt ){
    alert( '#myimg swiped.' );
  }
});
</script>
</head>
<body>
<img src="https://2.bp.blogspot.com/-hvxUlVBfZUI/XG4Ga7pkzxI/AAAAAAABRrE/Q2vhHjh76T8tFTyZ7D8fFE3MCQMqiKIVgCLcBGAs/s450/champion_belt_boxing_man.png" id="myimg" width="50%"/>
</body>
</html>

HTML はシンプルに(いらすとやの)画像を1つ画面に配置しているだけです。その画像に 'swipe' イベントが発生した場合に '#myimg swiped' という警告ダイアログが表示される、というものです。jQuery と jQuery Mobile は CDN を使ってロードしています。

このコードの肝になっているのはこの部分です:
  $('#myimg').on( 'swipe', mySwipeHandler );

'#myimg' は画像に付けられた id を指定しています。つまり画像に 'swipe' イベントが発生したら mySwipeHandler 関数を実行する、という指定をしています(そして mySwipeHandler 関数では警告ダイアログが表示されます)。

この 'swipe' イベントはデフォルトだと以下の条件で発生します:
条件デフォルト値カスタマイズする場合の設定箇所
スワイプにかかる時間1000ms$.event.special.swipe.durationThreshold
横方向の移動ピクセル30ピクセル以上$.event.special.swipe.horizontalDistanceThreshold
縦方向の移動ピクセル30ピクセル以内$.event.special.swipe.verticalDistanceThreshold


つまりデフォルトではタッチ開始から終了(指を離す)まで1秒以内で、縦方向には30ピクセル以内の移動で横方向に30ピクセル以上移動するようなスワイプが発生した時に 'swipe' とみなし、そのイベントを発生させる、というものになります。変更したい場合は上述箇所を書き換えてください。


このページをアップロードし、スマホから表示するとこんな感じになります:
2019031101


イラスト部分を右や左にスワイプするとイベントが発生して、警告メッセージが表示されます:
2019031102


iPhone でも Android でも動いて楽ちん。もちろん画像以外の <div> などのパーツにも使えます。

なお、今回のサンプルでは jQuery 2.0.3 と  jQuery Mobile 1.4.5 を使っています。jQuery は 2.x であれば動くようですが、3.x だとエラーになるっぽいです(ここになかなか気づけずハマりました):
https://stackoverflow.com/questions/40172264/jquery-swipe-event-does-not-work-with-jquery-3

マンホールマップでも使っている、 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


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


EC サイトの商品画像を効率よく表示するテクニックの1つに「カルーセル(carousel、「回転木馬」とか、日本だと「メリーゴーランド」と表現されることも)」と呼ばれる方法があります。スマホなどの限られた画面サイズの中により多くの画面や画像を含ませる技術で、画面・画像を小さくして詰め込むのではなく、個々の画面は横サイズいっぱいに表示させつつ、(一般的には)横スクロールで画面いっぱいの画像を次々に切り替えて表示するテクニックです。

このカルーセルを実装する方法はいくつもありますが、jQuery と組み合わせて使う slick が有名なようでした。というわけで、使い方を調べてみたので以下メモ代わりに残しておきます:
2018021900


CSS と JavaScript の指定
まず slick は jQuery が読み込まれている前提で動くので、最初に jQuery を読み込んでおきます。その後、CSS 、テーマ CSS、そして Slick の JavaScript をロードします。全て CDN を使うとこんな感じになります:
  :
<script type="text/javascript" src="//code.jquery.com/jquery-2.0.3.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.css" rel="stylesheet"/>
<link href="//cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick-theme.css" rel="stylesheet"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.js"></script>
  :

カルーセルの中身を作成
次にカルーセルの中身になる部分を、以下のような形で作成します(カルーセル全体となる <div> 要素に myclass という名前を付けて、その中に子 <div> 要素の形で各カルーセル内の要素を配置。子要素は HTML でも画像でも可)。

当たり前ですが、このままだと各要素が縦に5つ並ぶだけの UI になります:
  :
<div class="myclass">
 <div>(1番目のカルーセルの中身)</div>
 <div>(2番目のカルーセルの中身)</div>
 <div>(3番目のカルーセルの中身)</div>
 <div>(4番目のカルーセルの中身)</div>
 <div>(5番目のカルーセルの中身)</div>
</div>
  :

カルーセル化
この部分をカルーセル化します。カルーセル全体の div 要素(上記の myclass クラスを指定している部分)に slick() 関数を実行します。実行時のパラメータでカルーセルの挙動をカスタマイズすることも可能です(以下はその例):
<script>
  :
$(function(){
  $('.myclass').slick({
    infinite: false,    //. 端までスクロールしてもループしない
    slidesToShow: 3,    //. 1度に3つ表示
    slidesToScroll: 1,  //. スクロールは1つ単位
    initialSlide: 0,    //. 最初に表示するのは一番最初(上)の要素
    arrows: true,       //. コントロール用の矢印を表示する
    dots: true          //. 全体の位置がわかるような点コントロールを表示する
  })
});
  :
</script>


試しにこのブログエントリ内に同じような要素を作ってみるとこんな感じになりました:
(1番目のカルーセルの中身)
(2番目のカルーセルの中身)
(3番目のカルーセルの中身)
(4番目のカルーセルの中身)
(5番目のカルーセルの中身)


簡単ね~

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

このページのトップヘ