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

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

タグ:javascript

バックエンドの技術を使わずにウェブのフロントエンドだけで、つまり HTML と JavaScript だけで QR コードリーダーを作ることに挑戦してみました:
smartphone_qr_code


まずは完成品で実際の挙動を確認ください。PC またはスマホで(できればスマホで)こちらの URL にアクセスしてください:
https://dotnsf.github.io/web_qrcode/

2021012501



↑Github Pages で公開しているページなので、フロントエンドだけで作られていることがわかると思います。クロスオリジンな AJAX なども使ってないので、興味ある方はソースコードも確認してください。

「ファイルを選択」ボタンをクリックします。スマホの場合はカメラが起動するので QR コードを撮影してください。また PC の場合は画像選択ボックスが表示されるので、QR コードが撮影された画像を選択してください。

すると画面下部に画像が表示され、同時に画面内の QR コードが(存在していれば)解析され、解析結果の文字列データが表示されます:
2021012502


フロントエンドだけでここまでできるので、後は例えばこの結果が URL 文字列になっていれば、そのアドレスのページを開くこともできますし、テキストデータだけを読み取った上でバックエンドサーバーにデータを送信する、といったサーバーとの連携処理も容易に可能です。


【ソースコードの解説】
ソースコードはこちらで MIT ライセンスで公開しています(GitHub のウェブホスティングである Github Pages を使ってそのままサービスも公開しています):
https://github.com/dotnsf/web_qrcode

特にサービスの肝である index.html ファイルはこちらから参照いただけます。実質的にこの1ファイルだけでカメラ起動から撮影、QR コード解析まで実現しています:
https://github.com/dotnsf/web_qrcode/blob/main/index.html


まずスマホでカメラを起動するボタンの部分は以下の HTML コードで実現しています(PC ブラウザでこのボタンをクリックすると、保存済みの画像ファイルを選択するダイアログが表示されます):
<input type="file" accept="image/*" capture="camera" name="file" id="file-image"/>

type="file" で accept="image/*" capture="camera" 属性のついた <input> タグをクリックすると、カメラ内蔵機種の場合はそのカメラが起動し、撮影した画像を選択したことになります。(PCのなど)カメラ内蔵機種でない場合は普通にファイル選択画面になりますが、accept 属性により画像ファイルしか選択できません(要するにここで QR コードを撮影するか、QR コードが撮影された画像を選択することを想定しています)。

また jQuery を使った JavaScript によって以下の処理が画面ロード直後に実行されています:
var canvas = null;
var ctx = null;
$(function(){
  canvas = document.getElementById( 'mycanvas' );
  ctx = canvas.getContext( '2d' );

  var file_image = document.getElementById( 'file-image' );
  file_image.addEventListener( 'change', selectReadFile, false );
});


HTML 内下部に設置された <canvas> と、そのコンテキストを取得します(この <canvas> に画像が描画されることになりますが、その処理は後述します)。また上述の <input> タグの値が変化した時(=カメラで撮影するか、画像ファイルが選択された時)に selectReadFile() 関数が実行されるよう定義されています:
<div>
  <canvas id="mycanvas"></canvas>
</div>

したがってカメラで撮影するか画像ファイルを選択すると、以下の JavaScript コードが実行されます:
function selectReadFile( e ){
  var file = e.target.files;
  var reader = new FileReader();
  reader.onload = function(){
    readDrawImg( reader, canvas, 0, 0 );
  }
  reader.readAsDataURL( file[0] );
}

HTML5 の Filesystem API を使って撮影された画像ファイルを読み込み、その内容を上述で取得した <canvas> に描画するための関数 readDrawImg() が実行されます。つまり撮影した画像が画面内にも表示されるようになります。

readDrawImg() 関数および、この中で実行される多くの関数の中身は以下のようになっています:
function readDrawImg( reader, canvas, x, y ){
  var img = readImg( reader );
  img.onload = function(){
    var w = img.width;
    var h = img.height;
    printWidthHeight( 'src-width-height', true, w, h );

    // resize
    var resize = resizeWidthHeight( 1024, w, h );
    printWidthHeight( 'dst-width-height', resize.flag, resize.w, resize.h );
    drawImgOnCav( canvas, img, x, y, resize.w, resize.h );
  }
}

function readImg( reader ){
  var result_dataURL = reader.result;
  var img = new Image();
  img.src = result_dataURL;

  return img;
}

