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

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

タグ:bootstrap

Web アプリでドラッグ&ドロップ(以下 DnD と表記)すること自体はできるようになりました。HTML5 では DnD できる要素が多く定義されていたり、HTML5 以前にも mouseup イベントや mousedown イベントをハンドリングすることで独自に実装することは可能でした。 

ただモバイル Web で、つまりスマホのウェブブラウザから DnD を行うことはまだ困難が伴います。そもそもスマホの小さい画面で DnD を行うことが使いやすいかどうか、という根本的な問題もあると思っていて、それが理由かどうかは定かではありませんが、HTML5 の DnD API の多くはスマホブラウザからは使えないことが多いようです。

そういった事情を理解した上で、それでも現状でどこまでできるだろうか? という観点で実現方法を考えて実装し、公開してみました:
https://dotnsf.github.io/mobile_dnd_sample/


上記 URL にスマホのブラウザでアクセスすると以下のような画面になります。4角の枠内に5枚の付箋が貼られているイメージです:
2019111701


各付箋は指でドラッグして位置を変えることができるようにしています。下図はピンクの「ハロー」と書かれた付箋をドラッグして位置を変更した後の様子です:
2019111702


また各付箋はダブルタップすると編集モードになり、書かれた文字を編集することができるようになります。「ハロー」を編集して「ハロー!」にしてみました:
2019111703


編集モードで「OK」をタップすると編集後の文字列が反映されます:
2019111704


付箋を削除する場合は、一度ダブルタップして編集モードにして、「DELETE」をタップします:
2019111705


確認後に削除されて元の画面に戻ります。「abc」と書かれていた付箋が削除されました:
2019111706


新しい付箋を追加する場合は「NEW」をタップし、編集モードで内容を入力します:
2019111707


そして「OK」をタップすると新しい付箋が追加されます。この付箋も同様にドラッグして位置を変えたり、再度編集して内容を変更することができます:
2019111708



一連の機能紹介は以上です。一応スマホのウェブブラウザでもドラッグ&ドロップによる UI が実現できました。サンプルでは全て JavaScript を使ってフロントエンドだけで実現していますが、データベースとのバックエンド連携を加えることで永続化なども実現できると思っています。

詳しくは後述のソースコードを参照いただきたいのですが、今回のサンプルでは HTML5 Canvas を使って、Canvas 内に付箋に相当するオブジェクトを描画して実現しています。Canvas 内の Touch イベントを監視してドラッグを処理しています。また編集モード画面は Bootstrap のモーダルダイアログを使っています。


ソースコードはこちらです。実態は index.html ファイル1つで、全ての HTML と JavaScript がこの中に含まれています:
https://github.com/dotnsf/mobile_dnd_sample



ふと思い立って「モールモール」というパズルゲームを作ってみました。ただし「ゲーム内で使う画像はいらすとや様から入手できるものに限る」という制約を自分に課してみました。つまりいらすとや様から提供されている画像のみを、そのまま(書き足したり、一部だけ切り取ったりせずに)使ってゲームを作る、という条件にしてみました。

「モールモール」はもともと 1985 年に当時のビクター音楽産業(現JVCケンウッド・ビクターエンタテインメント)から MSX 向けにリリースされた、主人公のモグラを操作するパズルゲームでした。その後、他機種への移植や続編のリリースなどもされましたが、シンプルなルールや操作性もあってか、当時の素人プログラマー達が自分の所有していた機種に勝手に移植して雑誌で発表されていることも珍しくありませんでした。

※雑誌にプログラムコードが数ページに渡って記載されていたり、「ソノシート」と呼ばれる片面レコードにプログラム情報が記載されて付録になっていたりした時代がありました。。

実は自分もその一人 σ(^^) です。僕はパソコンの所有が大学4年生になってからと比較的遅かったのですが、高校生の頃にポケットコンピュータ(当時の通称は「ポケコン」)と呼ばれる、コンピュータというよりもプログラミング機能付き電卓を持っていました。その中の1つがシャープの PC-1350 で、当時では画期的な縦4行表示とグラフィック機能を備えており、BASIC によるプログラミングもできるものでした。この「縦複数行表示」が当時のポケコンでは珍しく、広い画面を使って「モールモール」をはじめとするゲームを勝手に移植して遊んでいたのでした。その意味で今でも思い入れあるゲームの1つです。

この「モールモール」、キャラクターとして必要な画像は6種類(ドア、はしご、土、石、イモ、主人公)と比較的少なく、また基本操作も(パズルをギブアップする時などは例外ですが)上下左右の移動のみなので矢印キーだけで実現できます。敵という概念もないので考えている間にやられてしまったり、敵を撃つ必要もありません。リソース的にも非力なポケコンにピッタリのゲームでした(笑)。

で、今回のゲーム制作にあたり、いらすとや様から以下6個の画像を使わせていただいております:

ドア =いろいろな状態のドアのイラスト



はしご =木のはしごのイラスト



 =デジタルデータ風の背景素材(緑)



 =石垣のイラスト(背景素材)



イモ =スネークフルーツのイラスト



主人公 =もぐらのイラスト



