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

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

タグ:jquery

ウェブアプリケーションで QR コードを表示したくなることがあります。スマホを持っている人にその場でとあるURLにアクセスしてもらったり、文字列などの情報を送るのに便利な方法だと思っています。

そんな機能が必要な場合、自分は jquery.qrcode.js を使っています。ブラウザの JavaScript(jQuery)だけで文字列の QR コードを簡単に生成することができます。サンプルページを用意したので、こちらで試すとわかりやすいと思います:
https://dotnsf.github.io/jquery_qrcode/

2020110601


画面最上部のテキストフィールドに URL や文字列を入力し、青いボタンを押すと入力した文字列から QR コードを生成して表示する、というものです。この部分は以下のようなコードで実現しています:
  :

<script src="./qrcode.min.js"></script>

  :

<div class="container">
  <input type="text" id="text" value=""/><button class="btn btn-primary" onClick="generateQRCode();">QRコード</button>
</div>

<canvas id="qrcode"></canvas>

<script>
var text = '';


function generateQRCode(){
  $('#qrcoe').html( '' );
  text = $('#text').val();
  if( text ){
    QRCode.toCanvas( document.getElementById( 'qrcode' ), text, function( err ){
      if( err ){
        console.log( err );
      }
    });
  }
}

  :

まず QR コードを描画するための <canvas> タグ(id="qrcode")を用意します。そしてボタンがクリックされるとテキストフィールドの値を取り出し、<canvas> の id を指定して QRCode を描画する、というシンプルなものです。これだけで QR コード化が実現できます。

文字列や URL を読み取らせたい対象者がスマートフォンユーザーだけであればこれだけで最小限必要な機能は実現できます。問題は対象者の中に PC ユーザーがいて、PC ユーザーにもこの URL や文字列を提供したい場合です。PC からは QR コードを読み取れない人が多数だと思うので直接メッセンジャーやメールなどで文字列や URL を送ればいいのですが、では逆に QR コードで表示されている文字列を取り出す便利な方法はないでしょうか? 上記サンプルの場合は QR コード化する文字列をテキストフィールドに指定しているのでテキストフィールドから取り出せばいいのですが、そういうケースばかりではありません。 以下は「QR コード部分をクリックした時に、その文字列をクリックボードにコピーする」ためのコードを加えました。これによってクリップボードからペーストするだけでメッセンジャー等で送れるようになり、UI的にも楽だと思ったのでした。 加えて、QR コード部分をクリックした際に「クリック感」というか「押された感」が出るような、一瞬だけ影ができるような視覚効果を加えてみました。

最終的なコードはこのようになりました:
  :
<script src="./qrcode.min.js"></script>

<style type="text/css">
html, body{
  text-align: center;
  background-color: #fafafa;
  font-size: 20px;
  color: #333;
}
#qrcode:active{
  border: 1px solid #334c66;
  background-color: #69c;
  -webkit-box-shadow:inset 0px 0px 8px #334c66;
  -moz-box-shadow: inset 0px 0px 2px #3a6da0;
  box-shadow: 0px 0px 2px #3a6da0;
}
</style>
</head>
<body>

<div class="container">
  <input type="text" id="text" value=""/><button class="btn btn-primary" onClick="generateQRCode();">QRコード</button>
</div>

<canvas id="qrcode"></canvas>

<script>
var text = '';

$(function(){
  $('#qrcode').on( 'click', function( e ){
    copyClipboard( text );
  });
});

function generateQRCode(){
  $('#qrcoe').html( '' );
  text = $('#text').val();
  if( text ){
    QRCode.toCanvas( document.getElementById( 'qrcode' ), text, function( err ){
      if( err ){
        console.log( err );
      }
    });
  }
}

function copyClipboard( str ){
  //. 空 div と空 pre
  var tmp = document.createElement( 'div' );
  var pre = document.createElement( 'pre' );

  pre.style.webkitUserSelect = 'auto';
  pre.style.userSelect = 'auto';
  tmp.appendChild( pre ).textContent = str;

  //. 画面外へ
  var s = tmp.style;
  s.position = 'fixed';
  s.right = '200%';

  //. body に追加
  document.body.appendChild( tmp );
  document.getSelection().selectAllChildren( tmp );

  //. クリップボードにコピー
  var result = document.execCommand( "copy" );

  //. 要素削除
  document.body.removeChild( tmp );

  return result;
}
</script>

  :

QR コード化したテキストの内容を変数 text に保存しておき、id="qrcode" のキャンバスがクリックされた時に text の値をクリップボードにコピーする、という内容を加えています。また視覚効果としてクリックされた時だけ有効になる #qrcode:active 要素を定義し、影をつけるなど見た目に変化を加えています。


