20210801のJavaScriptに関する記事は20件です。

p5.js と jsQR で簡素な QRコードのデコード処理:全体で40行ほど(試行錯誤の過程の情報も記載)

以下の記事の続きのような内容です。 ●JavaScript で QRコードのデコード:jsQR で簡素な処理(日本語の扱いでハマった部分についても記載) - Qiita  https://qiita.com/youtoy/items/d94a5bf835d3f4007c81 上記の記事では JavaScript で書かれた jsQR の公式デモを流用したのですが、この記事では p5.js を使って QRコード のデコードを行います。 jsQR と p5.js を組み合わせた話は、検索していたら以下の記事が見つかったのですが、自分の環境で動かなかったので、いろいろ試行錯誤してみたというのがこの記事の内容です。 ●ブラウザのみでQRコードをARマーカーにしてみた  https://zenn.dev/tkyko13/articles/b1dd3060488be6 完成版の内容 最初に、今回の内容の最終的な実行結果と、今回の完成版のソースコードを掲載してみます。 最終的な実行結果 以下が実行結果です。 QRコードには「test」という文字列を埋め込んでおり、カメラ映像の下側の領域に、その内容が出力できているのが確認できます。 jsQR + p5.js の組み合わせ、ブラウザの開発者ツールを使って取得できる要素・変数の中身を確認しつつコードを書いていったら、動くようになった!認識結果は、Teachable Machine の p5.js 公式サンプルのやり方をまねて、カメラ映像の下に出すようにしてみた! https://t.co/eSU3CdcDDK pic.twitter.com/BlftY956oC— you (@youtoy) August 1, 2021 【追記】 ソースコード:修正版 記事を公開した後にソースコードを簡素化できたので、それを修正後のバージョンとして掲載します。 <html> <head> <meta charset="UTF-8"> <title>jsQR と p5.js</title> <script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.js"></script> <script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script> </head> <body> <h1>jsQR と p5.js</h1> <script> let capture; function setup() { pixelDensity(1); createCanvas(640, 500); // 下にテキスト表示用の領域を作る(ビデオの高さ+20) capture = createCapture(VIDEO); capture.size(640, 480); capture.hide(); } function draw() { background(0); image(capture, 0, 0); capture.loadPixels(); const code = jsQR(capture.pixels, capture.width, capture.height, { inversionAttempts: "dontInvert" }); fill(255); textSize(16); textAlign(CENTER); if (code) { text(`QRコードのデータ: ${code.data}`, width / 2, height - 4); } else { text(`(QRコードが見つかりません)`, width / 2, height - 4); } } </script> </body> </html> 以下で、ソースコードの内容について補足していきます。 ライブラリ ライブラリは現時点で最新のバージョンのものを、CDN からロードしてます。 <script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.js"></script> <script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script> セットアップでのカメラ周りの処理: 修正版 カメラを使う処理は、p5.js公式の Video Capture のサンプルをほぼそのまま利用する形になりました。 let capture; function setup() { pixelDensity(1); createCanvas(640, 500); // 下にテキスト表示用の領域を作る(ビデオの高さ+20) capture = createCapture(VIDEO); capture.size(640, 480); capture.hide(); } createCanvas の指定サイズの高さと capture.size の高さが少し違うのは、この後に出てくる結果表示に利用する領域を確保するためです。 カメラ画像の表示と jsQR に渡すピクセルデータの処理 draw() の中では、ページ内にカメラ画像を表示させつつ、jsQR での処理を行うための画像の受け渡しを行います。 jsQR() には「0 から 255 の間の整数データを RGBA の順で収めた一次元配列と幅・高さの値を渡せば良いのですが、「loadPixels()」・「pixels」を用いることで簡単に実行できることが分かりました。 function draw() { ... image(capture, 0, 0); capture.loadPixels(); const code = jsQR(capture.pixels, capture.width, capture.height, { inversionAttempts: "dontInvert" }); ... } なお、このやり方は、以下のソースコードを探し当てて知りました。  ●p5.js Web Editor | capture pixels   https://editor.p5js.org/lmccart/sketches/xmPJXc81u また、jsQR の処理でオプションとして { inversionAttempts: "dontInvert" } を指定しています。 この指定を行ったのは、以下の公式の説明で「後方互換性のためにデフォルトを attemptBoth にしているものの、パフォーマンスの面から dontInvert を指定するのが良い(将来的には dontInvert をデフォルトにする)」というようなことが書いてあったためでした。また、1つ前の記事で参照した公式のデモも、こちらを指定していたようでした。 結果の表示 結果の表示は、ページ上で行います。 この部分は Teachable Machine の p5.js版公式サンプルのやり方をベースに作りました。 function draw() { background(0); ... fill(255); textSize(16); textAlign(CENTER); if (code) { text(`QRコードのデータ: ${code.data}`, width / 2, height - 4); } else { text(`(QRコードが見つかりません)`, width / 2, height - 4); } } p5.js で最初に作った Canvas の高さをカメラ画像の高さよりも大きめにしておき、その余分な部分(※ 下側にできるもの)の背景を黒く塗っておいて、白色のテキストを上に表示する、というやり方です。 まとめ jsQR と p5.js を組み合わせた JavaScript による QRコードのデコードについて、検索して見つけた記事のやり方が自分の環境でうまくいかなかったため、試行錯誤して動くようなやり方を見つけました。 ソースコードはできるだけシンプルになるようにしてみたので、HTMLの部分を含めて全体で 40行ほどのサイズ感になりました。 【追記】 スマホでの動作・長時間の利用について ここで書いた p5.js版はスマホでの動作確認が十分に行えておらず、また長時間の動作検証もできていないため、それらについて別途対応が必要な事項が出てくるかもしれません。 また、スマホで動かした時に外側のカメラを利用する設定が必要になります。 以下の部分に該当するものですが、まだ今は検証中という状況です。 ●capture = createCapture(VIDEO); Can we reference a different Camera on phone? · Issue #1496 · processing/p5.js  https://github.com/processing/p5.js/issues/1496 【追記】 Teachable Machine での画像分類との同時並行処理 その後、Teachable Machine の画像分類(p5.js版)と同時並行で処理できることが確認できました。 (その内容は、別の記事として書こうと思います) 先ほどの #p5js を使ったカメラから映像内の QRコードのデコード( #jsQR を利用)は途中段階で、その後やりたかった事があり、その話について。やりたかった内容は、上記の QRコードのデコード処理と #TeachableMachine による画像分類(p5.js版)の同時並行での処理で、無事に動いた!! pic.twitter.com/U6EO2J2VHp— you (@youtoy) August 1, 2021 記事公開時点で書いていた内容(古い情報) ※以下は記事公開時点で掲載していたものですが、上記の簡素化できたバージョンができたので不要になりました(記録として残す意味で、修正前の旧バージョンとして掲載しておきます) ソースコード:修正前の旧バージョン JavaScript のファイルは分けず、HTMLファイル 1つに詰め込む感じの実装にしました。 また、デバッグで使った処理はコメントアウトして残してます。 <html> <head> <meta charset="UTF-8"> <title>jsQR と p5.js</title> <script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.js"></script> <script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script> </head> <body> <h1>jsQR と p5.js</h1> <script> let canvas, capture, context; function setup() { canvas = createCanvas(640, 500); capture = createCapture(VIDEO); capture.size(640, 480); capture.hide(); console.log(`capture ${capture.width}, ${capture.height}`) // 640, 480 console.log(`context ${canvas.elt.width}, ${canvas.elt.height}`) // 1280, 1000 context = canvas.elt.getContext('2d'); // console.log(context); } function draw() { background(0); image(capture, 0, 0); const imageData = context.getImageData(0, 0, canvas.elt.height, canvas.elt.height); const code = jsQR(imageData.data, imageData.width, imageData.height); // console.log(code); if (code) { fill(255); textSize(16); textAlign(CENTER); text(code.data, width / 2, height - 4); } } </script> </body> </html> 変数とセットアップ関数のカメラ周りの処理 カメラを使う処理は、まず念のために p5.js公式の Video Capture のサンプルを動かしてみた上で、そこから手を加えていくやり方にしました。 やってみると、カメラ利用のまわりは冒頭で書いていた Zenn の記事と似たような実装で問題なく動きました。 let canvas, capture, context; function setup() { canvas = createCanvas(640, 500); capture = createCapture(VIDEO); capture.size(640, 480); capture.hide(); ... } createCanvas の指定サイズの高さと capture.size の高さが少し違うのは、この後に出てくる結果表示に利用する領域を確保するためです。 画像描画・取得を行う部分のサイズの確認 ここで、いったん画像描画・取得を行う部分のサイズの確認を行ってみました。 具体的には capture と、p5.js で生成された canvas要素のサイズを確認しました。 p5.js の Canvas生成時の処理で canvas = createCanvas(640, 500); と変数 canvas を使った処理を書いていたので、これを使って canvas要素 を取り出します。 canvas.elt として width・height の値を取り出しています。 その結果、冒頭で指定した「幅 640, 高さ 500」という値の 2倍の値が取得される形となりました。 console.log(`capture ${capture.width}, ${capture.height}`) console.log(`context ${canvas.elt.width}, ${canvas.elt.height}`) 念のため、Chrome の開発者ツールでそのあたりを確認してみると、以下のようになっていました。 要素の幅・高さは「1280, 1000」となっていて、style属性が「640, 500」となっているようでした。 【追記】 上記に関わる内容(pixelDensity) 要素の幅・高さは「1280, 1000」と倍の値になっていた件は、 pixelDensity(1); という指定をすると「640, 500」という値になりました(以下、pixelDensity に関連した記事)。 ●reference | pixels  https://p5js.org/reference/#/p5/pixels ●p5.jsでsave()した時に2倍の大きさの画像が出力されるのを回避する : だらっと学習帳  http://blog.livedoor.jp/reona396/archives/55827221.html ●p5.jsでpixel操作をするとなんか結果が小さくなる - Qiita  https://qiita.com/airtoxin/items/929eb4db382c16d5e18b p5.js の Canvas から画像取得・デコード等の処理 セットアップの部分で、後から画像取得を行えるよう canvas.elt.getContext('2d') という形で、2D レンダリングコンテキストを取得しておきます。 function setup() { ... context = canvas.elt.getContext('2d'); } その後の draw() の中で、Canvas ににカメラ画像をはりつけつつ、Canvas上の画像データを取得します。 その部分は、 getImageData() を用い、サイズを指定する部分は canvas要素の実サイズ(「canvas.elt.height, canvas.elt.height」で値は「1280, 1000」)を指定しています。 function draw() { ... image(capture, 0, 0); const imageData = context.getImageData(0, 0, canvas.elt.height, canvas.elt.height); const code = jsQR(imageData.data, imageData.width, imageData.height); ... } あとは、ImageData オブジェクトの data(0 から 255 の間の整数データを RGBA の順で収めた一次元配列を表す Uint8ClampedArray)を取り出して、 jsQR() に幅・高さの値とともに渡します。 このあたりは、前回の記事で JavaScript で行っているものと同じ流れです。 結果の表示 結果の表示は、ページ上で行います。 この部分は Teachable Machine の p5.js版公式サンプルのやり方をまねています。 function draw() { background(0); ... if (code) { fill(255); textSize(16); textAlign(CENTER); text(code.data, width / 2, height - 4); } } p5.js で最初に作った Canvas の高さをカメラ画像の高さよりも大きめにしておき、その余分な部分(※ 下側にできるもの)の背景を黒く塗っておいて、白色のテキストを上に表示する、というやり方です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HISE HiseScript

DTMプログラミング言語探訪 HISE HiseScript 概要 オープンソースのサンプラー/シンセサイザーソフトHISEにはHiseScriptというJavaScriptをベースにした拡張用の独自言語が用意されています。 HISEはモジュールをツリー上に積み重ねて楽器を構成します。スクリプトはそのモジュールごとに作成することができるため、小さな機能単位でモジュール化されたロジックを書くことができます。 用途 NoteOnなどのイベントに対応してサウンドエンジンのパラメータをカスタマイズしたり、タイマーイベントで音を鳴らしたりするのが典型的な使い方ですが、Webアクセスをはじめ非常に多くのAPIが用意されているため、可能性としてはそれだけにとどまりません。 Script FXは音声信号データを直接操作することが可能で、いわゆる信号処理を記述することができます。 GUIの作成やカスタマイズ、コールバックロジックなどもスクリプトで書くことができます。またGUIの作成はWYSIWYGエディタからおこなうこともできます。 キースイッチなどによるアーティキュレーション切り替え 乱数やラウンドロビンによるヒューマナイズ アルペジエーター GUI 信号処理 言語仕様 JavaScriptをベースにカスタマイズした独自言語 イベントコールバック サウンドエンジンAPI ファイルAPI WebアクセスAPI GUI API JavaScriptをベースに、newオペレータを廃止、namespace、inline関数、reg変数の追加など、動的なメモリ確保を最小限に抑え、より安全で高速に実行できる言語にカスタマイズされた独自言語です。 公式リファレンス GUI仕様 スライダー(ノブ)、ボタン、コンボボックス、テーブル、波形表示などが用意されています。 パーツを自作画像に置き換えられるため、独自デザインのGUIを作ることができます。 UI全体の背景やロゴなどの画像を読み込んで表示するだけでなく、パス指定のベクタグラフィックスなど自由な描画ができます。 フローティングタイルを使用してWebページのように拡大縮小に対応した自動レイアウトも可能です。 プログラム例 ノートナンバー表示 押された鍵盤のノートナンバーを表示します。 ConsoleのCは大文字であることに注意してください。 interface.onNoteOn function onNoteOn() { Console.print("note = " + Message.getNoteNumber()); } MIDIチャンネルマッパー HiseScriptはMIDIメッセージの操作も可能です。 ただし他のプラグインにMIDIメッセージを送ることはできません。 onInit const var map = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; map[1] = 5; // map 1ch -> 5ch map[2] = 3; // map 2ch -> 3ch onNoteOn functionon NoteOn() { Message.setChannel(map[Message.getChannel()]); } onNoteOff function onNoteOff() { Message.setChannel(map[Message.getChannel()]); } onController function onController() { Message.setChannel(map[Message.getChannel()]); } エフェクター さらに、限定的ですが信号処理のスクリプトを書くこともできます。 以下はScript FXを使用したFuzzエフェクターの例です。ノブで歪みの量を調整します。 onInit reg dataL; reg dataR; reg val; reg drive; const var knbFuzz = Content.addKnob("Fuzz", 50, 0); Content.setPropertiesFromJSON("Fuzz", { "text": "Fuzz Drive", "defaultValue": 0.5 }); processBlock function processBlock(channels) { drive = knbFuzz.getValue(); dataL = channels[0]; dataR = channels[1]; for(i = 0; i < dataL.length; i++) { val = dataL[i]; val *= 20.0 * drive; if (val > 1.0) val = 1.0; else if (val < -1.0) val = -1.0; val /= 10.0; dataL[i] = val; } dataL >> dataR; } 実行方法 トップレベルのスクリプト Master ChainモジュールのMIDIタブをクリック MIDI Processorの左端にある+をクリックしてScript Processorを選択 onInit、onNoteOnなどコールバックごとにタブがあるので選択して内蔵エディタに入力する 右下のCompileボタンを押すとスクリプトが適用される トップレベルのGUIは家アイコン(Frontend Panels)をクリックすると表示されます 子モジュールのスクリプト 次に子モジュールのスクリプトの例です Master Chainから例としてSine Wave Generatorを子モジュールとして追加します 今回はFXを作成します Sine Wave GeneratorのFXタブをクリックする FXの左端にある+をクリックしてScript FXを選択する FXの場合、コールバックがMIDIと異なります スクリプトを入力してCompileボタンで適用する 子モジュールのGUIはInterfaceタブを選択すると表示されます 感想 オープンソースながらHISE自体非常に安定しており、機能も豊富で、想像以上に完成度の高いソフトウェアという印象でした。 内蔵エディタも、シンタックスハイライトやショートカット、アンドゥなど用意されていて普通に使いやすいものになっています。 スクリプトは、商用ソフトウェアに比較しても圧倒的にAPIが充実していて多様な機能を実装することができます。また、JavaScriptそのままではなく、安全性と高速性を高めたカスタム言語であるところにもセンスを感じます。 さらに、今回は触れていませんがC++インタフェースやScriptnode Expression Language(SNEX)と呼ばれる高速な言語なども用意されています。もちろんオープンソースなのでソースコードを追加・改変することでHISE自体を変更することも可能です。ただしライセンスはGPL v3なのでその点は注意してください。 ただ、現状はサンプルスクリプトが非常に少なく、APIドキュメントだけ見てもわからないことも多い状況です。また、実用的なライブラリもほぼない状態で、このあたりが充実してくるとHISEのポテンシャルがもっと広く伝わるのではないかと思います。 DTMプログラミング言語探訪
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HISE HiseScript - DTMプログラミング言語探訪