function drawImgOnCav( canvas, img, x, y, w, h ){
  canvas.width = w;
  canvas.height = h;
  ctx.drawImage( img, x, y, w, h );

  checkQRCode();
}

function resizeWidthHeight( target_length_px, w0, h0 ){
  var length = Math.max( w0, h0 );
  if( length <= target_length_px ){
    return({
      flag: false,
      w: w0,
      h: h0
    });
  }

  var w1;
  var h1;
  if( w0 >= h0 ){
    w1 = target_length_px;
    h1 = h0 * target_length_px / w0;
  }else{
    w1 = w0 * target_length_px / h0;
    h1 = target_length_px;
  }

  return({
    flag: true,
    w: parseInt( w1 ),
    h: parseInt( h1 )
  });
}

function printWidthHeight( width_height_id, flag, w, h ){
  var wh = document.getElementById( width_height_id );
  wh.innerHTML = '幅: ' + w + ', 高さ: ' + h;
}

まず readImg() 関数の中で Filesystem API を用いて画像ファイルを読み込み、その結果を Image オブジェクトの src 属性に代入します(これで Image オブジェクトを drawImgOnCav() 関数で <canvas> に表示すると読み込んだ画像が表示されます)。またこの結果から画像の幅および高さを取得して、画面内に元画像のサイズとして表示されます。

drawImgOnCav() 関数の最後に checkQRCode() 関数が実行されています。画像の <canvas> への描画終了後にこの関数が実行され、画像に QR コードが写っていたらその内容を解析して、データを取り出します:
function checkQRCode(){
  var imageData = ctx.getImageData( 0, 0, canvas.width, canvas.height );
  var code = jsQR( imageData.data, canvas.width, canvas.height );
  if( code ){
    //console.log( code );
    alert( code.data );
  }else{
    alert( "No QR Code found." );
  }
}

具体的にはコンテキスト変数(ctx)から getImageData() 関数でイメージデータを取得し、その結果を jsQR を使って解析します。ややこしそうな QR コードの解析部分は実はライブラリによって一瞬で実現できてしまっています。jSQR バンザイ!

正しく QR コードが解析できた場合は解析結果の文字列(code.data)を alert() 関数で画面に表示し、解析できなかった場合は "No QR Code found." と表示される、という一連の処理が定義されています。


今回の処理では QR コードが正しく解析でした場合はそのまま表示しているだけですが、これが URL であればロケーションに指定してジャンプしてもよし、テキストなどのデータであればバックエンドの API にポストしてもよし、というわけで取り出したデータの使いみちは alert() の代わりに記述することで、自由に応用できると思います。


というわけで、Filesystem API と jsQR と <input> タグのカメラ拡張を使うことで、ウェブのフロントエンド技術だけで QR コードリーダーが実現できました。


JavaScript である程度プログラムを書いていると1度はハマることになるタイムゾーンの扱いについての自分メモです。
travel_world_jisa



【タイムゾーンとは?】
地球は丸くて、1日は24時間です。ある地点で朝を迎えた時、別のある地点では昼になっていて、更に別のある地点では夜だったりします。同じ瞬間でも時間や日付が異なるわけです。普通はあまり気にしなくていいのですが、「いま何時?」の質問に答えるには「どの地点での話か?」を明確にする必要があります。

この「どの地点での日付の話か?」を地域ごとに定義したものが「タイムゾーン」です。世界標準では GMT と呼ばれる、イギリスのグリニッジ天文台を通過する経線を使います。日本にはタイムゾーンは1つしか存在せず、兵庫県の明石市を通過する東経 135 度の経線のもの(世界標準時よりも9時間早いもの)を「日本標準時」として使います。つまり厳密には日本の東と西では日没時間などは異なりますが、全て「時計は明石市を基準に計測されるものを使う」ことになっています。横に広い国の場合はタイムゾーンが複数存在します。例えばアメリカの場合は「アメリカ東部標準時」、「アメリカ中部標準時」、「アメリカ山地標準時」、「アメリカ太平洋標準時」、「アラスカ標準時」、「ハワイ・アリューシャン標準時」と6つの標準時が存在します(国によってサマータイムとかもありますが、話が更にややこしくなるのでここでは無視します)。例えばあるイベントなども「東部標準時だと午後8時から、太平洋標準時だと午後5時からスタート」のようになります。