Bootstrap を使うと、簡単にタブを実装することができます:

タブA

タブB

タブC




この中の特定のタブの選択したり、有効化/無効化を JavaScript(jQuery) で切り替える方法を調べ、シンプルな実装方法を見つけたので共有します。また上のタブとボタンで実際の挙動を確認できるようにしています。

まず、特定のタブを選択するにはタブのアンカー(<a id="XXX">)をセレクトして、
$('.tav-tabs a[href="#XXX"]').show( 'tab' );
というメソッドを実行することで選択できます。

またタブのアンカーにつける data-toggle="tab" という属性を取り除くことでタブとしての挙動を無効化することができます。逆に data-toggle="tab" という属性を付けてあげることで、無効化されていたタブを再び有効化することができるようになります:
//. 無効化
$('.tav-tabs a[href="#XXX"]').removeAttr( 'data-toggle' );

//. 有効化
$('.tav-tabs a[href="#XXX"]').attr( 'data-toggle', 'tab' );

あとはこれらを組み合わせることで特定のタブを選択したり、その際に他のタブを有効化/無効化したりすることで実現できるようになります。

上述の画面では以下のようなコードを実装しています。サンプルとしてどうぞ:
(HTML)
<div  class="container">
  <ul  class="nav nav-tabs">
    <li  class="nav-item"><a  href="#tab-a" data-toggle="tab" class="nav-link active">A</a></li>
    <li  class="nav-item"><a  href="#tab-b" data-toggle="tab" class="nav-link">B</a></li>
    <li  class="nav-item"><a  href="#tab-c" data-toggle="tab" class="nav-link">C</a></li>
  </ul>
  <div  class="tab-content">
    <div  id="tab-a" class="tab-pane active">
      <p  class="text-left">タブA</p>
    </div>
    <div  id="tab-b" class="tab-pane">
      <p  class="text-center">タブB</p>
    </div>
    <div  id="tab-c" class="tab-pane">
      <p  class="text-right">タブC</p>
    </div>
  </div>
</div>

<div  class="container">
<button  onclick="selectX();" class="btn btn-xs btn-primary" id="btnX">タブAを選択&タブAのみ有効</button>
<button  onclick="selectY();" class="btn btn-xs btn-success" id="btnY">タブBを選択&タブBとCのみ有効</button>
<button  onclick="selectZ();" class="btn btn-xs btn-warning" id="btnZ">タブCを選択&タブA, B, Cすべて有効</button>
</div>
(JavaScript)
functon activeTab( tabid ){
  $('.nav-tabs a[href="#' + tabid + '"]').tab( 'show' );
}
function enableTab( tabid ){
  $('.nav-tabs a[href="#' + tabid + '"]').attr( 'data-toggle', 'tab' );
}
function disableTab( tabid ){
  $('.nav-tabs a[href="#' + tabid + '"]').removeAttr( 'data-toggle' );
}

function selectX(){
  //. A を選択して A のみを有効にする
  activeTab( 'tab-a' );
  enableTab( 'tab-a' );
  disableTab( 'tab-b' );
  disableTab( 'tab-c' );
}
function selectY(){
  //. B を選択して B, C のみを有効にする
  activeTab( 'tab-b' );
  disableTab( 'tab-a' );
  enableTab( 'tab-b' );
  enableTab( 'tab-c' );
}
function selectZ(){
  //. C を選択して A, B, C すべてを有効にする
  activeTab( 'tab-c' );
  enableTab( 'tab-a' );
  enableTab( 'tab-b' );
  enableTab( 'tab-c' );
}



ウェブページ内の特定要素の見た目を JavaScript で動的に変更する方法を調べました。

一般的にウェブページ内の要素の見た目はスタイルシートで管理されます。例えばこのような感じ:
<head>
<style>
#container1{
  background-color: #fcc;
  height: 100px;
}
</style>
</head>
<body>

<div class="container" id="container1">
</div>

</body>

この時点では #container1 要素はピンク(#ffcccc)で塗りつぶされて表示されます:
2020082601


この部分の「背景色を動的に(JavaScript で)別の色に変えたい」場合、いくつかの方法が考えられます。一般的には背景色が指定された class を複数用意し、id ではなく class 指定で背景色を与えた上で、JavaScript で指定 class を切り替える(jQuery であれば removeClass + addClass)方法です。 ただこの方法は「事前に class を定義しておく」必要があります。切り替える瞬間の情報や属性値を参照して背景色を決めるとか、ランダム要素を使って背景色を決めるなど、事前に定義する内容が定まらないような場合では使えません。このような場合も含めて #container1 要素に定義された内容そのものを変更する(つまりスタイル定義内容そのものを動的に変更する)にはどのようにすればよいかを調べました。以下、jQuery を併用した場合で説明します。