DTMプログラミング言語探訪 HISE HiseScript 概要 オープンソースのサンプラー/シンセサイザーソフトHISEにはHiseScriptというJavaScriptをベースにした独自言語が用意されています。 HISEはモジュールをツリー状に積み重ねて楽器を構成します。スクリプトはそのモジュールごとに作成することができるため、小さな機能単位でモジュール化されたロジックを書くことができます。 用途 NoteOnなどのイベントに対応してサウンドエンジンのパラメータをカスタマイズしたり、タイマーイベントで音を鳴らしたりするのが典型的な使い方ですが、Webアクセスをはじめ非常に多くのAPIが用意されているため、可能性としてはそれだけにとどまりません。 Script FXは音声信号データを直接操作することが可能で、いわゆる信号処理を記述することができます。 GUIの作成やカスタマイズ、コールバックロジックなどもスクリプトで書くことができます。またGUIの作成はWYSIWYGエディタからおこなうこともできます。 キースイッチなどによるアーティキュレーション切り替え 乱数やラウンドロビンによるヒューマナイズ アルペジエーター GUI 信号処理 言語仕様 JavaScriptをベースにカスタマイズした独自言語 イベントコールバック サウンドエンジンAPI ファイルAPI WebアクセスAPI GUI API JavaScriptをベースに、newオペレータを廃止、namespace、inline関数、reg変数の追加など、動的なメモリ確保を最小限に抑え、より安全で高速に実行できる言語にカスタマイズされた独自言語です。 公式リファレンス GUI仕様 スライダー(ノブ)、ボタン、コンボボックス、テーブル、波形表示などが用意されています。 パーツを自作画像に置き換えられるため、独自デザインのGUIを作ることができます。 UI全体の背景やロゴなどの画像を読み込んで表示するだけでなく、パス指定のベクタグラフィックスなど自由な描画ができます。 フローティングタイルを使用してWebページのように拡大縮小に対応した自動レイアウトも可能です。 プログラム例 ノートナンバー表示 押された鍵盤のノートナンバーを表示します。 ConsoleのCは大文字であることに注意してください。 interface.onNoteOn function onNoteOn() { Console.print("note = " + Message.getNoteNumber()); } MIDIチャンネルマッパー HiseScriptはMIDIメッセージの操作も可能です。 ただし他のプラグインにMIDIメッセージを送ることはできないようなので、HISEに複数の発音モジュールをロードしてマルチティンバー楽器として使用する場合に利用できるスクリプトです。 onInit const var map = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; map[1] = 5; // map 1ch -> 5ch map[2] = 3; // map 2ch -> 3ch onNoteOn functionon NoteOn() { Message.setChannel(map[Message.getChannel()]); } onNoteOff function onNoteOff() { Message.setChannel(map[Message.getChannel()]); } onController function onController() { Message.setChannel(map[Message.getChannel()]); } エフェクター さらに、限定的ですが信号処理のスクリプトを書くこともできます。 以下はScript FXを使用したFuzzエフェクターの例です。ノブで歪みの量を調整します。 onInit reg dataL; reg dataR; reg val; reg drive; const var knbFuzz = Content.addKnob("Fuzz", 50, 0); Content.setPropertiesFromJSON("Fuzz", { "text": "Fuzz Drive", "defaultValue": 0.5 }); processBlock function processBlock(channels) { drive = knbFuzz.getValue(); dataL = channels[0]; dataR = channels[1]; for(i = 0; i < dataL.length; i++) { val = dataL[i]; val *= 20.0 * drive; if (val > 1.0) val = 1.0; else if (val < -1.0) val = -1.0; val /= 10.0; dataL[i] = val; } dataL >> dataR; } 実行方法 トップレベルのスクリプト Master ChainモジュールのMIDIタブをクリック MIDI Processorの左端にある+をクリックしてScript Processorを選択 onInit、onNoteOnなどコールバックごとにタブがあるので選択して内蔵エディタに入力する 右下のCompileボタンを押すとスクリプトが適用される トップレベルのGUIは家アイコン(Frontend Panels)をクリックすると表示されます 子モジュールのスクリプト 次に子モジュールのスクリプトの例です Master Chainから例としてSine Wave Generatorを子モジュールとして追加します 今回はFXを作成します Sine Wave GeneratorのFXタブをクリックする FXの左端にある+をクリックしてScript FXを選択する FXの場合、コールバックがMIDIと異なります スクリプトを入力してCompileボタンで適用する 子モジュールのGUIはInterfaceタブを選択すると表示されます 感想 オープンソースながらHISE自体非常に安定しており、機能も豊富で、想像以上に完成度の高いソフトウェアという印象でした。 内蔵エディタも、シンタックスハイライトやショートカット、アンドゥなど用意されていて普通に使いやすいものになっています。 スクリプトは、商用ソフトウェアに比較しても圧倒的にAPIが充実していて多様な機能を実装することができます。また、JavaScriptそのままではなく、安全性と高速性を高めたカスタム言語であるところにもセンスを感じます。 さらに、今回は触れていませんがC++インタフェースやScriptnode Expression Language(SNEX)と呼ばれる高速な言語なども用意されています。もちろんオープンソースなのでソースコードを追加・改変することでHISE自体を変更することも可能です。ただしライセンスはGPL v3なのでその点は注意してください。 ただ、現状はサンプルスクリプトが非常に少なく、APIドキュメントだけ見てもわからないことも多い状況です。また、実用的なライブラリもほぼない状態で、このあたりが充実してくるとHISEのポテンシャルがもっと広く伝わるのではないかと思います。 DTMプログラミング言語探訪
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JS】チェーンメソッドのTips

チェーンメソッド's Tips いくつもメソッドを連続で実行するためには、一工夫するだけで実行できます。 それは、メソッド末尾にreturn this;と返すだけです。 以下の例では、最後にthisを返すことで、Personオブジェクトを返すことになるので、チェーンにしてもエラーが発生せずに実行出来ます。 class Person { constructor(name, age) { this.name = name; this.age = age; } hello(person) { console.log(`hello ${person.name}`); return this; } bye(person) { console.log(`Goodbye, ${person.name}.`); return this; } } // インスタンスを生成 const bob = new Person('Bob', 23); // チェーンメソッドで実行する bob.hello(bob).bye(bob); // => 'hello bob' 'Goodbye, bob'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptでクラスを使ってみる。