【タイムスタンプとは?】
タイムゾーンと少し似た言葉ですが、時間に関係するという以外は全く意味の異なる用語です。タイムスタンプ(UNIX タイムスタンプ)は「ある瞬間を整数で表したもの」です。より正確にはある瞬間が「世界標準時 1970 年 1 月 1 日午前零時から何ミリ秒経過しているか、を整数で表したもの」です。ちなみに日本時間での 2021 年 1 月 22 日午前10時40分40秒ごろに一度計測してみたところ、タイムスタンプの値は 1611279640693 という数値でした。単位はミリ秒なので、ここから1秒経過するごとに 1000 ずつ増えて、1秒さかのぼるごとに 1000 ずつ減っていくことになります。

このタイムスタンプ自体はタイムゾーンの影響を受けません。つまり上述の 1611279640693 という数字は「日本では 2021 年 1 月 22 日午前10時40分40秒(と 0.693 秒)」を示す値であって、別のタイムゾーンだと別の日時(例えばロンドンだと 2021 年 1 月 22 日午前1時40分40秒ごろ)になります。

ソフトウェア・アプリケーションを開発する際に、データの入力日時や更新日時を記録することがありますが、一般的には '2021-01-22 10:40:40' のような日時の文字列では記録せず、タイムスタンプ値を使って記録することが多いです(特にこのシステムが世界中で使われる場合、日時は日本時間として扱われない可能性があるので、後からどのタイムゾーンにでも変換できるようタイムスタンプとして保存するのがよい、とされています)。


【タイムスタンプのタイムゾーン問題】
さて、このようにシステム上において日時はタイムスタンプとして記録されることが多いのですが、一方で画面に表示する際にはタイムスタンプを日時に変換した上で表示されることがほとんどです( 1611279640693 という数字だけを見ても、何年何月何日の何時何分何秒なのか全くわからないので)。

このタイムスタンプから日時文字列への変換を JavaScript で行う場合、何通りかの方法がありますが、例えば以下のような方法が考えられます:
var timestamp = 1611279640693;      //. タイムスタンプ値
var dt = new Date( timestamp );     //. Date オブジェクトに変換
var h = dt.getHours();              //. 時
var m = dt.getMinutes();            //. 分
var s = dt.getSeconds();            //. 秒

var hms = h + ':' + m + ':' + s;    //. 時刻を H:M:S 形式の文字列に変換
   :

タイムスタンプの値を指定して Date オブジェクトを作成し、Date オブジェクトのメソッドを使って時、分、秒の情報をそれぞれ取り出してから H:M:S とコロン区切りの文字列に変換する、という方法です。この例でのタイムスタンプ値は上述のものなので、この結果 hms 変数には 10:40:40 という文字列が代入される、、、 と思われます。

ところが実際に実行してみると、そのようになる場合とならない場合があります。実行環境によって結果が異なってしまうのでした。このならない場合の原因こそがタイムゾーンの問題です。

上述のように 1611279640693 は日本時間で「2021 年 1 月 22 日午前10時40分40秒ごろ」を示すタイムスタンプでした。なので日本時間で動くシステムであれば、この JavaScript の実行結果は "10:40:40" という文字列になるはずです。問題はそのシステムが日本時間で動いているとは限らないことにあります。つまり「日本から使う」想定はできたとしても、そのシステムそのものが「日本にある」とは限らず、日本時間とは別のタイムゾーンが設定されて動いている可能性があるのです。例えばロンドンにあるサーバーで、ロンドンのタイムゾーンが設定されて動いている場合のこの処理結果は "01:40:40" と表示されてしまうことになります(ロンドンタイムゾーンの時間になってしまうので)。同じ日であればともかく、条件によっては別の日に入力されたデータとして扱われる可能性もあります。特に昨今はクラウドサーバーを利用する機会が多く、そのサーバーは日本にはないどころか、「サーバーインスタンスがどこで動いているのかよく分からない」状態で使っていることもあるでしょう。PaaS やコンテナ環境によっては(アプリケーション側からは)サーバーのタイムゾーン設定を変えることができないことも考えられます。


【JavaScript でのタイムゾーン問題】
ここまでは JavaScript に限らず、タイムスタンプを記録している以上はどのプログラミング言語でも起こりうる問題です。JavaScript だと話がもう少しややこしくなります。

アプリケーションが動いているサーバーのタイムゾーン設定が日本とは限らないとわかったとして、ではどのようにして日付時刻を日本時間で表示すればよいでしょうか?