これらの画像に手を加えず、そのまま使っています。モールモールでは「土」がなぜか緑色なのですが、デジタルデータ風背景画像が遠くからみるとそれっぽく見えるのが大発見でした(苦笑)。また「イモ」の画像は数種類あったのですが、一番イモっぽく見えたのが「スネークフルーツ」だったのも新発見でした。 (^^;


で、作ってみました。github リポジトリはこちらです:
https://github.com/dotnsf/molemole


リポジトリには画像は含まれていません。必要な画像はすべて動的にいらすとや様から直接ダウンロードして使っています:
2019071801


実質 index.html ファイルだけなので GitHub Pages でも公開しました。PC ブラウザでこちらのリンク先から遊ぶことができます:
https://dotnsf.github.io/molemole/

2019071701


簡単なルールなどは README.md に記載しているので、上記ページを参照ください。簡単に言うと「すべてのイモを取ってからドアまで行けばクリア」です。ただ重力を意識する必要があることと、落ちてくる石をうまく使わないとクリアできない面があったりします。

例えば上図は第0ステージですが、この面は普通にイモを取りに行って、そのままドアまで向かえばゴールです。よほど捻くれた取り方をしない限りは詰むことはないと思います。

でも第1ステージはこうなります:
2019071703


この面の場合、何も考えずにイモを取りに行ってしまう↓と・・・、ドアにたどり着く術がなくなってしまいます。こうなると "retry" ボタンでリセットするしかありません。石をうまく誘導しながらイモを取る必要があります:
2019071704

 
先に進むともう少し複雑なステージも待っています:
2019071702


README.md にステージ追加のカスタマイズ方法も乗せているので、興味ある人はダウンロード後に自分でステージを考えて追加して遊んでみてください。



Bootstrap を使うとモーダルダイアログを簡単に作ることができます。


モーダルダイアログは親画面の中で表示されるダイアログボックス画面のことで、「このダイアログボックスを閉じるまで親画面や他の画面に移動することができない」という特徴を持っています。利用者に質問を促して「はい」か「いいえ」のボタンを押させる、といった時の、どちらかを押すまで他の処理ができなくなる(どちらかを押す前に他の処理をされては困る)場合などに使われます。

具体的にはこんな感じで実装できます:
<html>
<head>
<meta charset="utf8"/>
<title>Bootstrap モーダルから別のモーダルへ</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
</head>
<body>
  
  <nav class="navbar">
    <a href="#" class="navbar-brand">Bootstrap モーダルから別のモーダルへ</a>
  </nav>

  <!-- Button trigger modal -->
  <div class="container">
    <a href="#" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal1">モーダル1</a><br/>
    <a href="#" class="btn btn-success" data-toggle="modal" data-target="#exampleModal2">モーダル2</a>
  </div>

  <!-- Modal1 -->
  <div class="modal fade" id="exampleModal1" tabindex="-1" role="dialog" aria-labelledby="exampleModal1Label" aria-hidden="true">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModal1Label">モーダル1</h5>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <div class="modal-body">
          <p>1番目のモーダル</p>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        </div>
      </div>
    </div>
  </div>

  <!-- Modal2 -->
  <div class="modal fade" id="exampleModal2" tabindex="-1" role="dialog" aria-labelledby="exampleModal2Label" aria-hidden="true">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModal2Label">モーダル2</h5>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <div class="modal-body">
          <p>2番目のモーダル</p>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        </div>
      </div>
    </div>
  </div>
</body>
</html>

画面内には2つのモーダルダイアログ(便宜上、「モーダル1」と「モーダル2」と呼ぶことにします)が定義されていて、最初はどちらも非表示状態になっています。初期画面では2つのボタンがあり、それぞれモーダル1、モーダル2を表示するためのボタンになっています。モーダル1を表示している間、元の親画面は薄暗くグレーアウトされ、モーダル1を閉じるまではこの画面上に戻って処理を行うことはできません。モーダル2も同様です。HTML を見ていただくとわかるのですが、特別に JavaScript などを定義することもなく、Bootstrap の標準機能の一部として定義されているので、CSS の指定だけで実現できることがわかります。またモーダルダイアログ内を HTML で簡単&自由度高くカスタマイズできる点も便利です:



では「モーダル1を表示し、モーダル1を消すと同時にモーダル2を呼び出す」ということはできるでしょうか? 結論としてはできるのですが、少し JavaScript を使う必要があります。

上記を少し改良しました。モーダル1を閉じる際に「普通に閉じる」ボタンと「閉じてモーダル2を呼び出す」ボタンを用意しています。そして後者がクリックされた時に以下の JavaScript が呼び出されるように定義しました:
<html>
<head>
<meta charset="utf8"/>
<title>Bootstrap モーダルから別のモーダルへ</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script>
function changeModal(){
  $('body').removeClass( 'modal-open' );
  $('.modal-backdrop').remove();
  $('#exampleModal1').modal( 'hide' );
  
  $('#exampleModal2').modal();
}
</script>
</head>
<body>
  
  <nav class="navbar">
    <a href="#" class="navbar-brand">Bootstrap モーダルから別のモーダルへ</a>
  </nav>

  <!-- Button trigger modal -->
  <div class="container">
    <a href="#" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal1">モーダル1</a><br/>
    <a href="#" class="btn btn-success" data-toggle="modal" data-target="#exampleModal2">モーダル2</a>
  </div>

  <!-- Modal1 -->
  <div class="modal fade" id="exampleModal1" tabindex="-1" role="dialog" aria-labelledby="exampleModal1Label" aria-hidden="true">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModal1Label">モーダル1</h5>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <div class="modal-body">
          <p>1番目のモーダル</p>
          <a href="#" class="btn btn-success" onClick="changeModal()">モーダル2へ</a>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        </div>
      </div>
    </div>
  </div>

  <!-- Modal2 -->
  <div class="modal fade" id="exampleModal2" tabindex="-1" role="dialog" aria-labelledby="exampleModal2Label" aria-hidden="true">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModal2Label">モーダル2</h5>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <div class="modal-body">
          <p>2番目のモーダル</p>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        </div>
      </div>
    </div>
  </div>
</body>
</html>

こうすることでモーダル1を閉じて、後始末の処理も行い、その上でモーダル2を表示する、という一連の処理をすべて JavaScript で行うようにしています。これでモーダルダイアログから別のモーダルダイアログを表示することができるようになります。



このページのトップヘ