例えば #container1 で定義されるエリアをクリックしたら背景色をランダムに変える、という場合であれば以下のような JavaScript で実現できます:
<script>
$(function(){
  $('#container1').on( 'click', function( e ){
    //. #container1 のスタイルシート定義を動的に変える。具体的には背景色を変更する
    var bgs = [ '#fcc', '#cfc', '#ccf', '#cff', '#fcf', '#ffc', '#ccc' ];
    var bg = bgs[Math.floor( Math.random() * bgs.length )];
    var t = $('#container1')[0];
    t.style['background-color'] = bg;
  });
});
</script>

スタイルシートの内容を変更したい要素をセレクターを指定して取り出し、その取得結果(配列)の最初の要素を取り出します。そして取り出した要素の style 属性がスタイルシートの内容なのでここを JavaScript で上書きするよう記述します(上例では t.style 内の 'background-color' 属性をランダムに上書きしています)。この方法であれば class を使うことなく、切り替えることもなく、スタイルシート定義を書き換えることで見た目の変更を実現できます。


実際に動くサンプルを用意しました:
https://dotnsf.github.io/dynamic-css/


上記 URL にアクセスすると以下のような画面が表示されます。画面上部に高さ 100px でピンク色の矩形が表示されています:
2020082602


この矩形部分をクリック(タップ)すると、7種類の中からランダムに選ばれた背景色に切り替わります:
2020082603


JavaScript でスタイルシートの内容を動的に書き換える方法のサンプルでした。class を事前に用意して指定し直す、というロジックが使えない場合でも実現可能な方法です。


HTML の <textarea> に行番号を付与する jQuery プラグイン jQuery LinedTextArea を使ってみました。

<textarea> フィールドを(コーディング目的などの)テキストエディタとして使いたい場合など、行番号を付与して使いたいケースがあります。CSS と JavaScript でがんばって作る方法もありますが、jQuery のプラグインを使うことで比較的簡単に実現できそうだったので紹介します。

目的のプラグインは jQuery LinedTextArea です。ざっと探した限りで CDN が存在していなさそうだったので、リンク先から git clone するかダウンロードしておきます(最低限必要になるのは jquery-linedtextarea.cssjquery-linedtextarea.js の2つです)。


準備段階として、これらを jQuery 本体の後にロードしておきます:
<script type="text/javascript" src="//code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="./jquery-linedtextarea.js"></script>
<link href="./jquery-linedtextarea.css" rel="stylesheet"/>

  :

そして目的の <textarea> を指定して、以下のようなコードを実行します:
<textarea class="lined">
あいうえお
かきくけこ
さしすせそ
</textarea>

<script>
$(function(){
  $('.lined').linedtextarea({
    selectedLine: 1
  });
});
</script>

目的の <textarea> に "lined" というクラスを指定した上で、jQuery で $('.lined') に対して linedtextarea() を実行します。上記例では { selectedLine: 1 } というパラメータを付けて実行しています。これにより1行目が選択された状態で表示されます:
2020051200


こんな感じ。

オープンな地図データである OSM(OpenStreetMap) と、オープンな地図操作 JavaScript ライブラリである Leaflet.js を使うことで、ライセンスフリーな地図アプリを作ることができます。このブログでも過去に何度か扱ったことがあります:
http://dotnsf.blog.jp/tag/leaflet

例えば、単に地図を全画面表示するのであればこんな感じのコードを書くと実現できちゃいます:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3c.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta charset="utf8"/>
<meta http-equiv="pragma" content="no-cache"/>
<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>

<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<meta name="apple-mobile-web-app-title" content="OSM Sample"/>

<title>OSM Sample</title>
<script>
</script>
<script>
//. 初期中心位置
var lat = 35.681377778;
var lng = 139.76736389;

//. 初期ズームレベル
var zoomlevel = 9;

var map = null;

$(function(){
  //. 初期位置を中心とした地図を OpenStreetMap データで表示
  map = L.map('demoMap', {}).setView( [ lat, lng ], zoomlevel );
  L.tileLayer(
    'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: 'Map data © <a href="//openstreetmap.org/">OpenStreetMap</a>',
    }
  ).addTo( map );
});
</script>
<style>
html, body {
  width: 100%;
  height: 100%;
  padding: 0px;
  margin: 0px;
}
#demoMap{
  position: fixed;
  width: 100%;
  height: 100%;
}
</style>
</head>
<body>
  <div>
    <div id="demoMap"></div>
  </div>