例えばプログラム内でサーバーのタイムゾーン設定を異なる設定(例えば「日本」)に変更することができるのであれば、システム内のタイムスタンプ値を Date オブジェクトに変更し、「日本時間での」日時を取得することができるようになります。また現在のサーバーのタイムゾーン設定を調べることができれば、その値から日本との時差を計算できるので、タイムスタンプ値に時差時間ぶんの数値を加えたり、引いたりすることで、関数実行結果が日本時間のものになるよう調整することもできます。

一般的には前者の方法が使われます。例えばタイムゾーン設定を変更するための setTimezoneOffset() のような関数を使って強制的に日本時間で計算できるようにできればいいのです。が、JavaScript にはこのような関数が用意されていません。つまり JavaScript では前者の方法は使えないのです。

したがって JavaScript では後者の方法を使う必要があります。JavaScript には setTimezoneOffset() 関数は存在していないのですが、設定値を取得するための getTimezoneOffset() 関数は存在しています。例えばこのような感じで使います:
var timestamp = 1611279640693;      //. タイムスタンプ値
var dt = new Date();                //. Date オブジェクトを作成

var tz = dt.getTimezoneOffset();    //. サーバーで設定されているタイムゾーンの世界標準時からの時差(分)
tz = ( tz + 540 ) * 60 * 1000;      //. 日本時間との時差(9時間=540分)を計算し、ミリ秒単位に変換

dt = new Date( timestamp + tz );    //. 時差を調整した上でタイムスタンプ値を Date オブジェクトに変換

var h = dt.getHours();              //. 時
var m = dt.getMinutes();            //. 分
var s = dt.getSeconds();            //. 秒

var hms = h + ':' + m + ':' + s;    //. 時刻を H:M:S 形式の文字列に変換
   :


これでサーバーがどこでどのようなタイムゾーン設定で動いていても、常に日本時間での文字列に変換することができるようになります。


もちろん、このような不便で面倒な方法を使わなければならないというわけではなく、moment などの外部ライブラリを使うことでもう少し楽に扱うことができるようになります。

とはいえ、この事情を理解してないと原因調査も回避するのも難しいと思ってます。というわけでの自分メモでした。


2021 年、あけましておめでとうございます。昨年は人の集まりを自粛する機会が多く、不便に感じることも多くありましたが、一方で「オンライン」や「オフライン」であることを強く意識しながら自分のエンジニアとしてのスキルアップを考える機会も多くありました。今年は考えたり勉強したりしたことを色々な形でアウトプットできるよう心がけていこうと思っています。本年もよろしくおねがいします。


さて、そんな新年最初のブログエントリですが、簡易的な HTML プレビューワーを作ってみました。自分はウェブ関連の技術を紹介する機会が公私で多くあるのですが、そこで本当に紹介したい内容を話す際に HTML や CSS, JavaScript といったフロントエンドの基礎技術は理解しているという前提で紹介することがほとんどです。でも実際にはこの前提を満たしていない人に紹介するケースもあります。

考えようによっては「これから色んなことを勉強していきたい」という強い意志を持った人であって、そういう人が自分の話に興味を持ってもらえることは光栄でもあるのですが、とはいえこれらの基礎技術を理解できていない人にフロントエンドのコードを見せても(しかもここは理解している前提なので、詳しい解説を加えるのは別の部分)おそらくちゃんとは理解できないと思っています。願わくば、そのまま基礎技術にも興味を持ち続けて、自分で勉強も続けていただくことで、いつか理解の点と点がつながる日がやってくることが期待できるとも言えます。が、やはり前提を理解している人向けの説明では本来の難易度以上に難しく聞こえてしまうのか、説明している私からも手応えを感じにくい結果となってしまうことがほとんどです。

で、そういった人向けに、ある程度はその場でも(必要以上のツールのインストールなどをしなくても)HTML などの最低限のフロントエンド基礎をオンラインで独習したり、ある程度理解している人であればライブデモのようなことができるようなものを作ってみました。必要なものはオンラインのネット環境とブラウザだけで、オンラインミーティングとの併用でハンズオンを行う場合の相性がいいと思っています。ただテキスト編集の要素もあるツールなので、UI はスマホやレスポンシブではなく PC からの利用を想定しています(タブレットであればまあなんとか・・かも)。


【サービスの URL】
このツールは GitHub Pages を使って公開しています。URL はこちらです:
https://dotnsf.github.io/jch/


PC のブラウザからアクセスすると下のような4つの列からなる画面が表示されます:
2021010501