はじめに JSでもクラスが使えるようになってとても便利だなと感じていますが、まだES5の名残りがあるコードはたくさんあるので、使ったことがない人もいるかと思います。これからは主流の書き方になると思うので、確実に押さえておきましょう! これまでの使い方を復習した上で、クラスの使い方を見ていこうと思います。 コンストラクタとは コンストラクタとは、インスタンス(実体)を作成する関数のことです。 function Person(name, age) { // プロパティを設定 this.name = name; this.age = age; // メソッドを設定 this.setName = function(name) { this.name = name; } } // コンストラクタからインスタンスを作成 let person1 = new Person('tanaka', 22); プロトタイプについて メソッド定義の方法のことで、インスタンスが生成されるたびにコピーされることはないのでメモリの節約になります。 Person.prototype.setName = function(name) { this.name = name; } クラスの使い方 class Person { // コンストラクタを設定 constructor(name, age) { this.name = name; this.age = age; } // メソッドを設定 hello(someone) { console.log('Hello ' + someone.name, ', I am ' + this.name + '.'); } } let tanaka = new Person('Tanaka', 22); let kato = new Person('Kato', 30); kato.hello(tanaka); // Hello Tanaka , I am Kato. tanaka.hello(kato); // Hello Kato , I am Tanaka. クラスの継承 クラスを継承することで無駄の無いコードを実装出来ます。継承には、extends句を使います。 class Person { // コンストラクタを設定 constructor(name, age) { this.name = name; this.age = age; } } // Personクラスを継承 class ChildPerson extends Person { // コンストラクタを設定 constructor(name, age, person) { super(name, age) // Personクラスのプロパティを継承 this.person = person } } 終わりに 一部のブラウザではまだ機能できない事がありますが、これから調整されて利用できると思うので今から身につけておきましょう! 参考 【JavaScript入門】コンストラクタの使い方 JavaScriptのクラス(class)を理解する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScriptで配列の中身のデータ型を指定する方法

TypeScriptで配列の中身の数やそのデータ型を指定したかったのですが、ググり方がダメなのかわかるまで大変でしたので記事にします 結論 const food: [string, string, string, number] = [ "吉野家", "牛丼", "並盛", 352 ]; 数が多くても少なくてもダメですし、型を間違えればもちろんエラーが出ます。 エラーを出してみる 型を変えてみる const food: [string, string, string, number] = [ "吉野家", "牛丼", "並盛", "352円" ]; // TS2322: Type 'string' is not assignable to type 'number'. 中身を減らしてみる const food: [string, string, string, number] = [ "吉野家", "牛丼", 352 ]; // TS2322: Type '[string, string, string, number]' is not assignable to type '[string, string, number]'.  Source has 3 element(s) but target requires 4. 活用方法 普通の配列指定 const food: Array<any> = [ "吉野家", "牛丼", "並盛", 352 ]; これでも指定できるんですけど混在していて厳密に型指定しておきたい場合は不便ですよね。 例えば以下のようなループで使うことを前提とした配列。 const food: Array<[string, string, string, number]> = [ ["吉野家", "牛丼", "並盛", 352], ["松屋", "牛めし", "並盛", 320], ["すき家", "とろ〜り3種のチーズ牛丼", "大盛", 630], ]; こういう厳密性が大切な配列を定義したいときなんかは非常に頼りがいがあります。 ※2021/08/01時点での牛丼情報なので時期によっては価格が違う可能性があります。 お付き合いいただきありがとうございました! この記事が何かお力になれたらうれしいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者】アニメーションで理解する二分探索木(検索・走査編)

概要 コンピュータサイエンスを学習していると必ず出てくる二分探索木ですが、動きがイメージしにくい内容です。今回JavaScriptで二分探索木の可視化ライブラリーを作ったので、アニメーションを使って二分探索木を解説しました。学習の助けになれば幸いです。 二分探索木 二分木は下図のようにどの親も2つ以下の子を持つデータ構造です。 その二分木の中でも「左の子孫の値 ≤ 親の値 ≤ 右の子孫の値」を満たす二分木を二分探索木と呼びます。下図のような左右の部分木の高さのバランスが取れた構造であれば$O(\log n)$程度で探索できます。 ※注意:例えばすべての子が左にあるような偏った二分探索木の場合は、計算量は$O(n)$です。 まずはその探索を見てみましょう。 探索 配列やリストである値を探索する場合、計算量は$O(n)$です。例えば、0~14の数値データが単方向リンクリストで表される場合、検索に最大15Step必要です。 See the Pen DLL検索 by Nori_CS (@ut3gs) on CodePen. 一方バランスの取れた二分探索木は計算量は$O(\log n)$です。同じデータ数でアニメーションを見てみましょう。 See the Pen 二分探索木検索 by Nori_CS (@ut3gs) on CodePen. 最大でもたった4Stepだけで検索することができました。データ量が増えるほど$O(\log n)$が効果を発揮します。今回はデータ数15個程度でしたが、もし全人類80億人のデータベースから一人を特定するようなとき、$O(n)$だと80億Step必要ですが、$O(\log n)$だと30Stepで検索できます。 走査 木構造の全てのノードにアクセスする一連の処理を走査といいます。走査は深さ優先走査(前順、間順、後順)、幅優先走査があります。 深さ優先走査 前順 先行順や行きがけ順とも呼ばれています。下図のように、親→左の子孫→右の子孫の順にアクセスしていきます。 See the Pen 前順 by Nori_CS (@ut3gs) on CodePen. 間順 通りがけ順ともよばれます。下図のように、左の子孫→親→右の子孫の順にアクセスしていきます。 二分探索木だと昇順になります。 See the Pen 中間順 by Nori_CS (@ut3gs) on CodePen. 後順 後行順や帰りがけ順とも呼ばれています。下図のように、左の子孫→右の子孫→親の順にアクセスしてきます。 See the Pen 後順 by Nori_CS (@ut3gs) on CodePen. 幅優先走査 下図のように、浅いほうから順に左側からアクセスしていきます。 See the Pen 幅優先 by Nori_CS (@ut3gs) on CodePen. まとめ 今回は、検索と走査を見ていきました。本記事でイメージを理解できたら、その他参考書や記事を参考にして実際にコードを書いてみることをお勧めします。 次回は、二分探索木の削除・挿入を扱います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jquery.datatable.jsで重複するセルをハイライト表示する

デモ 前提として jquery.js と jquery.datatable.js を読み込んでおく必要があります See the Pen by qwe001 (@qwe001) on CodePen. 実装 "preDrawCallback": function(settings){ var api = this.api(); var items = api.rows().data(); // 重複セルをチェックする items.map((item, idx, arr)=>{ item.is_duplicate_code = arr.filter(({code}) => code === item.code).length > 1 ? true : false; item.is_duplicate_name = arr.filter(({name}) => name === item.name).length > 1 ? true : false; return item; }); }, データ取得が完了したタイミングで重複チェックを実行。 データに「is_duplicate_{key}」プロパティを埋め込んで、 同じ値がある場合にフラグを立てておきます "rowCallback" : function(row, item){ // 商品コード if(item.code){ // 重複セルをハイライト if(item.is_duplicate_code){ $('td.dt-column-code', row).addClass("same"); } } // 名前 if(item.name){ // 重複セルをハイライト if(item.is_duplicate_name){ $('td.dt-column-name', row).addClass("same"); } } }, 各行ごとの描写処理を行うタイミングでフラグ状態を確認して条件分岐させます。 あとは色を変えるなりお好きに。 もっとスマートな実装をご存知の方がいましたら、 教えて頂けると嬉しいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript で QRコードのデコード:jsQR で簡素な処理(日本語の扱いでハマった部分についても記載)

この記事は JavaScript を用いた QRコードのデコードに関する内容です。 Qiita の記事でも、いくつかこの話題に関する記事がありますが、「シンプルな最低限の実装となるとどうなるか」という話を調べて試してみたくなり、今回の内容を試しました。 ライブラリ(jsQR)を試す Web上の記事を検索してみていると、JavaScript用のライブラリはいくつかあるようですが、よく見かけたものの 1つに jsQR というものがありました。 cozmo/jsQR: A pure javascript QR code reading library. This library takes in raw images and will locate, extract and parse any QR code found within.  https://github.com/cozmo/jsQR 例えば Qiita の記事でいうと、このあたりの記事などです。 便利ページ:JavascriptでQRコードスキャン - Qiita Webの技術だけで作るQRコードリーダー - Qiita 公式デモサイトを使ったお試し 上記のサイトを見るとデモページがあったので、それを開いて PC やスマホで動かしてみました。 ●jsQR Demo  https://cozmo.github.io/jsQR/ 具体的には、PC上で以下のサイトを利用して、適当な文字列を埋め込んだ QRコードを生成し、それを上記のページを開いたスマホのブラウザで読み込ませてみました。 ●QRコード(二次元バーコード)作成【無料】  https://cman.jp/QRcode/ QRコードに埋め込む内容で日本語を入力していた場合、デモサイトでその文字列が表示されたなかったのですが、その話はいったん置いておいて、アルファベット・半角スペースのみで作った文字列で再度試したました。こちらは問題なく成功。 (原因については、「マルチバイトを考慮した処理がされてない?」とか「ライブラリ内でマルチバイトを扱えない?」とか、そのあたりかなとか思いつつ...) 【追記】 これは、QR生成を行うサイト側の文字コードの問題でした...(詳細は後で補足) QRコード処理に関するJavaScriptのライブラリのデモサイト、日本語の文字だとデコード結果が表示されない?(マルチバイトを考慮してない実装なだけかな?)アルファベットと半角スペースで作った文字列は、大丈夫だった。 pic.twitter.com/I0KogL4oi3— you (@youtoy) August 1, 2021 ちなみに、デモサイトのソースコードを表示させてみると、以下のような内容でした。 <html> <head> <meta charset="utf-8"> <title>jsQR Demo</title> <script src="./jsQR.js"></script> <link href="https://fonts.googleapis.com/css?family=Ropa+Sans" rel="stylesheet"> <style> body { font-family: 'Ropa Sans', sans-serif; color: #333; max-width: 640px; margin: 0 auto; position: relative; } #githubLink { position: absolute; right: 0; top: 12px; color: #2D99FF; } h1 { margin: 10px 0; font-size: 40px; } #loadingMessage { text-align: center; padding: 40px; background-color: #eee; } #canvas { width: 100%; } #output { margin-top: 20px; background: #eee; padding: 10px; padding-bottom: 0; } #output div { padding-bottom: 10px; word-wrap: break-word; } #noQRFound { text-align: center; } </style> </head> <body> <h1>jsQR Demo</h1> <a id="githubLink" href="https://github.com/cozmo/jsQR">View documentation on Github</a> <p>Pure JavaScript QR code decoding library.</p> <div id="loadingMessage">? Unable to access video stream (please make sure you have a webcam enabled)</div> <canvas id="canvas" hidden></canvas> <div id="output" hidden> <div id="outputMessage">No QR code detected.</div> <div hidden><b>Data:</b> <span id="outputData"></span></div> </div> <script> var video = document.createElement("video"); var canvasElement = document.getElementById("canvas"); var canvas = canvasElement.getContext("2d"); var loadingMessage = document.getElementById("loadingMessage"); var outputContainer = document.getElementById("output"); var outputMessage = document.getElementById("outputMessage"); var outputData = document.getElementById("outputData"); function drawLine(begin, end, color) { canvas.beginPath(); canvas.moveTo(begin.x, begin.y); canvas.lineTo(end.x, end.y); canvas.lineWidth = 4; canvas.strokeStyle = color; canvas.stroke(); } // Use facingMode: environment to attemt to get the front camera on phones navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then(function(stream) { video.srcObject = stream; video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen video.play(); requestAnimationFrame(tick); }); function tick() { loadingMessage.innerText = "⌛ Loading video..." if (video.readyState === video.HAVE_ENOUGH_DATA) { loadingMessage.hidden = true; canvasElement.hidden = false; outputContainer.hidden = false; canvasElement.height = video.videoHeight; canvasElement.width = video.videoWidth; canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height); var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height); var code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: "dontInvert", }); if (code) { drawLine(code.location.topLeftCorner, code.location.topRightCorner, "#FF3B58"); drawLine(code.location.topRightCorner, code.location.bottomRightCorner, "#FF3B58"); drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, "#FF3B58"); drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, "#FF3B58"); outputMessage.hidden = true; outputData.parentElement.hidden = false; outputData.innerText = code.data; } else { outputMessage.hidden = false; outputData.parentElement.hidden = true; } } requestAnimationFrame(tick); } </script> </body> </html> 上記の内容を HTMLファイルにして、jsQR.js を同じ階層に置けば動きそうです。 「CDN からロードできるかな?」と思って調べてみたら、以下のページにたどり着けたので、こちらを利用してみます。 ●jsqr CDN by jsDelivr - A CDN for npm and GitHub  https://www.jsdelivr.com/package/npm/jsqr デモサイトの内容をローカルのファイルで動かす デモサイトの内容をそのままコピーした後、スクリプトタグの部分を以下の内容にして動作させてみました(上記の CDN から、2021/8/1時点の最新バージョンでミニファイされたファイルをロード)。 <script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script> PC上で上記のファイルを動作させたので、今度はスマホで QRコードを生成し、読み込ませて動作確認をしました。そして問題なく動きました。 公式ライブラリのデモのソースを見てみる 「この後に何から独自の処理を加えたい場合、どこに手を入れれば良さそうか」というのを見てみました。 試しに動かしてみた時の挙動的には、以下のような処理を行っていると想像できました。 カメラから画像を取得 取得したカメラ画像に対して QRコードの検出処理(画像上の位置情報を取得) QRコードのデコードも行う(検出されていた場合) 上記2 の結果が得られていた場合、取得したカメラ画像上に検出領域を示す線を重畳 QRコードのデコード結果をページ上に表示 カメラ画像の次のフレームを取得する(あとは、同様の処理を繰り返す) ここからは、実際にソースコードを見ていって確認してみます。 カメラ画像の取得 上記1 に該当する部分は、以下のようでした。 // Use facingMode: environment to attemt to get the front camera on phones navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then(function(stream) { video.srcObject = stream; video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen video.play(); requestAnimationFrame(tick); }); カメラ画像の取得は、WebRTC等のカメラを利用する処理でおなじみの navigator.mediaDevices.getUserMedia です。 その処理の中で、スマホで動かした時、内向き/外向きのカメラのうち、外向きのカメラを利用する設定をしているようでした。 具体的には facingMode: "environment" を指定している部分です。 MDN の MediaDevices.getUserMedia() のページにも書いてあるとおりです(内向きのカメラを使うときは user を指定)。 video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen の部分は、コメント内や以下のサイトにも書かれた iOS の Safari 向けの処理のようです。 ●javascript - How to create an HTML5 video element in iOS that will stay hidden? - Stack Overflow  https://stackoverflow.com/questions/64415963/how-to-create-an-html5-video-element-in-ios-that-will-stay-hidden なお、MDN のページで playsinline について書かれた部分は以下となっています。 QRコードの検出・デコード QRコードを検出したりデコードしたりする処理は、以下になりそうです。 canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height); var imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height); var code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: "dontInvert", }); カメラ画像をいったん Canvas に書き込み、 canvas.getImageData でそれを取得した後に、 var code = jsQR... という部分でライブラリ内での処理に引き渡しているようです。 また、オプションで {inversionAttempts: "dontInvert"} と指定している部分は、公式のサイトに説明がありました。 このオプション指定について、「後方互換性のためにデフォルトを attemptBoth にしているものの、パフォーマンスの面から dontInvert を指定するのが良い(将来的には dontInvert をデフォルトにする)」といった説明が書いてありました。 QRコードが検出された場合の処理 QRコードに対する処理結果を格納した code について、デモサイトのソースを読み解いて中身を確認する方法もありますが、ここではソースコードにログ出力の処理をつけたして、その出力された中身を見てみます。 Chrome で動作させ、ログを開発者ツールのコンソールに出してみました。 QRコードが検出されなかった場合は、 code が null になるようです。 一方、QRコードが検出がされた場合、検出した位置の情報( code.location の部分)やデコード結果( code.data の部分)が code の中に格納されるようです。 あらためてソースコードのほうも見てみると、そのあたりの情報を使った処理が以下の部分で見つかります。 if (code) { drawLine(code.location.topLeftCorner, code.location.topRightCorner, "#FF3B58"); drawLine(code.location.topRightCorner, code.location.bottomRightCorner, "#FF3B58"); drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, "#FF3B58"); drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, "#FF3B58"); ... outputData.innerText = code.data; } else { ... 日本語の扱い 冒頭で書いた、日本語を QRコードに埋め込んだ場合に code.data の中身がどうなるかをログに出して確認してみました。 ちなみに、この時利用していたQRコード(「日本語」という文字を埋め込んだもの)は、以下の画像の内容でした。 この場合の code.data の中身は「空の文字列」になっているようでした。 公式ページの仕様が書かれていそうな部分(Return value の部分)を見ると、以下のような記載がありました。 上記を見ると、 code.data が空の文字列になっている時も code.binaryData にはデータが入っているように思われます。 そこで、 code.binaryData をログに出力してみて、中身を見てみます。 以下は、「日本語」という文字列を QRコードに埋め込んだ場合と、「test」という文字列を QRコードに埋め込んだ場合の code.binaryData のそれぞれの中身です。 ▼ 「日本語」という文字列を QRコードに埋め込んだ場合 ▼ 「test」という文字列を QRコードに埋め込んだ場合 バイナリデータのサイズ的には、うまくデータが取得できていそうに見えます(「日本語」は 2バイト文字が 3文字分で、test は 1バイトの文字が 4文字分)。 以下のサイトを参考にしつつ、Google Chrome の開発者ツールのコンソールで、デコードの処理を実行していろいろ試してみました。 JavaScript バイナリデータの配列をUTF-8文字列へ変換する - Why it doesn't work? TextDecoder - Web API | MDN TextDecoder.prototype.encoding - Web APIs | MDN その結果、分かったことは QRコードの日本語の文字列が Shift_JIS で、jsQR のデコード側が UTF-8 で文字列変換をしていそうだということでした。 以下に、Google Chrome の開発者ツールのコンソールで、「TextDecoder」を用いたデコードの処理を実行した際の、ソースコードと結果を掲載してみます。 var bytes = [147, 250, 150, 123, 140, 234]; var text_decoder = new TextDecoder('shift-jis'); var str = text_decoder.decode(Uint8Array.from(bytes).buffer); console.log(str); 上記を実行した結果は以下となりました。 QRコード作成サイトについて少し検索してみると、例えば以下のサイトは文字コード指定も行えるようでした。 ●QRコード作成| Softel labs  https://www.softel.co.jp/labs/tools/qr/ 「日本語」という文字列を、UTF-8 で QRコード化してみます。 上記の画像を jsQR の公式デモで読ませた場合は、無事に日本語の文字もページ上の出力に表示されました。 jsQR の公式デモで、日本語の埋め込まれた QRコードを読み込ませる場合、QRコード生成側で UTF-8 を指定しておくと、画面上のデコード結果表示で失敗しなくてすむ。 https://t.co/RCShZLEoqk pic.twitter.com/XdMq83eVJn— you (@youtoy) August 1, 2021 そして、最初に利用していたサイト内で、こんな記載がありました。 最初に利用したサイト、日本語を Shift_JIS で埋め込んでたけど、同じサイト内での説明によるとこういうことらしい。 pic.twitter.com/aYtxX0a3Fo— you (@youtoy) August 1, 2021 jsQR の処理では、この話に関係なく文字列への変換で UTF-8 を指定している実装になってる、という感じなのかな。 おわりに JavaScript による QRコードのデコードについて、jsQR というライブラリを使うことで、簡単に実行できました。 ソースコードは、公式デモの実装がシンプルなもので、それを簡素化するというようなことはする必要がない状態でした。 そして、日本語の取り扱いに関する部分は少しハマったところがありましたが、内部実装についての知見が得られたので良かったです。 余談 かなり環境を選ぶようですが、以下の記事に出ていた Shape Detection API というものが気になったりもしています。 ●続・Webの技術だけで作るQRコードリーダー - Qiita  https://qiita.com/kan_dai/items/3486880236a2fcd9b527 後で上記の記事や、以下の仕様のページなどを見ていければと思っています。 ●Accelerated Shape Detection in Images  https://wicg.github.io/shape-detection-api/ 【追記】 この後にやったこと 2つ p5.js版も完成!(動作確認は PC のみ) その後、p5.js を使って、ソースコードの行数を減らしたバージョンができました。 ●p5.js と jsQR で簡素な QRコードのデコード処理:全体で40行ほど(試行錯誤の過程の情報も記載) - Qiita  https://qiita.com/youtoy/items/d7dd18e7127f23f482dd QRコードのデコードと Teachable Machine での画像分類を同時に動かす その後、Teachable Machine の画像分類(p5.js版)と jsQR による QRコードのデコードを、同時に動かす処理を試してみて、動作させることができました。 先ほどの #p5js を使ったカメラから映像内の QRコードのデコード( #jsQR を利用)は途中段階で、その後やりたかった事があり、その話について。やりたかった内容は、上記の QRコードのデコード処理と #TeachableMachine による画像分類(p5.js版)の同時並行での処理で、無事に動いた!! pic.twitter.com/U6EO2J2VHp— you (@youtoy) August 1, 2021
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

高校生になった気分でピクトグラム作ってみた

オリンピックでのピクトグラムをきっかけに、たくさんの方がピクトグラムのモーションキャプチャを作られました。 私も、モチベーションを上げるには今しかないと思い、Googleの機械学習であるMediaPipeを使ってピクトグラムを作ってみました。多くの方が、ピクトグラムのモーションキャプチャにMediaPipeを使っていらっしゃいますし、私も過去に使ったことがあったので、さくっと作ってみようと思いました。 MediaPipe が、以外にベクトル計算にてまどったので、まとめておきます。 久しぶりに、高校生になった気分でした。 ソースコードもろもろは以下に置いてあります。 poruruba/Pictogram 使ってみたい方は、以下をブラウザから開いてみてください。PCにWebCamがつながっている必要があります。スマホだと縦横比を調整してないです。 すでにたくさんの方がピクトグラムに挑戦されているため、あまり真新しさはありませんが、ベクトル計算の部分がためになったので、投稿しておきました。 こちらがピクトグラムしているときの画面。(ちょっと近すぎ) こちらがスナップショット撮影したときの画像 (2021/8/1 修正) 何を勘違いしたのか、投稿内容の計算式が間違っていたので修正しました。 モーションキャプチャのしくみ モーションキャプチャには、MediaPipeのPoseソリューションを使います。 MediaPipeを使うことで、ブラウザに接続されたWebCamを使って撮影された映像から、以下にある体の一部を検出してくれます。 一番難しい作業のはずが、今回の中では一番簡単な作業でした。。。 ピクトグラムの構成要素 ピクトグラムは、HTMLのCanvasに描画します。 ピクトグラムは、ヒト型にすると以下のようになります。 これを、部品に分けると以下の2つになります。 頭の部分は単に円なので、簡単です。後者は、腕や足の関節も表現できるようになっていて、少し複雑です。 今度は、これを少し分解します。 丸と台形を2つ合わせたようなものに分かれました。これまた後者が少々複雑です。 後者は、2つの端点の円の半径が異なるため、台形のような形になっています。 各頂点をP1からP6とし、P1とP2をMediaPipeで検出した点(Landmark)に割り当てればよいです。なので、P1とP2からそれ以外の場所(X座標、Y座標)がわかれば、塗り潰しできるのですが。。。。 図形を見てみると、P1とP2を境にした2つの台形をつなげたもののようです。なぜ、P3、P5、P4、P6による台形にならないのかは、以下のように表示するとわかります。 わかりやすいように、P1を原点にし、P2がX座標と交わるように場所と角度を変えます。 それでは、この時の、P3の座標を求めてみましょう。 以降は、角度を求める必要がでてきますが、角度を求める代わりに、cosθ、sinθを求めることで、直接角度(ラジアン)を導出する必要がないようにしています。 以降では、座標を以下のように表現します。 P3 = (P3.x, P3.y) または、P3.x、P3.y P3の座標は、以下のように表現されます。 P3.x = r1 * cosα P3.y = r1 * sinα r1とr2は、各端点の円の半径です。r1>r2の前提です。 r1が既知なので、αの角度がわかればよいですね。 先にいっておくと、P1とP2の間の距離が非常に大事です。Dとします D = |P2-P1| = sqrt((P2.x-P1.x)2乗 + (P2.y-P1.y)2乗) 実は、P2を頂点とした三角形も同じ形なので、こっちを使った方がよさそうです。 そうすると、 cosα = (r1 – r2) / D 1 = sinα 2乗 + cosα 2乗 sinα = sqrt(1 – cosα 2乗) となります。三角関数の公式を使っています(懐かしい。。。)。よって、以下のようになります。 P3.x = r1 * cosα = r1 * (r1 – r2) / D P3.y = r1 * sinα = r1 * sqrt(1 – cosα 2乗) = r1 * sqrt(1 – ((r1 – r2) / D)2乗) 次に、P3がわかったので、P4、P5、P6を求めます。ついでに、自明ですが、P1、P2も書いておきます。 P4.x = D + r2/r1 * P3.x P4.y = r2/r1 * P3.y P5.x = P3.x P5.y = -P3.y P6.x = P4.x P6.y = -P4.y P1.x = 0 P1.y = 0 P2.x = D P2.y = 0 できました! 見てわかります通り、P1とP2の間の距離Dさえわかれば、それ以外の座標が決まることがわかります。 それでは、実際にモーションキャプチャでキャプチャされたp1とp2から求めたいのですが、P1が原点に、P2がX軸上に来るように座標をずらしていました。 ですので、先ほど求めたPnの座標を、実際にキャプチャされたp1とp2に合わせて回転と移動する必要があります。 P1からみたP2の角度に合わせて、さきほどのPnの図形を回転させたのち、原点としていたP1を実際のキャプチャされたp1に移動します。 高校時代に、こんな感じの回転行列を使っていましたね。。。。 R(θ).x = x * cosθ – y * sinθ R(θ).y = x * sinθ + y * cosθ 回転の計算に必要なcosθ、sinθは以下で表されます。 cosθ = (p2.x - p1.x) / D sinθ = (p2.y - p1.y) / D 一方の移動量Eは以下で表されます。 E.x = p1.x E.y = p1.y あとはこれを、回転→移動の順に適用すればよいです。 pn = R(θ)Pn + E 結局のところ展開すると以下になります。 pn.x = Pn.x * cosθ- Pn.y * sinθ + E.x pn.y = Pn.x * sinθ+ Pn.y * cosθ + E.y PnというのがさきほどP1を原点としたときの座標で、pnが実際にキャプチャで検出された座標です。 それを実装に起こしたのが以下です。 function calcRange( x1, y1, x2, y2, r1, r2 ){ var D = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); var cosF = (x2 - x1) / D; var sinF = (y2 - y1) / D; var p3x = r1 * (r1 - r2) / D; var p3y = r1 * Math.sqrt(1 - Math.pow((r1 - r2) / D, 2)); var q3 = rotation(p3x, p3y, cosF, sinF); var q3x = q3.x + x1; var q3y = q3.y + y1; var p2x = D; var p2y = 0; var q2 = rotation( p2x, p2y, cosF, sinF ); var q2x = q2.x + x1; var q2y = q2.y + y1; var p4x = D + r2 / r1 * p3x; var p4y = r2 / r1 * p3y; var q4 = rotation(p4x, p4y, cosF, sinF ); var q4x = q4.x + x1; var q4y = q4.y + y1; var p5x = p3x; var p5y = -p3y; var q5 = rotation(p5x, p5y, cosF, sinF); var q5x = q5.x + x1; var q5y = q5.y + y1; var p6x = p4x; var p6y = -p4y; var q6 = rotation(p6x, p6y, cosF, sinF); var q6x = q6.x + x1; var q6y = q6.y + y1; var q1x = x1; var q1y = y1; return { q1x, q1y, q2x, q2y, q3x, q3y, q4x, q4y, q5x, q5y, q6x, q6y }; } function rotation(x, y, cosF, sinF) { return { x: x * cosF - y * sinF, y: x * sinF + y * cosF }; } Canvasへの描画 あとは、モーションキャプチャ完了の都度、以下の関数が呼ばれます。 function onResults(results) { // videoCtx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height); if (!results.poseLandmarks || results.poseLandmarks.length < 31 ) return; // 全体を背景色で塗りつぶし canvasCtx.fillStyle = PICTO_BACKGROUND; canvasCtx.fillRect(0, 0, canvasElement.width, canvasElement.height); canvasCtx.fillStyle = PICTO_FOREGROUND; // 頭部分の円を描画 // var head = dimension([results.poseLandmarks[0], results.poseLandmarks[4], results.poseLandmarks[1]]) * 100; var head = dimension([results.poseLandmarks[11], results.poseLandmarks[12], results.poseLandmarks[23], results.poseLandmarks[24]]) * 10; drawCircle(median([results.poseLandmarks[0], results.poseLandmarks[4], results.poseLandmarks[1]]), head * 35); // 腕の部分を描画 drawParts(results.poseLandmarks[11], results.poseLandmarks[13], head * 23, head * 20); drawParts(results.poseLandmarks[13], results.poseLandmarks[15], head * 20, head * 15); drawParts(results.poseLandmarks[12], results.poseLandmarks[14], head * 23, head * 20); drawParts(results.poseLandmarks[14], results.poseLandmarks[16], head * 20, head * 15); // 足の部分を描画 var waist = median([results.poseLandmarks[23], results.poseLandmarks[24]]); drawParts(waist, results.poseLandmarks[26], head * 23, head * 20); drawParts(results.poseLandmarks[26], median([results.poseLandmarks[28], results.poseLandmarks[30], results.poseLandmarks[32]]), head * 20, head * 15); drawParts(waist, results.poseLandmarks[25], head * 23, head * 20); drawParts(results.poseLandmarks[25], median([results.poseLandmarks[27], results.poseLandmarks[29], results.poseLandmarks[31]]), head * 20, head * 15); } 変な台形の描画は以下の部分です。 function drawParts(pos1, pos2, r1, r2){ var x1 = pos1.x * canvasElement.width; var y1 = pos1.y * canvasElement.height; var x2 = pos2.x * canvasElement.width; var y2 = pos2.y * canvasElement.height; var p = calcRange(x1, y1, x2, y2, r1, r2); canvasCtx.beginPath(); canvasCtx.moveTo(p.q1x, p.q1y); canvasCtx.lineTo(p.q3x, p.q3y); canvasCtx.lineTo(p.q4x, p.q4y); canvasCtx.lineTo(p.q2x, p.q2y); canvasCtx.lineTo(p.q6x, p.q6y); canvasCtx.lineTo(p.q5x, p.q5y); canvasCtx.fill(); canvasCtx.beginPath(); canvasCtx.arc(p.q1x, p.q1y, r1, 0, 2 * Math.PI); canvasCtx.fill(); canvasCtx.beginPath(); canvasCtx.arc(p.q2x, p.q2y, r2, 0, 2 * Math.PI); canvasCtx.fill(); } PoseのLandmarkの単点のうち、0, 1, 4, 11, 12, 13, 14, 15, 16, 23, 24, 25, 26, 27, 28 を使っています。 全体的な縮尺は、顔のパーツの間隔からだったり、胴体の大きさからだったり、なんでもよいです。 終わりに 専門家でもないので、もっとよい計算の仕方があるとは思いますが、まあ、頑張って自分なりのやり方で求めたので、記念に残しておきます。 あと、おまけで、Canvas部分をマウスクリックすれば、スナップショット撮影できるようにしたり、モーション状態を動画で録画できるようにしておきました。 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ドメイン検索×LINE Bot=効率的なドメイン検索   

【ドメイン管理の重要性】 昨今、新型コロナウィルスをきっかけに、人々の生活や働き方は、大きく変わりました。 ウェブサイトや電子メールは、ビジネスにおける重要な役割を担ってきており、インターネットがなければ生活できない時代となりました。 企業としても、インターネットの設備環境、サイト管理が非常に重要な資産管理となり、怠ってしまった結果、ブランド毀損、多額の賠償金に繋がっているケースも散見されます。 (例) ・サイト取得日や管理者が分からない⇒不更新となり、サイトが急にダウン ・サイトが存在しているかも分からない⇒いつの間にか、第三者に新規取得され、模倣サイト 【そんな中、、こんなシステムがあったら便利?】 サイト運営にあたり、ドメイン管理は、重要な役割であるが、各社・各部署どのドメインを取得、保持しているのか、分からないという事例が非常に多い。 その為、サイト構築するたびに、ドメインを新規取得している。 SEOの観点やセキュリティ上、あまりよろしくない。基本的に、サブドメインやディレクトリを活用する事がお勧めである。 そのような背景の中、各社がドメイン取得状況や更新月を一括で確認できるツールがあれば、非常に便利かつ、セキュリティレベルを強化できると感じた為、ドメイン管理に特化したLINE Botを作成しようと考えました。 【作成した仕組み】 文字列を含むドメインを検索すると一括確認できるシステム 【開発環境の準備】 ・LINE公式アカウント開設 ・LINE Developersアカウント登録 ・Herokuの登録 ・Node-REDインストール、アカウント登録 ・FacebookのAPI(https://domainsdb.info/?query=facebook) 【Node-RED構成】 【各ノード詳細】 http inノード:ラインサーバーから送信されたメッセージ取得 functionノード(左側):受け取ったメッセージ格納 // LINEサーバーからの情報を「msg.line」に移動させる msg.line = msg.payload; // msg.payloadにメッセージ本体を入れる msg.payload.domain = msg.payload.events[0].message.text; return msg; http request: 「function」ノード:受け取った2次元配列を格納したメッセージと突合させて条件に合致したものをメッセージとして代入する // http requestの結果を取得する const result = msg.payload // 返信したいメッセージの変数を定義しておく let reply = 'こんにちは、Facebookを含むドメインは、下記ドメインになります'; // http requestの中身を配列ごとに順番に処理 result.domains.forEach(item => { // 返信メッセージにドメイン名と改行を追記する reply = reply + JSON.stringify(item.domain) + "\n"; }); // LINEサーバーからの内容を復元する msg.payload = msg.line; // 返信メッセージをreplyの中身にする msg.payload.events[0].message.text = reply; return msg; 「ReplyMessage」ノード:メッセージをラインサーバーに返信 【動画】 【感想】 初めてLINE Botを活用し、ドメイン検索システムを作成してみましたが、 このシステムが各社に展開されれば利便性向上かつドメイン管理体制強化に繋がると感じました。 今後は、ドメイン検索に加え、DNS情報や権利者情報、有効期限なども一括で確認できる仕組みを構築していきたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ドメイン検索×LINE Bot=効率的なドメイン検索の仕組み

【ドメイン管理の重要性】 昨今、新型コロナウィルスをきっかけに、人々の生活や働き方は、大きく変わりましたよね。 ウェブサイトや電子メールは、ビジネスにおける重要な役割を担ってきており、インターネットがなければ生活できない時代となりました。 企業としても、インターネットの設備環境、サイト管理が非常に重要な資産管理となり、怠ってしまった結果、ブランド毀損、多額の賠償金に繋がっているケースも散見されます。 (例) ・サイト取得日や管理者が分からない⇒不更新となり、サイトが急にダウン ・サイトが存在しているかも分からない⇒いつの間にか、第三者に新規取得され、模倣サイト 【そんな中、、、、、、、こんなシステムがあったら便利?】 サイト運営にあたり、ドメイン管理は、重要な役割であるが、各社・各部署どのドメインを取得、保持しているのか分からないという事例が現状多い。 また、サイト構築するたびに、ドメインを新規で、取得する事は、SEOの観点やセキュリティ上、あまりよろしくない。基本的に、サブドメインやディレクトリを活用する事がお勧めである。 そのような背景の中、各社がドメイン取得状況や更新月を一括で確認できるツールがあれば、非常に便利かつ、セキュリティレベルを強化できると感じた為、ドメイン管理に特化したLINE Botを作成しようと考えました。 【作成した仕組み】 企業名を入力すると、ドメイン管理情報を一括で、確認できるシステム (ドメイン名、レコード情報、取得日、有効期限など) 【開発環境の準備】 ・LINE公式アカウント開設 ・LINE Developersアカウント登録 ・Herokuの登録 ・Node-REDインストール、アカウント登録 ・FACEbookのAPI 昨今、新型コロナウィルスをきっかけに、人々の生活や働き方は、大きく変わりましたよね。 ウェブサイトや電子メールは、ビジネスにおける重要な役割を担ってきており、インターネットがなければ生活できない時代となりました。 企業としても、インターネットの設備環境、サイト管理が非常に重要な資産管理となり、怠ってしまった結果、ブランド毀損、多額の賠償金に繋がっているケースも散見されます。 (例) ・サイト取得日や管理者が分からない⇒不更新となり、サイトが急にダウン ・サイトが存在しているかも分からない⇒いつの間にか、第三者に新規取得され、模倣サイト 【そんな中、、、、、、、こんなシステムがあったら便利】 サイト運営にあたり、ドメイン管理は、重要な役割であるが、 各社・各部署どのドメインを取得、保持しているのか分からないという事例が現状多い。 また、サイト構築するたびに、ドメインを新規取得する事は、SEOの観点やセキュリティ上、あまりよろしくない。 基本的に、サブドメインやディレクトリを活用する事がお勧めである。 そのような背景の中、各社がドメイン取得状況や更新月を一括で確認できるツールがあれば、非常に便利かつ、セキュリティレベルを強化できると感じた為、ドメイン管理に特化したLINE Botを作成しようと考えました。 【作成した仕組み】 保持ドメイン検索ツール 【開発環境の準備】 ・LINE公式アカウント ・LINE Developersアカウント ・Heroku ・Node-REDインストール、アカウント ・facebook API 【Node-RED構成】 【各ノード詳細】 http inノード:ラインサーバーから送信されたメッセージ取得 functionノード(左側):受け取ったメッセージ格納 // LINEサーバーからの情報を「msg.line」に移動させる msg.line = msg.payload; // msg.payloadにメッセージ本体を入れる msg.payload.domain = msg.payload.events[0].message.text; return msg; http request: 「function」ノード:受け取った2次元配列を格納したメッセージと突合させて条件に合致したものをメッセージとして代入する // http requestの結果を取得する const result = msg.payload // 返信したいメッセージの変数を定義しておく let reply = 'こんにちは、Facebookを含むドメインは、下記ドメインになります'; // http requestの中身を配列ごとに順番に処理 result.domains.forEach(item => { // 返信メッセージにドメイン名と改行を追記する reply = reply + JSON.stringify(item.domain) + "\n"; }); // LINEサーバーからの内容を復元する msg.payload = msg.line; // 返信メッセージをreplyの中身にする msg.payload.events[0].message.text = reply; return msg; 「ReplyMessage」ノード:メッセージをラインサーバーに返信 【動画】 【感想】 初めてLINE Botを活用し、ドメイン検索システムを作成してみましたが、 このシステムが各社に展開されれば利便性向上かつドメイン管理体制強化に繋がると感じました。 今後は、ドメイン検索に加え、DNS情報や権利者情報、有効期限なども一括で確認できる仕組みを構築していきたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】再レンダリングとメモ化

はじめに Reactの再レンダリングとメモ化、について学習したことを簡単にまとめてみました。 参考 わかりやすく有益な記事で、参考にさせていただきした。 本当にありがとうございました!! https://qiita.com/soarflat/items/b9d3d17b8ab1f5dbfed2#usecallback-%E3%82%92%E9%96%A2%E6%95%B0%E3%81%AE%E5%86%8D%E7%94%9F%E6%88%90%E3%82%92%E9%98%B2%E3%81%90%E7%9B%AE%E7%9A%84%E3%81%A7%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6%E3%81%AF%E3%81%84%E3%81%91%E3%81%AA%E3%81%84%E3%81%AE%E3%81%8B https://zenn.dev/b1essk/articles/react-re-rendering コンポーネントの再レンダリングが起きる条件 1. stateが更新されたとき 例:フォーム入力の値を監視するstateを保持しており、そのstateの値に変更があったとき(フォームに値を入力 or 削除するとき) 2. propsが更新されたとき 例:親コンポーネントで値を保持しており、その値をpropsで子コンポーネントに渡して表示している際にその値に変更があったとき 3. 親コンポーネントが再レンダリングされるとき メモ化による再レンダリングの最適化 再レンダリングの最適化とは Reactのコンポーネントではレンダリングは頻繁に起きることが多いため、そのたびに重い処理などを走らせるとパフォーマンスが落ちる。 その不必要な処理をできるだけ抑え、処理は必要最低限にしたい。 それによりページ描画スピード向上など、パフォーマンスの最適化をはかることが再レンダリングの最適化である。 メモ化とは 計算処理結果や生成された関数、コンポーネントなどを保持しておき、それを適切に再利用すること。 メモ化しておくことで不必要に処理を走らせることを防ぐことができる。 ※メモ化すること事態にもコストが発生するようなので、何でもかんでもメモ化すれば良いということでもないようである。 メモ化の方法 memo() コンポーネントに対してのメモ化 useCallback() コールバック関数に対してのメモ化 useMemo() 値に対してのメモ化 memo() コンポーネントに対してのメモ化 親コンポーネントが再レンダリングされても、子コンポーネントに渡すpropsの値が変更されない限り、子コンポーネントは再レンダリングされないようにする仕組み。 逆に言えばmemo()を利用してもpropsに変更があれば際レンダリングされてしまう。 memo(コンポーネント); useCallback() コールバック関数に対してのメモ化 不必要に関数が再生成されることを防ぐ。 第2引数で指定する依存配列に変更がない限り第1引数のコールバック関数は再生成されず、メモ化された保持している関数をコールバック関数を返す。 useCallbackを利用しないと、このコンポーネントがレンダリングされるたびにその関数は再生成される。この関数をpropsとして渡していたら、再生成のタイミングでpropsが変更されたことを子コンポーネントが検知して子コンポーネントも再レンダリングされる。 const 関数名 = useCallback(コールバック関数, [依存配列]); ※ 利用方針は様々あると思うが、基本的にはmemo(コンポーネント)とセットで利用することで子コンポーネントの不必要な再レンダリングを防ぐ目的で利用する。 useMemo() 値に対してのメモ化 不必要に重い処理を走らせることを防ぐ。 第2引数で指定する依存配列に変更がない限り第1引数の処理は実行されず、メモ化された保持している値を返す。 const 変数名 = useMemo(処理, [依存配列]); ※第1引数で渡す処理が重い処理でなければ、useMemo()を利用するコストの方が大きくなることもあり、利用するメリットが無いこともあるよう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring Boot で Ajax を実装する単純なサンプル

はじめに 初心者用の丁度よい記事が見当たらなかったので、Spring Boot で Ajax を動かすためのサンプルコードを残しておきます。 文字列を送信すると、2回繰り返して表示されるだけの簡単なものです。 Ajax は jQuery を使用して書きますが、参考としてネイティブ Java Script も併記しておきます。 CSRF 対策が有効な場合の対処についても触れておきます。 <参考サイト> ・Ajax:Jsonで受け取り画面遷移なしに更新する ・SpringBoot × Ajax通信による画面遷移 ・どのようにしてajaxでSpring Controllerからデータを取得できますか? 1. プロジェクトの作成 プロジェクトは、どのように作成しても構いませんが、一応書いておきます。 サンプルでは、ビルドツールは「Maven」、Java バージョンは「11」、パッケージングは「Jar」を選んでいます。とはいえ、何でも構いません。 特別なライブラリも入れる必要はありません。 次のように、「Spring Web」と「Thymeleaf」だけ選んでおきます。 Thymeleaf は直接には使用しませんが、HTML ファイルの読み込みで必要になるので入れておきます(参考)。 2. サンプルコード 以下、POST メソッドで HTTP リクエストを送信して、サーバからレスポンスを受け取るサンプルコードです。 2-1. 作成するファイル ファイルは、赤枠の3つを作成します。 js ファイルは、/src/main/resources/static に「js」フォルダを作成した上で格納します。 2-2. HTML ファイル HTML は次のように記述しておきます。 /src/main/resources/templates/index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Ajax Test</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="/js/note.js"></script> </head> <body> <form method="post" action="/test" id="note_form"> <input type="text" id="note"> <button type="submit">送信</button> </form> <div class="notes"></div> </body> </html> <補足> src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js" で、jQuery をGoogleのCDN経由で読み込んでいます(参考)。 src="/js/note.js" で、使用する JavaScript ファイルを指定します。 <div class="notes"> のところに Ajax で取得した文字列を表示します。 <参考:CSRF対策が有効な場合> なお、Spring Security などを導入していて、CSRF 対策が有効になっている場合は、Thymeleaf を使用して、form タグの action 属性を次のようにする必要があります。 Sample <html xmlns:th="http://www.thymeleaf.org"> <!-- 略 --> <form method="post" th:action="@{/test}" id="note_form"> <!-- 略 --> こうすることで、次のように自動的に CSRF トークンが作成されます。 この、CSRF トークンがないまま POST メソッド等でリクエストをしても、サーバ側で拒否されてしまいます。 2-3. JavaScript ファイル 2-3-1. jQuery で実装(CSRF 対策なし) jQuery を使用して Ajax の処理を書くと次のようになります。 /src/main/resources/static/js/note.js $(function() { $("#note_form").on("submit", function(e) { e.preventDefault(); // デフォルトのイベント(ページの遷移やデータ送信など)を無効にする $.ajax({ url: $(this).attr("action"), // リクエストを送信するURLを指定(action属性のurlを抽出) type: "POST", // HTTPメソッドを指定(デフォルトはGET) data: { note: $("#note").val() // 送信データ } }) .done(function(data) { $(".notes").append(`<div>${data}</div>`); // HTMLを追加 $("#note").val(""); // 入力欄を空にする }) .fail(function() { alert("error!"); // 通信に失敗した場合の処理 }) }); }); <補足> $(this).attr("action") で、action 属性のURL文字列("/test")を取り出しています。 type: "POST" で、HTTP メソッドを「POST」と指定しています。 data: { note: $("#note").val() } で、リクエストで送るデータを「キー/値のペア」で指定しています。$("#note")により、id="note" の要素を探し出しています。 $(".notes").append([HTML文]); で、class="notes" のタグの子要素として [HTML文] を追加しています。 <Ajax実装のひな形> jQuery による Ajax のひな形を簡単に示すと次のとおりです(詳細はこちらを参照してください)。 jQueryのAjax実装のひな形 $.ajax({ url: /* リクエストを送信するURLを指定 */, type: /* HTTPメソッドを指定 */, data: /* 送信データを指定 */, dataType: /* レスポンスデータの形式を指定 */ }) .done(function(data) { // (1) リクエストが成功した場合に行う処理 }) .fail(function() { // (2) リクエストが成功しなかった場合に行う処理 }) .always(function() { // (3) リクエストの成功・失敗に関わらず行う処理 }); <参考サイト> ・JavaScript 日本語リファレンス jQuery $.ajax() ・一般的なAjax通信を実装するには?($.ajax) 2-3-2. jQuery で実装(CSRF 対策あり) この記事で作成しているサンプルコードでは、CSRF 対策は考慮する必要はありませんが、参考までに CSRF 対策が有効になっている場合のコードも併せて書いておきます。 Spring Security などを導入して CSRF 対策を有効にしている場合は、CSRF トークンを併せて送信しないと、認可エラーが生じます。 具体的には、送信するデータの項目に、_csrf: $("*[name=_csrf]").val() と追加することで、POST メソッドによるリクエストが可能となります。 /src/main/resources/static/js/note.js $(function() { $("#note_form").on("submit", function(e) { e.preventDefault(); $.ajax({ url: $(this).attr("action"), type: "POST", data: { note: $("#note").val(), _csrf: $("*[name=_csrf]").val() // CSRFトークンを送信 } }) .done(function(data) { $(".notes").append(`<div>${data}</div>`); $("#note").val(""); }) .fail(function() { alert("error!"); }) }); }); 2-3-3. (参考)ネイティブ JavaScript で実装 なお、参考までにネイティブ JavaScript で書くと次のようになります(無視してよい内容なので、折りたたんでいます)。 JavaScript のソースコード /src/main/resources/static/js/note.js window.addEventListener("load", function() { document.getElementById("note_form").addEventListener("submit", function(e) { e.preventDefault(); // デフォルトのイベントを無効にする let inputText = document.getElementById("note").value; // 入力値を取得 let url = document.getElementById("note_form").getAttribute("action"); // action属性のurlを抽出 let data = "note=" + encodeURIComponent(inputText); // URIエンコード let httpRequest = new XMLHttpRequest(); // XMLHttpRequestのインスタンス作成 httpRequest.open("POST", url, true); // open(HTTPメソッド, URL, 非同期通信[true:default]か同期通信[false]か) httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // リクエストヘッダーを追加(URIエンコードで送信) httpRequest.send(data); // sendメソッドでサーバに送信 httpRequest.onreadystatechange = function() { if (httpRequest.readyState === 4) { // readyStateが4になればデータの読込み完了 if (httpRequest.status === 200) { // statusが200の場合はリクエストが成功 let divElm = document.createElement("div"); // 追加用のdivタグを作成 divElm.appendChild(document.createTextNode(httpRequest.response)); // レスポンスで得た値をdivタグの子要素に追加 document.getElementsByClassName("notes")[0].appendChild(divElm); // class="notes" の子要素として追加 document.getElementById("note").value = ""; // テキストエリアを空白に戻す } else { // statusが200以外の場合はリクエストが適切でなかったとしてエラー表示 alert("error"); } } }; }, false); }); 補足は、コメント部分を参照してください。 <CSRF 対策が有効な場合> Spring Security を導入すると、CSRF 対策への対応も必要となります。 jQuery の場合と同様に、次のように data の内容を変更して、CSRF トークンを送信するようにすればリクエストをすることができます。 修正前 let data = "note=" + encodeURIComponent(inputText); 修正後 let csrfToken = document.getElementsByName("_csrf")[0].value; // csrfトークンを取得 let data = "note=" + encodeURIComponent(inputText) + "&_csrf=" + encodeURIComponent(csrfToken); // リクエストbodyにcsrfトークンを追加 JavaScript の Ajax については、次の記事で細かく書いていますのでご参考としていただければ幸いです。 ・RailsにおけるAjaxの実装(JavaScriptとjQueryのコード比較) ・JavaScriptの多次元ハッシュ(連想配列)を一括でURIエンコードする 2-4. Controller コントローラーは、次のように書きます。 /src/main/java/com/example/demo/TestController.java package com.example.demo; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping public class TestController { @GetMapping public String index() { return "index"; } @PostMapping("/test") @ResponseBody public String note(@RequestParam String note) { return note + note; } } ① リクエストデータを受け取る クライアントから送信されたリクエストパラメータを受け取るには、@RequestParam アノテーションを使用します。 メソッドの引数に、@RequestParam String note と指定することで、キーが note のデータを取得することができます。 ② メソッドの戻り値をレスポンスデータにする @Controller で作成したコントローラーのメソッドの戻り値は、基本的に view の遷移先となります。 この場合、メソッドに @ResponseBody アノテーションを付けることで、戻り値を HTTP レスポンスのコンテンツとすることができます。 なお、@RestController を使用してコントローラーを作成すると、メソッドの戻り値がそのままレスポンスのコンテンツとなります(後述「3-2. @RestController を使用する場合」に記載)。 サンプルコードは以上です。 これで、Ajax を使用したデータのやり取りができます。 3. 補足 補足として、別のパターンによる実装にも触れておきます。 3-1. レスポンスデータを JSON で返す場合 レスポンスデータは JSON で返すことの方が多いと思いますので、そのパターンを書いておきます。 3-1-1. JavaScript ファイル JavaScript ファイルでは、レスポンスデータを JSON 形式で受け取れるように修正します。 3-1-1-1. jQuery で実装 /src/main/resources/static/js/note.js $(function() { $("#note_form").on("submit", function(e) { e.preventDefault(); $.ajax({ url: $(this).attr("action"), type: "POST", data: { note: $("#note").val() }, dataType: "json" // レスポンスデータをjson形式と指定する }) .done(function(data) { $(".notes").append(`<div>${data.note}</div>`); // JSON形式のレスポンスからnoteを取得 $("#note").val(""); }) .fail(function() { alert("error!"); }) }); }); 修正したのは、コメントを付した2行です。 dataType: "json" で、レスポンスデータをjson形式と指定しています。 ${data.note} で、JSON 形式で受け取った data から、キーが note の値を取得しています。 なお、Spring Security を導入していると、CSRF トークンの送信も必要となります。 実装方法は、前述の「2-3-2. jQuery で実装(CSRF 対策あり)」を参照してください。 3-1-1-2. (参考)ネイティブ JavaScript で実装 ネイティブ javaScript で書くと次のとおりです。 javaScript のソースコード /src/main/resources/static/js/note.js window.addEventListener("load", function() { document.getElementById("note_form").addEventListener("submit", function(e) { e.preventDefault(); let inputText = document.getElementById("note").value; let url = document.getElementById("note_form").getAttribute("action"); let data = "note=" + encodeURIComponent(inputText); let httpRequest = new XMLHttpRequest(); httpRequest.open("POST", url, true); httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); httpRequest.setRequestHeader("Accept", "application/json"); // リクエストヘッダーを追加(クライアントが理解できるコンテンツタイプをサーバに通知) httpRequest.responseType = "json"; // レスポンスデータをjson形式と指定 httpRequest.send(data); httpRequest.onreadystatechange = function() { if (httpRequest.readyState === 4) { if (httpRequest.status === 200) { let divElm = document.createElement("div"); divElm.appendChild(document.createTextNode(httpRequest.response.note)); // JSON形式のレスポンスからnoteを取得 document.getElementsByClassName("notes")[0].appendChild(divElm); document.getElementById("note").value = ""; } else { alert("error"); } } }; }, false); }); 修正したのは、コメントを付した部分です。 ① レスポンスデータをjson形式と指定 レスポンスデータをjson形式と指定するには、次の2行が必要です. Sample httpRequest.setRequestHeader("Accept", "application/json"); httpRequest.responseType = "json"; 最初の行では、クライアントが受け取るデータタイプを JSON と指定しています(参考記事)。 2行目は、受け取るデータの形式が JSON 形式であること指定しています(指定しなければ"note":"hogehoge" という形のままですが、指定すればnote: "hogehoge" という形のデータとして受け取れるので、その後の処理が簡単になります)。 ② JSON 形式のデータから値を取得 次の1行で、受け取ったデータ httpRequest.response から、キーが note のデータを取り出して、HTML コードを生成しています。 Sample divElm.appendChild(document.createTextNode(httpRequest.response.note)); なお、Spring Security を導入していると、CSRF トークンの送信も必要となります。 実装方法は、前述の「2-3-3. (参考)ネイティブ JavaScript で実装」を参照してください。 3-1-2. Controller コントローラーでは、次のようにして、レスポンスデータを単純に JSON 形式にしています。 これにより、戻り値は "{"note":"テストテスト"}" というような文字列になります。 /src/main/java/com/example/demo/TestController.java // 略 @Controller @RequestMapping public class TestController { // 略 @PostMapping("/test") @ResponseBody public String note(@RequestParam String note) { String json = "{\"note\":\"" + note + note + "\"}"; return json; } } <参考サイト> ・JSONとは?データフォーマット(データ形式)について学ぼう! 3-2. @RestController を使用する場合 @Controller に代えて @RestController を使用すると、メソッドの戻り値がそのままレスポンスのコンテンツになります。 Ajax のみのコントローラーを作成する場合は、こちらを使用した方がシンプルに記述できます。 /src/main/java/com/example/demo/TestController.java // 略 @RestController @RequestMapping public class TestController { @GetMapping public ModelAndView index() { ModelAndView mav = new ModelAndView(); mav.setViewName("index"); return mav; } @PostMapping("/test") public String note(@RequestParam String note) { return note + note; } } <参考サイト> ・Spring BootのRestControllerからHTMLを生成する方法 さいごに とりあえず、順序通りにコードを書けば動くものを記事として残しておきました。 まだ、色々と試している段階なので、CSRF トークンの送信方法については、もっと簡単な方法があるのかもしれません。より簡単な方法がわかりましたら追記するようにします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring Boot で Ajax を実装する単純なサンプル(CSRF 対策への対応も含む)

はじめに 初心者用の丁度よい記事が見当たらなかったので、Spring Boot で Ajax を動かすためのサンプルコードを残しておきます。 文字列を送信すると、2回繰り返して表示されるだけの簡単なものです。 Ajax は jQuery を使用して書きますが、参考としてネイティブ Java Script も併記しておきます。 CSRF 対策が有効な場合の対処についても触れておきます。 <参考サイト> ・Ajax:Jsonで受け取り画面遷移なしに更新する ・SpringBoot × Ajax通信による画面遷移 ・どのようにしてajaxでSpring Controllerからデータを取得できますか? 1. プロジェクトの作成 プロジェクトは、どのように作成しても構いませんが、一応書いておきます。 サンプルでは、ビルドツールは「Maven」、Java バージョンは「11」、パッケージングは「Jar」を選んでいます。とはいえ、何でも構いません。 特別なライブラリも入れる必要はありません。 次のように、「Spring Web」と「Thymeleaf」だけ選んでおきます。 Thymeleaf は直接には使用しませんが、HTML ファイルの読み込みで必要になるので入れておきます(参考)。 2. サンプルコード 以下、POST メソッドで HTTP リクエストを送信して、サーバからデータを受け取るサンプルコードです。 2-1. 作成するファイル ファイルは、赤枠の3つを作成します。 js ファイルは、/src/main/resources/static に「js」フォルダを作成した上で格納します。 2-2. HTML ファイル HTML は次のように記述しておきます。 /src/main/resources/templates/index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Ajax Test</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script src="/js/note.js"></script> </head> <body> <form method="post" action="/test" id="note_form"> <input type="text" id="note"> <button type="submit">送信</button> </form> <div class="notes"></div> </body> </html> <補足> src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js" で jQuery をGoogleのCDN経由で読み込んでいます(参考)。 src="/js/note.js" で使用する JavaScript ファイルを指定します。 <div class="notes"> のところに Ajax で取得した文字列を表示します。 <CSRF対策が有効な場合> なお、Spring Security などを導入していて、CSRF 対策が有効になっている場合は、Thymeleaf を使用して、form タグの action 属性を次のようにする必要があります。 Sample <html xmlns:th="http://www.thymeleaf.org"> <!-- 略 --> <form method="post" th:action="@{/test}" id="note_form"> <!-- 略 --> こうすることで、次のように自動的に CSRF トークンが作成されます。 この、CSRF トークンがないまま POST メソッド等でリクエストをしても、サーバ側で拒否されてしまいます。 2-3. JavaScript ファイル 2-3-1. jQuery で実装(CSRF 対策なし) jQuery で書くと次のようになります。 /src/main/resources/static/js/note.js $(function() { $("#note_form").on("submit", function(e) { e.preventDefault(); // デフォルトのイベント(HTMLデータ送信など)を無効にする $.ajax({ url: $(this).attr("action"), // リクエストを送信するURLを指定(action属性のurlを抽出) type: "POST", // HTTPメソッドを指定(デフォルトはGET) data: { note: $("#note").val() // 送信データ } }) .done(function(data) { $(".notes").append(`<div>${data}</div>`); // HTMLを追加 $("#note").val(""); // 入力欄を空にする }) .fail(function() { alert("error!"); // 通信に失敗した場合の処理 }) }); }); <補足> $(this).attr("action") で、action 属性のURL文字列("/test")を取り出しています。 type: "POST" で、HTTP メソッドを「POST」と指定しています。 data: { note: $("#note").val() } で、リクエストで送るデータを「キー/値のペア」で指定しています。id="note" で取得する値を特定しています。 $(".notes").append([HTML文]); で、class="notes" のタグの子要素として [HTML文] を追加しています。 <Ajax実装のひな形> jQuery による Ajax のひな形を簡単に示すと次のとおりです(詳細はこちらを参照してください)。 jQueryのAjax実装のひな形 $.ajax({ url: /* リクエストを送信するURLを指定 */, type: /* HTTPメソッドを指定 */, data: /* 送信データを指定 */, dataType: /* レスポンスデータの形式を指定 */ }) .done(function(data) { // (1) リクエストが成功した場合に行う処理 }) .fail(function() { // (2) リクエストが成功しなかった場合に行う処理 }) .always(function() { // (3) リクエストの成功・失敗に関わらず行う処理 }); <参考サイト> ・JavaScript 日本語リファレンス jQuery $.ajax() ・一般的なAjax通信を実装するには?($.ajax) 2-3-2. jQuery で実装(CSRF 対策あり) Spring Security を導入して CSRF 対策を有効にしている場合は、CSRF トークンを併せて送信しないと、認可エラーが生じます。 この場合は、送信するデータの項目に、_csrf: $("*[name=_csrf]").val() を追加することで、POST メソッドによるリクエストが可能となります。 /src/main/resources/static/js/note.js $(function() { $("#note_form").on("submit", function(e) { e.preventDefault(); $.ajax({ url: $(this).attr("action"), type: "POST", data: { note: $("#note").val(), _csrf: $("*[name=_csrf]").val() // CSRFトークンを送信 } }) .done(function(data) { $(".notes").append(`<div>${data}</div>`); $("#note").val(""); }) .fail(function() { alert("error!"); }) }); }); 2-3-3. (参考)ネイティブ JavaScript で実装 なお、参考までにネイティブ JavaScript で書くと次のようになります(無視してよい内容なので、折りたたんでいます)。 JavaScript のソースコード /src/main/resources/static/js/note.js window.addEventListener("load", function() { document.getElementById("note_form").addEventListener("submit", function(e) { e.preventDefault(); // デフォルトのイベントを無効にする let inputText = document.getElementById("note").value; // 入力値を取得 let url = document.getElementById("note_form").getAttribute("action"); // action属性のurlを抽出 let data = "note=" + encodeURIComponent(inputText); // URIエンコード let httpRequest = new XMLHttpRequest(); // XMLHttpRequestのインスタンス作成 httpRequest.open("POST", url, true); // open(HTTPメソッド, URL, 非同期通信[true:default]か同期通信[false]か) httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // リクエストヘッダーを追加(URIエンコードで送信) httpRequest.send(data); // sendメソッドでサーバに送信 httpRequest.onreadystatechange = function() { if (httpRequest.readyState === 4) { // readyStateが4になればデータの読込み完了 if (httpRequest.status === 200) { // statusが200の場合はリクエストが成功 let divElm = document.createElement("div"); // 追加用のdivタグを作成 divElm.appendChild(document.createTextNode(httpRequest.response)); // レスポンスで得た値をdivタグの子要素に追加 document.getElementsByClassName("notes")[0].appendChild(divElm); // class="notes" の子要素として追加 document.getElementById("note").value = ""; // テキストエリアを空白に戻す } else { // statusが200以外の場合はリクエストが適切でなかったとしてエラー表示 alert("error"); } } }; }, false); }); 補足は、コメント部分を参照してください。 <CSRF 対策が有効な場合> Spring Security を導入すると、CSRF 対策への対応も必要となります。 jQuery の場合と同様に、次のように data の内容を変更して、CSRF トークンを送信するようにすればリクエストをすることができます。 修正前 let data = "note=" + encodeURIComponent(inputText); 修正後 let csrfToken = document.getElementsByName("_csrf")[0].value; // csrfトークンを取得 let data = "note=" + encodeURIComponent(inputText) + "&_csrf=" + encodeURIComponent(csrfToken); // リクエストbodyにcsrfトークンを追加 JavaScript の Ajax については、次の記事で細かく書いていますのでご参考としていただければ幸いです。 ・RailsにおけるAjaxの実装(JavaScriptとjQueryのコード比較) ・JavaScriptの多次元ハッシュ(連想配列)を一括でURIエンコードする 2-4. Controller コントローラーは、次のように書きます。 /src/main/java/com/example/demo/TestController.java package com.example.demo; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping public class TestController { @GetMapping public String index() { return "index"; } @PostMapping("/test") @ResponseBody public String note(@RequestParam String note) { return note + note; } } ① リクエストデータを受け取る クライアントから送信されたリクエストパラメータを受け取るには、@RequestParam アノテーションを使用します。 @RequestParam String note とすることで、キーが note のデータを取得することができます。 ② メソッドの戻り値をレスポンスデータにする @Controller のメソッドの戻り値は、基本的に view の遷移先となります。 メソッドに @ResponseBody アノテーションを付けることで、戻り値を HTTP レスポンスのコンテンツとすることができます。 なお、@RestController を使用すると、メソッドの戻り値がそのままレスポンスのコンテンツとなります(後述「3-2. @RestController を使用する場合」に記載)。 サンプルコードは以上です。 これで、Ajax を使用したデータのやり取りができます。 3. 補足 補足として、別のパターンによる実装にも触れておきます。 3-1. レスポンスデータを JSON で返す場合 レスポンスデータは JSON で返すことの方が多いと思いますので、そのパターンを書いておきます。 3-1-1. JavaScript ファイル JavaScript ファイルでは、レスポンスデータを JSON 形式で受け取れるように修正します。 3-1-1-1. jQuery で実装 /src/main/resources/static/js/note.js $(function() { $("#note_form").on("submit", function(e) { e.preventDefault(); $.ajax({ url: $(this).attr("action"), type: "POST", data: { note: $("#note").val() }, dataType: "json" // レスポンスデータをjson形式と指定する }) .done(function(data) { $(".notes").append(`<div>${data.note}</div>`); // JSON形式のレスポンスからnoteを取得 $("#note").val(""); }) .fail(function() { alert("error!"); }) }); }); 修正したのは、コメントを付した2行です。 dataType: "json" で、レスポンスデータをjson形式と指定しています。 ${data.note} で、JSON 形式で受け取った data から、キーが note の値を取得しています。 なお、Spring Security を導入していると、CSRF トークンの送信も必要となります。 実装方法は、前述の「2-3-2. jQuery で実装(CSRF 対策あり)」を参照してください。 3-1-1-2. (参考)ネイティブ JavaScript で実装 ネイティブ javaScript で書くと次のとおりです。 javaScript のソースコード /src/main/resources/static/js/note.js window.addEventListener("load", function() { document.getElementById("note_form").addEventListener("submit", function(e) { e.preventDefault(); let inputText = document.getElementById("note").value; let url = document.getElementById("note_form").getAttribute("action"); let data = "note=" + encodeURIComponent(inputText); let httpRequest = new XMLHttpRequest(); httpRequest.open("POST", url, true); httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); httpRequest.setRequestHeader("Accept", "application/json"); // リクエストヘッダーを追加(クライアントが理解できるコンテンツタイプをサーバに通知) httpRequest.responseType = "json"; // レスポンスデータをjson形式と指定 httpRequest.send(data); httpRequest.onreadystatechange = function() { if (httpRequest.readyState === 4) { if (httpRequest.status === 200) { let divElm = document.createElement("div"); divElm.appendChild(document.createTextNode(httpRequest.response.note)); // JSON形式のレスポンスからnoteを取得 document.getElementsByClassName("notes")[0].appendChild(divElm); document.getElementById("note").value = ""; } else { alert("error"); } } }; }, false); }); 修正したのは、コメントを付した部分です。 ① レスポンスデータをjson形式と指定 レスポンスデータをjson形式と指定するには、次の2行が必要です. Sample httpRequest.setRequestHeader("Accept", "application/json"); httpRequest.responseType = "json"; 最初の行では、クライアントが受け取るデータタイプを JSON と指定しています(参考記事)。 2行目は、表示形式を JSON 形式に指定しています(指定しなければ"note":"hogehoge"、指定すればnote: "hogehoge" という形のデータになるだけです)。 ② JSON 形式のデータから値を取得 次の1行で、受け取ったデータ httpRequest.response から、キーが note のデータを取り出して、HTML コードを生成しています。 Sample divElm.appendChild(document.createTextNode(httpRequest.response.note)); なお、Spring Security を導入していると、CSRF トークンの送信も必要となります。 実装方法は、前述の「2-3-3. (参考)ネイティブ JavaScript で実装」を参照してください。 3-1-2. Controller コントローラーでは、次のようにして、レスポンスデータを単純に JSON 形式にしています。 これにより、戻り値は "{"note":"テストテスト"}" というような文字列になります。 /src/main/java/com/example/demo/TestController.java // 略 @Controller @RequestMapping public class TestController { // 略 @PostMapping("/test") @ResponseBody public String note(@RequestParam String note) { String json = "{\"note\":\"" + note + note + "\"}"; return json; } } <参考サイト> ・JSONとは?データフォーマット(データ形式)について学ぼう! 3-2. @RestController を使用する場合 @Controller に代えて @RestController を使用すると、メソッドの戻り値がそのままレスポンスのコンテンツになります。 Ajax のみのコントローラーを作成する場合は、こちらを使用した方がシンプルに記述できます。 /src/main/java/com/example/demo/TestController.java // 略 @RestController @RequestMapping public class TestController { @GetMapping public ModelAndView index() { ModelAndView mav = new ModelAndView(); mav.setViewName("index"); return mav; } @PostMapping("/test") public String note(@RequestParam String note) { return note + note; } } <参考サイト> ・Spring BootのRestControllerからHTMLを生成する方法 さいごに とりあえず、順序通りにコードを書けば動くものを記事として残しておきました。 まだ、色々と試している段階なので、CSRF トークンの送信方法については、もっと簡単な方法があるのかもしれません。より簡単な方法がわかりましたら追記するようにします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JS]thisのややこしさに終止符を打つ。

はじめに JavaSctiptに苦手意識がついてしまった原因が何を隠そう「this」です。初心者のうちは特に理解できずに苦しみました。しかし、重要な概念であるため、これをおざなりにしてはいけないと思い、学習してなんとか理解することが出来ましたので、自分と同じ境遇の人に共有します。。 thisとは thisとは、呼び出し元のオブジェクトへの参照を保持するキーワードのです。 thisの参照先は、同じ関数でも呼び出し方法が違えば、thisは異なるオブジェクトを参照します。 もう少し詳しく言うと、関数(function)を呼んだ時の.の前についているオブジェクトを指しているということです。(.が省略された場合はグローバルオブジェクトになります。) // 関数testを定義 function test() { console.log(this) } test() // => Window {frames: Window, postMessage: ƒ, ...} // オブジェクトを定義 const animal = { name: 'dog', introduce: function() { console.log('The animal is ' + this.name); // ここでのthisは「animal」を指す } } animal.introduce(); // The animal is dog と出力 bind bindとは、強制的にあるオブジェクトと結びつけるものです。 第一引数には結びつけたいオブジェクトを指定し、第二引数以降は渡したい実引数を指定する。 // thisをコンソールに出力する関数testを定義 function test() { console.log(this) } let obj = { name: "obj" } let check = test.bind(obj) // testをobjに結びつける test() // Window {frames: Window, postMessage: ƒ, ...} check() // => {name: "obj"} callとapply callやapplyを使って関数を呼び出すとオブジェクトの指定と同時に関数を実行することが出来る。 第一引数には結びつけたいオブジェクトを指定し、第二引数以降は渡したい実引数を指定する。 // thisをコンソールに出力する関数testを定義 function test() { console.log(this) } let obj = { name: "obj" } test() // Window {frames: Window, postMessage: ƒ, ...} test.call(obj) // => {name: "obj"} アロー関数 アロー関数とはES6から使用できる、無名関数を省略化して記述する記法のことです。 // 無名関数の場合 const greet = { hello: function() { console.log('hello'); } } // アロー関数の場合 const greet = { hello: () => { console.log('hello'); } } greet.hello(); // 'hello' 無名関数とアロー関数の違い 下記の表を見たら分かる通り、アロー関数では使えない機能があります。省略記法といっても全く同じように使えるわけでは無いことを理解しておく必要があります・ 無名関数 アロー関数 this ○ × arguments ○ × new ○ × prototype ○ × 終わりに 少しはthisに対する苦手意識は薄れましたでしょうか? 苦手だと思っているものはなかなか頭が働きませんが、一つ踏み出せれば理解できる日が来ると思うので、共に頑張りましょう! 参考 JavaScript の this を理解する多分一番分かりやすい説明 【JavaScript】thisを弄ぼう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【第2回】ブラウザ上で動画をカット編集する

ブラウザ上で動画を表示し、指定した時間でカット編集する方法をメモしておきます。 動画編集はMoviePyというPythonライブラリを用いることとし、環境はAWSのcloud9で作成します。 全2回の構成で、第1回ではMoviePyを使って動画編集を行えるようにします。 第2回では、javascriptでカットする領域を表示するなどします。 間違い等あった場合は、コメントでご指摘いただけると幸いです。 第1回はこちら。 1. cut.htmlの作成(前回と同じ) まずはcut.htmlを書いておきます。 cut.html {% load static %} <html lang="ja"> <head> <meta charset="Shift-JIS"> <title>video edit</title> <link rel="stylesheet" href="{% static 'main.css' %}"> </head> <body> <h1>カット編集画面</h1> <section class="cut_left"> <video id=video controls src='{{ MEDIA_URL }}{{ record.Original }}'></video> {% for time in cuttime_list %} <div hidden class='cut_starttime'>{{ time.startTime }}</div> <div hidden class='cut_endtime'>{{ time.endTime }}</div> {% endfor %} <div hidden id='video_duration'>{{ video_duration }}</div> <div class="flagBox"> {% for time in cuttime_list %} <div name="cutflag"></div> {% endfor %} </div> <div class="changeTime"> <button id="changeStartTime">カット開始時間変更</button> <button id="changeEndTime">カット終了時間変更</button> </div> <form method="post"> {% csrf_token %} <div class='edit_time'> <input type='number' value=5 min=0 max={{ video_duration }} step='0.01' id='startTime' name='startTime'> <input type='number' value=10 min=0 max={{ video_duration }} step='0.01' id='endTime' name='endTime'> </div> <div class='cut_operation'> <input type="submit" name="cutCanceled" value="キャンセル"> <input type="submit" name="cutReset" id="cutReset" value="元に戻す"> <input type="submit" name="cutExecute" id="cutExecute" value="カットする"> <input type="submit" name="cutFinished" id="cutFinished" value="完了"> </div> </form> <a href="/"><p>戻る</p></a> </section> <script src={% static 'cut.js' %}></script> </body> </html> 2. main.cssの作成 main.css body { color: #333; font-family: Verdana, sans-serif; margin: 10px; } .cut_left { float: left; width: 70vw; } .cut_left video { width: 63vw; } .flagBox{ width:90%; height:10px; background-color: #C0C0C0; margin: 0; margin-top: 20px; position:relative; } .changeTime button { margin-top: 10px; width: 140px; } .edit_time input { margin-bottom: 10px; width: 140px; } main.cssでhtmlの表示を微調整します。 ここで重要なのは".flagBox"の部分です。 flagBoxは、動画の横のサイズと同じ長さのグレーのバーを表示するために記述しています。 最初の"width:90%"は、cut_leftに占める動画の大きさと同じにしています(63vw/70vw=0.9)。これにより、バーの長さが動画の横のサイズと同じになります。 後は高さと色の設定などです。 3. cut.jsの作成 最後に、JavaScriptを記述していきます。 ここでは、カット開始/終了時間の変更や、カットする領域のバー形式での表示を担当します。 3.1 動画の再生 cut.js // videoの取得 let video = document.getElementById('video'); // 自動再生 video.autoplay = false; // ループ video.loop = true; // カット開始/終了時間を格納 let startTimeArray = []; let endTimeArray = []; // 現在の経過時間 video.addEventListener('timeupdate', function(e) { for (let i=0; i<endTimeArray.length; i++){ if (video.currentTime > startTimeArray[i] && video.currentTime < endTimeArray[i]) { video.currentTime = endTimeArray[i]; } } }); 最初の行で、cut.htmlでidを'video'としているタグを取得します。 自動再生=false、ループ=trueとしておきます。 また、カット開始/終了時間を格納しておく配列を作成し、動画再生中にカット開始時間に来たらカット終了時間まで飛ばすようにしています。 3.2 カット開始/終了時間変更ボタンを押したときの挙動 cut.js // カット開始時間変更ボタンを押した時 let changeStartTime = document.getElementById("changeStartTime"); let startTimeNum = document.getElementById("startTime"); changeStartTime.addEventListener('click', function () { startTimeNum.value = Math.round(video.currentTime * 100) / 100; }); // カット終了時間変更ボタンを押した時 let changeEndTime = document.getElementById("changeEndTime"); let endTimeNum = document.getElementById("endTime"); changeEndTime.addEventListener('click', function () { endTimeNum.value = Math.round(video.currentTime * 100) / 100; }); cut.htmlで「カット開始時間変更」ボタンのidを"changeStartTime"、「カット開始時間」のidを"startTime"と指定しています。ボタンをクリックしたとき、「カット開始時間」のvalueを動画の現在時刻に変更しています。 カット終了時間についても同様です。 3.3 カット領域の表示 cut.js // htmlから必要な要素を取得 let startClass = document.getElementsByClassName("cut_starttime"); let endClass = document.getElementsByClassName("cut_endtime"); let flagName = document.getElementsByName("cutflag"); // カットされている時間の集合 let cutBar = []; // 動画再生時間 let duration = parseFloat(document.getElementById('video_duration').textContent); let inverse_duration = 100 / duration; // カット領域の表示 ShowCutArea(); function ShowCutArea() { let startTimeID = "startTime"; let endTimeID = "endTime"; let flagID = "cutFlag"; let startPer = [0]; // 0を入れておく let endPer = [0]; // 0を入れておく for (var i=0; i<startClass.length; i++){ startClass[i].setAttribute("id", startTimeID + i); endClass[i].setAttribute("id", endTimeID + i); startTimeArray.push(document.getElementById(startTimeID + i).textContent); endTimeArray.push(document.getElementById(endTimeID + i).textContent); } for (var i=1; i<=startClass.length; i++){ startPer[i] = startTimeArray[i-1] * inverse_duration; endPer[i] = endTimeArray[i-1] * inverse_duration; flagName[i-1].className = flagID + i; cutBar[i] = document.getElementsByClassName(flagID + i); cutBar[i][0].style.marginLeft = (startPer[i] - endPer[i-1]) + '%'; cutBar[i][0].style.width = (endPer[i] - startPer[i]) + '%'; cutBar[i][0].style.backgroundColor = "#000000"; cutBar[i][0].style.height = 10 + "px"; cutBar[i][0].style.cssFloat = "left"; } } "ShowCutArea"関数でカット領域を表示します。 この中では同じfor文を2度回しています。 1回目のfor文では、カット開始/終了時間を取得しています。 cut.htmlで、カット開始時間に"cut_starttime"のclassを付けて非表示にしていました。 この値を取得するため、classに順番にstartTimeID1, startTimeID2…とIDを振り、startTimeArrayにこのIDのテキストをpushしていきます。 この操作により、startTimeArray, endTimeArrayにカット開始/終了時間を格納できました。 2回目のfor文では、cut.htmlのflagBox(main.cssによってグレーのバーとして表示されている)のうち、黒く表示する部分を決めています。 実際に色を変更しているのは以下の部分です。 cutBar[i][0].style.marginLeft = (startPer[i] - endPer[i-1]) + '%'; cutBar[i][0].style.width = (endPer[i] - startPer[i]) + '%'; cutBar[i][0].style.backgroundColor = "#000000"; 表示領域は%で表したいので、割合を計算してstartPer, endPerに入れています。 黒く表示する部分の左端はカット開始時間から1つ前のカット終了時間を引いた値、黒く表示する部分の長さはカット終了時間からカット開始時間を引いた値になります。 配列の長さだけ繰り返すことで、複数のカット時間に対応できるようになっています。 cut.js全体はこちらです。 順番等、変更している部分があります。 cut.js 'use strict'; // videoの取得 let video = document.getElementById('video'); // 自動再生 video.autoplay = false; // ループ video.loop = true; // カット開始/終了時間を格納 let startTimeArray = []; let endTimeArray = []; // htmlから必要な要素を取得 let startClass = document.getElementsByClassName("cut_starttime"); let endClass = document.getElementsByClassName("cut_endtime"); let flagName = document.getElementsByName("cutflag"); // カットされている時間の集合 let cutBar = []; // 動画再生時間 let duration = parseFloat(document.getElementById('video_duration').textContent); let inverse_duration = 100 / duration; // カット領域の表示 ShowCutArea(); // 現在の経過時間 video.addEventListener('timeupdate', function(e) { for (let i=0; i<endTimeArray.length; i++){ if (video.currentTime > startTimeArray[i] && video.currentTime < endTimeArray[i]) { video.currentTime = endTimeArray[i]; } } }); // カット開始時間変更ボタンを押した時 let changeStartTime = document.getElementById("changeStartTime"); let startTimeNum = document.getElementById("startTime"); changeStartTime.addEventListener('click', function () { startTimeNum.value = Number(Math.round(video.currentTime * 100) / 100); }); // カット終了時間変更ボタンを押した時 let changeEndTime = document.getElementById("changeEndTime"); let endTimeNum = document.getElementById("endTime"); changeEndTime.addEventListener('click', function () { endTimeNum.value = Number(Math.round(video.currentTime * 100) / 100); }); function ShowCutArea() { let startTimeID = "startTime"; let endTimeID = "endTime"; let flagID = "cutFlag"; let startPer = [0]; // 0を入れておく let endPer = [0]; // 0を入れておく for (var i=0; i<startClass.length; i++){ startClass[i].setAttribute("id", startTimeID + i); endClass[i].setAttribute("id", endTimeID + i); startTimeArray.push(document.getElementById(startTimeID + i).textContent); endTimeArray.push(document.getElementById(endTimeID + i).textContent); } for (var i=1; i<=startClass.length; i++){ startPer[i] = startTimeArray[i-1] * inverse_duration; endPer[i] = endTimeArray[i-1] * inverse_duration; flagName[i-1].className = flagID + i; cutBar[i] = document.getElementsByClassName(flagID + i); cutBar[i][0].style.marginLeft = (startPer[i] - endPer[i-1]) + '%'; cutBar[i][0].style.width = (endPer[i] - startPer[i]) + '%'; cutBar[i][0].style.backgroundColor = "#000000"; cutBar[i][0].style.height = 10 + "px"; cutBar[i][0].style.cssFloat = "left"; } } 4. 最後に 以上でブラウザ上で動画のカット編集ができるようになりました。 まだまだ不十分な部分があるので、参考にしながら改良していただければと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JS]mutableとimmutableでの配列操作について

はじめに 自身のおさらいも含め、初学者の方に向けて、JavaScriptでの配列操作についてまとめていきたいと思います。 [対象読者] JavaScript / React初学者 [記事のテーマ] mutable / immutableとは? フロントエンド開発における、非破壊的な配列操作について Reactでアプリケーションを作っていく際は、副作用のない純粋な関数を使用することが推奨されます。純粋な関数であるためには、以下の条件が必要です。 ・最低1つの引数を持つ ・1つの値、もしくは1つの関数を返す ・引数を変化させてはいけない (immutable) ← 今回の記事のテーマ mutable, immutableとは? JSの配列操作等のキーワードで検索をかけると、mutable(破壊的なメソッド)とimmutable(非破壊的なメソッド)というキーワードを良く目にするかと思いますが、破壊的なメソッドとは、対象となる元の配列の値を変えてしまうメソッドのことを指します。 以下に破壊的なメソッドと非破壊的なメソッドの違いを示します。 破壊的なメソッドは元の配列を直接操作(=破壊)する 破壊的なメソッド const nums = [1,2,3,4,5]; // 〜何らかの処理〜 nums.push(6); // 〜何らかの処理〜 console.log(nums); // 読み手は[1,2,3,4,5]を期待する 非破壊的なメソッドは、元の配列のコピーに対して配列操作する 非破壊的なメソッド const nums = [1,2,3,4,5]; // 〜何らかの処理〜 const newNums = nums.concat(6); // 〜何らかの処理〜 console.log(nums); // [1,2,3,4,5] 読み手の期待通り console.log(newNums) // [1,2,3,4,5,6] こちらも読み手の期待通り 上記のように、破壊的なメソッドを使用して配列操作すると、不要な混乱を招くことになります。JavaScriptには、いくつか破壊的なメソッドが用意されており、以下メソッドを使う時は、注意が必要です。 破壊的なメソッド splice() push() pop() shift() unshift() sort() reverse() fill() copyWithin() 破壊的な配列操作をする際も、以下のように、元の配列を変えてしまわないようにスプレッド演算子でコピーを作って操作できないか検討してみましょう。以下はsort()を使用する際に、スプレッド演算子を使用して、破壊的な操作を回避した例です。 スプレッド演算子を使用して非破壊的な操作を const articleIds = [3,5,1,4,2]; const sortedArticleIds = [...articleIds].sort(); console.log(sortedArticleIds); // [1,2,3,4,5] ユースケース別の実装例 1.配列を加工したい ES6には複数の便利メソッドが用意されてますが、ここではよく利用されるであろうmap()を紹介します。 まずは破壊的な操作 破壊的な配列加工 let nums = [1, 2, 3]; for (var i = 0; i < nums.length; i++) { nums[i] = nums[i] * 2; } console.log(nums); // [ 2, 4, 6 ] 上記を非破壊的な操作に書き換えると以下になります。 mapを使用して非破壊的な配列加工をする const nums = [1, 2, 3]; const doubled = nums.map((num) => { return num * 2; }); console.log(nums); // [ 1, 2, 3 ] console.log(doubled); // [ 2, 4, 6 ] 2.要素を追加したい push() unshift() まずは、破壊的なメソッド。 要素を追加したい:破壊的[push、unshift] // push() const nums_push = [1,2,3,4,5]; nums_push.push(6); console.log(nums_push); // [1,2,3,4,5,6] // unshift() const nums_unshift = [1,2,3,4,5]; nums_unshift.unshift(0); console.log(nums_unshift); // [0,1,2,3,4,5] 非破壊的な操作に書き直します。 要素を追加したい:非破壊的 // popを非破壊的に書き直す const nums = [1,2,3,4,5]; const newNums = [...nums, 6]; console.log(nums); // 1,2,3,4,5 console.log(newNume); // 1,2,3,4,5,6 // unshiftを非破壊的に書き直す const nums = [1,2,3,4,5]; const newNums = [0, ...nums]; console.log(nums); // 1,2,3,4,5 console.log(newNume); // 0,1,2,3,4,5 3.要素を削除したい pop() shift() まずは、破壊的なメソッド。 要素を追加したい:破壊的[pop、shift] // pop() const nums_pop = [1,2,3,4,5]; nums_pop(); console.log(nums_pop); // [1,2,3,4] // shift() const nums_shift = [1,2,3,4,5]; nums_unshift.shift(); console.log(nums_unshift); // [2,3,4,5] 非破壊的な操作に書き直します。 要素を追加したい:非破壊的 const nums = [1,2,3,4,5]; const newNums = nums.filter(num => num !== 3) console.log(newNums) // 1,2,4,5 最後に 以上、駆け足ではありましたが、mutable / immutableな配列操作の違いの解説でした。相互レビューをする際に、不要な破壊的操作を行っているコードは指摘されるポイントになるので、非破壊的な操作は意識しながら開発するのが良いと思います。何故immutableである方が望ましいのかは別記事でもう少し掘り下げようかと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gatsby で雑にマークダウン形式ブログを作る

「そうだ、自作ブログを作ろう。」 「どうせなら Qiita の記事をそのままブログに載せられるようにしたい。」 「もっというと、マークダウン形式のファイルをそのままブログに突っ込めるようにしたい。」 「でも面倒なことはしたくない。サーバーすら立てたくない」 こんなわがままでもブログが作れてしまいます。 そう、Gatsby ならね。 前準備 npm がインストールされている前提で進めます。 まずは Gatsby のプロジェクトを作成しましょう 公式のチュートリアルを見つつ、コマンドをぽちぽち叩いていきましょう。 npm install -g gatsby-cli gatsby new {任意のプロジェクト名} これで完了です。早い。 生成されたディレクトリに移動し、npm run developを実行することで Gatsby のデフォルトページが閲覧できます。 TypeScript を使いたい場合はsrc/pagesディレクトリ中のjsファイルの拡張子をtsxに変えましょう。もともと TypeScript での開発も想定されているようで、これだけで機能します。すごい。 マークダウンファイルのパース 下準備はできたので、早速本題に入りましょう。 マークダウンをパースするためにはgatsby-transformer-remarkを使います。 npm install gatsby-transformer-remarkでインストールし、gatsby-config.jsに設定を加えましょう。 gatsby-config.js plugins: [ `gatsby-transformer-remark`, ], プラグイン項目に記述を追加するだけで動作してくれます。 次に、実際に読み込みたいマークダウンファイルを追加します。 プロジェクトのルートディレクトリ配下ならどこでもいいのですが、今回はsrc/markdown配下に追加することとします。 適当に記述したマークダウンファイルを配置できたら、再びgatsby-config.jsに記述を追加しましょう。 gatsby-config.js plugins: [ { resolve: `gatsby-source-filesystem`, options: { name: `markdown`, path: `${__dirname}/src/markdown`, }, }, ] こちらの設定はマークダウンファイルを Gatsby のエコシステムで認識できるようにするためのものです。 ここまで設定できたら、一度ローカル環境で起動し、http://localhost:8000/___graphqlにアクセスしてみましょう。 Gatsby では GraphQL を採用しており、様々な情報を GraphQL 経由で取得することができます。 今回追加したマークダウンファイルについても同様で、上記の設定を追加することで GraphQL から取得できようになっています。 下記のクエリを投げてみましょう。 query MyQuery { allMarkdownRemark { nodes { html } } } 問題がなければ、先ほど追加したマークダウンファイルが HTML 形式で取得できているはずです。 Gatsby への追加 では、上記で取得できた HTML を Gatsby に追加していきましょう。 先ほど行った修正により、マークダウンファイルを HTML 形式で GraphQL 経由で取得することができるようになっています。 Gatsby ではこの GraphQL を利用することができます。 index.js import * as React from "react" import { graphql } from "gatsby" import Layout from "../components/layout" export const query = graphql` query MyQuery { allMarkdownRemark { nodes { id html } } } ` const IndexPage = ({data}) => ( <Layout> <div dangerouslySetInnerHTML={{__html: data.allMarkdownRemark.nodes[0].html}}></div> </Layout> ) export default IndexPage GraphQL で取得したデータは、引数にdataを指定することで読み込むことができます。簡単ですね。 HTML 形式で取得しているので、あとはそのまま出力するように突っ込むだけです。 今回利用しているdangerouslySetInnerHTMLは React で提供されている属性です。 わざわざdangerouslyとしているのは XSS の危険性があるためです。詳しいことは公式のドキュメントを参照してください。 あとはnpm run developなどで結果を確認するだけです。 うまく設定できていれば、マークダウンファイルの中身がちゃんと表示されているはずです。 おわりに というわけで、Gatsby なら簡単にマークダウン形式のブログが作れるよ、という記事でした。 私は上記に加え Github Pages を利用することで、簡単な俺俺ブログにしています。 厳密には Qiita では改行コードがそのまま改行として扱われますが、今回作成したものではちゃんとスペースを2つ並べなければ改行されません。 とはいえあとはほぼそのままで動きますし、改行させるだけなら大した手間ではないでしょう。 Gatsby は便利だなぁ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Chart.js 3系と2系のオプションの違い

Chart.jsは2系と3系で大きくオプションが変わっているそうで、昔の記事からコードをコピペすると全然上手く動きません。 ということで、Chart.js3系を使ってみて2系からの違いをまとめていこうと思います! 基本は以下のドキュメントに記載されています。 https://www.chartjs.org/docs/latest/getting-started/v3-migration.html fontSize 2系 fontSize:20 3系 font: { size: 20 } title 2系 title: { display: true, text: "Chart Title", } 3系 いくつかのオプションはpluginsの中に入れるようになりました。 plugins: { title: { display: true, text: "Chart Title", } } legend 凡例 2系 legend: { display: false } 3系 いくつかのオプションはpluginsの中に入れるようになりました。 plugins: { legend: { display: false } } scale 2系 scale :{ ticks: { beginAtZero: true, stepSize: 50, fontSize: 20, }, } 3系 scaleという設定値が削除され、scales[r]の形式で表されるようになったそうです。 scales: { r: { ticks: { beginAtZero: true, stepSize: 50, font: { size: 20, }, }, },
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む