</body>
</html>

(東京あたりを中心に、ズームレベル9で地図を表示)
2020041501


さて、上図の左上にズームコントロールがあります(上記コードでは特に指定していませんが、明示的に非表示にしない限り、デフォルトでズームコントロールが表示されます)。スマホであればピンチイン・アウトの操作でズームイン・アウトができるのですが、タッチスクリーンに対応していない PC のブラウザで地図を見ている場合は、このコントールを使ってズームイン・アウトを行うことになります。

ただ例えば画面上部に常に表示されるメッセージがある場合など、メッセージとズームコントロールが重なってしまい、操作しにくくなってしまうことがあります:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3c.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta charset="utf8"/>
<meta http-equiv="pragma" content="no-cache"/>
<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>

<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<meta name="apple-mobile-web-app-title" content="OSM Sample"/>

<title>OSM Sample</title>
<script>
</script>
<script>
//. 初期中心位置
var lat = 35.681377778;
var lng = 139.76736389;

//. 初期ズームレベル
var zoomlevel = 9;

var map = null;

$(function(){
  //. 初期位置を中心とした地図を OpenStreetMap データで表示
  map = L.map('demoMap', {}).setView( [ lat, lng ], zoomlevel );
  L.tileLayer(
    'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: 'Map data © <a href="//openstreetmap.org/">OpenStreetMap</a>',
    }
  ).addTo( map );
});
</script>
<style>
html, body {
  width: 100%;
  height: 100%;
  padding: 0px;
  margin: 0px;
}
#demoMap{
  position: fixed;
  width: 100%;
  height: 100%;
}
#message{
  position: absolute;
  top: 10px;
  font-size: 20px;
  color: red;
  font-weight: bold;
}
</style>
</head>
<body>
  <div>
    <div id="demoMap"></div>
    <div id="message">このメッセージが邪魔でズーム制御ができない?</div>
  </div>
</body>
</html>

(画面上のメッセージとズームコントロールが重なってしまう例)
2020041502


この状態をなんとか回避できないか、というのが今回のブログエントリのテーマです。メッセージ側の位置を変えるのは簡単ですが、当然意図があって画面上部にメッセージを表示しているわけですから、できればズームコントロールの位置を変えたい。一方、なまじデフォルトで表示されてしまっているズームコントロールだけにどうすると位置を変更できるのか、がわかりにくいのでした。

正解の一例を先にお見せすると、こんな感じになります:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3c.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta charset="utf8"/>
<meta http-equiv="pragma" content="no-cache"/>
<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>

<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
<meta name="apple-mobile-web-app-title" content="OSM Sample"/>

<title>OSM Sample</title>
<script>
</script>
<script>
//. 初期中心位置
var lat = 35.681377778;
var lng = 139.76736389;

//. 初期ズームレベル
var zoomlevel = 9;

var map = null;

$(function(){
  //. 初期位置を中心とした地図を OpenStreetMap データで表示
  map = L.map('demoMap', { dragging: true, zoomControl: false }).setView( [ lat, lng ], zoomlevel );
  L.control.zoom( { position: 'bottomright' } ).addTo( map );
  L.tileLayer(
    'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: 'Map data © <a href="//openstreetmap.org/">OpenStreetMap</a>',
    }
  ).addTo( map );
});
</script>
<style>
html, body {
  width: 100%;
  height: 100%;
  padding: 0px;
  margin: 0px;
}
#demoMap{
  position: fixed;
  width: 100%;
  height: 100%;
}
#message{
  position: absolute;
  top: 10px;
  font-size: 20px;
  color: red;
  font-weight: bold;
}
</style>
</head>
<body>
  <div>
    <div id="demoMap"></div>
    <div id="message">このメッセージがあってもズーム制御できる</div>
  </div>
</body>
</html>

青字部分が肝になります。初期化時(L.map() 実行時)のオプションで zoomControl: false をつけることでズームコントロールを(一旦)非表示にします。なおもう一つ指定しているオプション dragging: true は地図のスクロール(ドラッグ)ができるようにするオプションで、ここを false にするとスクロールできない地図になります。

このままだとズームコントロールは表示されないのですが、直後の1行でズームコントロールを再び表示しています。そしてこの行では position: 'bottomright' というオプションをつけることで「右下」にズームコントロールを表示させています(デフォルトは 'topleft'):

2020041503

↑こんな感じになって、メッセージと重ならない位置にズームコントロールを移動できました。


(参考)
https://www.wrld3d.com/wrld.js/latest/docs/leaflet/L.Control.zoom/



このページのトップヘ