画面内の4つの列は左から JavaScript, CSS(スタイルシート), HTML, そしてこれらをまとめたプレビューです。左3つが編集エリアになっていて、その編集内容に応じて右のプレビュー結果が変わるようになっています。JavaScript や CSS に外部ライブラリを追加することはできませんが、jQuery だけは初めから使えるようにロード済みです。

初期状態はそれぞれ以下のようになっています:
JavaScript
2021010502

jquery.js をロードしています。そして $(function(){ と }); の間がテキストエリアになっていて JavaScript を記述できるようになっています(初期状態では空です)。ロード済みなので jQuery のセレクタを使った記述も可能です。

変更を加えてから "JavaScript" ボタンをクリックすると、変更内容がプレビューに反映されます。


CSS
2021010503

<style> と </style> の間がテキストエリアになっていて、スタイルシートの指定を自由に記述することができるようになっています(初期状態では空です)。

変更を加えてから "HTML" ボタンをクリックすると、変更内容がプレビューに反映されます。


HTML
2021010504

ベースとなる HTML が記述されています。初期状態では <h2> の見出しが1つと、<div> に囲われたリスト(<ul>~</ul>)が1つ記述されています。

変更を加えてから "HTML" ボタンをクリックすると、変更内容がプレビューに反映されます。


プレビュー結果
2021010505

上述の JavaScript, CSS, HTML の内容をすべて反映した結果がここに表示されます。初期状態では JavaScript と CSS が実質的に空なので、HTML に記述された内容(<h2> の見出しと <ul> のリスト)がそのまま表示されています。

ではこの初期状態を改良すべく、少しずつ編集してみましょう。まずは HTML に以下の赤字の行を加えてみます:
<h2> 50音 </h2>
<div id="xxx">
<ul>
<li id="li1" class="li">あいうえお</li>
<li id="li2" class="li">かきくけこ</li>
<li id="li3" class="li">さしすせそ</li>
<li id="li4" class="li">たちつてと</li>
<li id="li5" class="li">なにぬねの</li>
<li id="li6" class="li">はひふへほ</li>
<li id="li7" class="li">まみむめも</li>
</ul>
</div>

そして上部の "HTML" と書かれたボタンをクリックします。すると追加した2行のぶんが反映されて、プレビュー結果は以下のように変わります:
2021010506


次は CSS を追加してみます。初期状態では空だった <style> と </style> の間のスタイルシート編集部分に以下を追加します:
h2{
  font-size: 50px;
  color: 'blue';
}
#li3{
  color: '#f0f';
}

見出しである <h2> タグのフォントサイズ(50px)と色(青)を指定し、id="li3" の要素(さしすせそ)のみ紫色になるよう指定しています。そして最後に上部の "CSS" ボタンをクリックして、指定通りのプレビュー結果になることを確認します(下図では HTML 列は表示していません):
2021010507


続けて JavaScript も編集してみましょう。こちらも空だったテキストエリア内に以下を追加します:
$('#xxx').css( 'background', 'yellow' );
$('#li4').css( 'color', 'green' );

リストをラップしている <div id="xxx"> タグの背景色(黃)を指定し、id="li4" の要素(たちつてと)のみ緑色になるよう指定しています。そして最後に上部の "JavaScript" ボタンをクリックして、指定通りのプレビュー結果になることを確認します(下図では CSS 列と HTML 列は表示していません):
2021010508


HTML, CSS, JavaScript すべての変更が反映されたプレビュー結果が表示されることを確認できました。もちろんこのまま HTML, CSS, JavaScript に(順番関係なく)変更を追加していく都度、プレビューを確認することができます:
2021010509


このサービスを使うことで、最小限の準備作業で HTML や CSS, JavaScript といったフロントエンドの基礎技術を実際に編集しては結果を確認しながら学んでいくことができるようになると思っています。


【ツールのソースコード】
このサービスは上述のように GitHub Pages を使って公開していますが、実はページそのものもフロントエンドだけで作られています(アイコン画像などは別ファイルですが、サーバーサイドのバックエンド技術は使っていません)。要するにこのサービスも1枚の HTML とその中に記述された CSS, JavaScript だけで実現しています。

このサービスのソースコードも公開しているので、興味ある方は参照ください:
https://github.com/dotnsf/jch


特に該当 HTML ページのソースコードはこちらから参照できます:
https://github.com/dotnsf/jch/blob/main/index.html

ウェブアプリケーションで 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' );
}



このページのトップヘ