- 投稿日:2020-11-16T23:26:17+09:00
WebView2 C#とJavaScriptの連携
昨日の今日ではあるんですが、WebView2を使ってJavaScriptとC#を連携させる方法がわかったので、記事に残します。
(筆者の個人的な事情なんですが、Qiitaの記事作成が今日で三日連続です(笑)
今日は仕事終わりなので、サクッと記事を書いて終わりたい…(笑))コーディング
とりあえず、サンプルのコードを下記に貼り付けます。
Form1.cs と JavaScriptを仕込んだ sample.htmlだけなので、察しの良い人ならサンプルのコードを見ただけで十分かもしれません。
(.htmlに関しては適当に書いたので、あまり深く突っ込まないでください(笑))Form1.csusing Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.WinForms; using System; using System.Runtime.InteropServices; using System.Windows.Forms; namespace SampleWebView2Form { public partial class Form1 : Form { /// <summary>webviewのコントロール(わかりやすい様に、デザイナーを使わずにコード側で実装します。)</summary> private WebView2 WebView = new WebView2 { //個人の環境に合わせて下さい Source = new Uri("file:///C:/Users/name/Desktop/sample.html"), }; /// <summary>JavaScriptで呼ぶ関数を保持するオブジェクト</summary> private JsToCs CsClass = new JsToCs(); public Form1() { this.Controls.Add(WebView); InitializeComponent(); //WebView2のサイズをフォームのサイズに合わせる WebView.Size = this.Size; this.SizeChanged += Form1_SizeChanged; //WebView2のロード完了時のイベント WebView.NavigationCompleted += WebView_NavigationCompleted; } /// <summary>WebView2のロード完了時</summary> private void WebView_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e) { try { if (WebView.CoreWebView2 != null) { //JavaScriptからC#のメソッドが実行できる様に仕込む WebView.CoreWebView2.AddHostObjectToScript("class", CsClass); //JavaScriptの関数を実行 CsToJs(); } else MessageBox.Show("CoreWebView2==null"); } catch(Exception ex) { MessageBox.Show(ex.ToString()); } } /// <summary>Jsのメソッドを実行</summary> private async void CsToJs() { //WebView.ExecuteScriptAsync("func1()").ResultをするとWebView2がフリーズする string str1 = await WebView.ExecuteScriptAsync("func1(\"C#からの呼び出し\")"); MessageBox.Show("Jsからの戻り値>" + str1); } /// <summary>サイズ変更時のイベントでWebView2のサイズをフォームに合わせる</summary> private void Form1_SizeChanged(object sender, EventArgs e) { WebView.Size = this.Size; } } //↓属性設定が無いとエラーになります /// <summary>WebView2に読み込ませるためのJsで実行する関数を保持させたクラス</summary> [ClassInterface(ClassInterfaceType.AutoDual)] [ComVisible(true)] public class JsToCs { public void MessageShow(string strText) { MessageBox.Show("Jsからの呼び出し>" + strText); } } }sample.html<!DOCTYPE html> <html lang="ja"> <head> <meta content="text/html;charset=utf-8" http-equiv="Content-Type" /> <script language="javascript" type="text/javascript"> //C#から呼び出すための関数 function func1(str1) { alert("C# called>" + str1); return "success" } function ButtonClick() { //C#の関数の実行 chrome.webview.hostObjects.class.MessageShow("Js send text"); } </script> </head> <body> <h1>wasm_sample</h1> <input type="button" value='send Message' onclick="ButtonClick();"/> <script></script> </body> </html>解説
C#->JavaScript
C#からJavaScript内の関数を呼び出すには、ExecuteScriptAsync()
を使います。この関数は、先日書いた記事でExecuteScriptAsync("alert(\"message\")");
的な使い方をしたのですが、引数の中身がJavaScriptとして処理できるのであれば、JavaScriptのコード内の独自で作成した関数でも実行できます。
注意点として、ExecuteScriptAsync()
は Task なので、戻り値を取得する場合は.Result
かawait
を使用することになるのですが、.Result
を使用するとフリーズして処理が進まなくなるため、戻り値を取得するならawait
を使用する必要があります。
サンプルコードでは、下記の部分が該当の箇所です。Form1.cs/// <summary>Jsのメソッドを実行</summary> private async void CsToJs() { //WebView.ExecuteScriptAsync("func1()").ResultをするとWebView2がフリーズする string str1 = await WebView.ExecuteScriptAsync("func1(\"C#からの呼び出し\")"); MessageBox.Show("Jsからの戻り値>" + str1); }sample.html<script language="javascript" type="text/javascript"> //C#から呼び出すための関数 function func1(str1) { alert("C# called>" + str1); return "success" } </script>挙動としては下記画像の様になります。
・ロード直後にJavaScriptのアラート出力
・アラートを閉じると、メッセージボックス出力
JavaScript->C#
JavaScriptからC#の呼び出しには、AddHostObjectToScript()
を使ってC#内で作成した関数をJavaScriptに読み込ませます。
(WebMessageReceived
を使う方法もあるらしいので、気になる方は調べてみてください)
サンプルコードでは、下記の部分が該当の箇所です。Form1.cs/// <summary>JavaScriptで呼ぶ関数を保持するオブジェクト</summary> JsToCs CsClass = new JsToCs(); //JavaScriptからC#のメソッドが実行できる様に仕込む WebView.CoreWebView2.AddHostObjectToScript("class", CsClass); //~~~一部省略~~~ /// <summary>WebView2に読み込ませるためのJsで実行する関数を保持させたクラス</summary> [ClassInterface(ClassInterfaceType.AutoDual)] [ComVisible(true)] public class JsToCs { public void MessageShow(string strText) { MessageBox.Show("Jsからの呼び出し>" + strText); } }sample.html<script language="javascript" type="text/javascript"> function ButtonClick() { //C#の関数の実行 chrome.webview.hostObjects.class.MessageShow("Js send text"); } </script> <body> <input type="button" value='send Message' onclick="ButtonClick();"/> </body>挙動としては、ブラウザー内の「send Message」を押下してもらうと、C#のメッセージが出力される様になっています。
まとめ
今回はこんなところです。
WebView2に関しての記事は昨日書いたのですが、JavaScriptからC#を実行する処理に関しては直近一週間くらい、ずっと放置だったので、その問題が解消して良かったです。今日は特に書くことないですね(別に無くても良いんですが(笑))
昨日書いた記事のリンクを一応貼り付けときます。
https://qiita.com/NagaJun/items/4925a63ce7b93b80639e
最後まで読んで頂き、ありがとうございました。
- 投稿日:2020-11-16T22:21:38+09:00
Vue.jsでObnizを操作する基本-単純Lチカ(メモ)
Vue.jsからのObniz操作でエラーになったのでメモ。Webアプリでボタン押下でLEDを点灯。
Consoleエラー
原因
- "obniz.onconnect"内で処理が必要? ※Vue.jsでは良くないらしい
デバイスへの接続 - obniz公式var obniz = new Obniz('1234-5678'); obniz.onconnect = async function() { // **** 実行する処理 **** }
- Vue.js での書き方が悪かった ※Vue.jsでは以下のほうが良さそう
Vue.jsでobnizをつかおう - Qiita
if (obniz.connectionState === "connected") { // **** 実行する処理 **** } else { obniz.on('connect', () => { // **** 実行する処理 **** })
- コールバック関数、アロー関数のメモ
JavaScriptの「コールバック関数」とは一体なんなのか
JavaScript アロー関数を説明するよ - Qiita
書き方で変わる?! Vue.jsでthisのスコープを調査した修正後のコード
.html<!DOCTYPE html> <html lang="jp" > <head> <meta charset="UTF-8"> <title>Obniz Test</title> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <!-- 全体をVue.js有効にする --> <div id="app"> <!-- タイトル --> <h1>Obniz Test</h1> <!-- 設定 --> <h5>Obniz ID</h5> <input v-model:value="ObnizID[0]" type="text" maxlength="4"> <label>-</label> <input v-model:value="ObnizID[1]" type="text" maxlength="4"> <!-- LED-ON-OFF --> <button v-on:click="PowerON">LED-ON</button> <button v-on:click="PowerOFF">LED-OFF</button> </div> <!-- CDN --> <script src='https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js'></script> <script src='https://unpkg.com/obniz@3.9.0/obniz.js'></script> <!-- 実行script --> <script src="./script.js"></script> </body> </html>script.jslet obniz; // Obniz関数 // Obniz呼び出し関数 const connect = function(func, ob){ console.log(ob.connectionState); // Obnizへの接続を確認 if (ob.connectionState === "connected") { func(); } else { ob.on('connect', () => { func(); }) } } const app = new Vue({ el: '#app', // Vueが管理する一番外側のDOM要素 data: { // Vue内部で利用する変数定義 ObnizID: ['0000', '0000'], }, methods: { // 関数はココに記述 PowerON: function() { // LED ON // Obniz ID 指定 let obnizid = `${this.ObnizID[0]}-${this.ObnizID[1]}`; this.obniz = new Obniz(obnizid); console.log(obnizid); let me = this; // thisを関数内で使えないので変数に代入 // connect関数を呼んで、connect関数内で以下のFunctionを実行 connect(async function() { const led = me.obniz.wired('LED', { anode: 0, cathode: 1 }); me.obniz.display.clear(); me.obniz.display.print('ON'); led.on(); // LED点灯 }, this.obniz); }, PowerOFF: function() { // LED OFF // Obniz ID 指定 let obnizid = `${this.ObnizID[0]}-${this.ObnizID[1]}`; this.obniz = new Obniz(obnizid); console.log(obnizid); let me = this; // thisを関数内で使えないので変数に代入 // connect関数を呼んで、connect関数内で以下のFunctionを実行 connect(async function() { const led = me.obniz.wired('LED', { anode: 0, cathode: 1 }); me.obniz.display.clear(); me.obniz.display.print('OFF'); led.off(); }, this.obniz); }, }, });
- 投稿日:2020-11-16T20:33:56+09:00
Google Apps Scriptで画像データからビットマップを作成する
Google Apps Script(GAS)でPNGファイル自動生成したいと思ったところ、当然ながら<canvas>が無いので、Javascriptのようには行かないことがわかりました。
しかし、ビットマップのBlobを作ればBlob.getAs(mineTipe)メソッドでPNGに変換することは出来ます。というわけで、画像データからビットマップのBlobを生成する関数を作りました。
ソースコード
24bitフルカラー固定です。
/* dataは左上から右下に向かってのピクセルの色情報を持つUint8Array。色はRGBAの4バイト。 width, heightは横縦のピクセル数。 */ function toBMP( data, width, height ) { const RGBA_BYTES = 4; const BMP_BYTES_PER_DOT = 4; const BYTES_PER_DOT = 4; if ( data == null || width == null || height == null ) { throw new Error("引数が不足しています。"); } data = Uint8ClampedArray.from( data.map( v => ( v >>> 0 ) & 0xff) ); if ( data.length != ( height * width * RGBA_BYTES ) ) { throw new Error("データの長さが画像サイズと一致しません。"); } const rowCount = height; const rowByteCount = width * RGBA_BYTES; const paddingCount = ( BMP_BYTES_PER_DOT - ( rowByteCount % BMP_BYTES_PER_DOT ) ) % BMP_BYTES_PER_DOT; const imageDataSize = paddingCount == 0 ? data.length : ( rowByteCount + paddingCount ) * rowCount; const imageData = new Uint8ClampedArray( imageDataSize ); const paletteData = new Uint8ClampedArray( 0 ); //フルカラーなら要らない for ( let rowIndex = rowCount -1, j = 0; rowIndex >= 0; rowIndex-- ) { const offset = rowIndex * rowByteCount; for ( let i = offset, end = rowByteCount + offset; i < end; ) { const [ red, green, blue, alpha ] = data.slice( i, i += BYTES_PER_DOT ); for (const ori of [ blue, green, red ]) { const color = (ori * alpha / 0xff) | 0; imageData[ j++ ] = color; } } for ( let i = 0; i < paddingCount; i++ ) { imageData[ j++ ] = 0; } } const paletteDataSize = 0; const informationHeaderSize = 40; const fileHeaderSize = 14; const imageDataOffset = fileHeaderSize + informationHeaderSize + paletteDataSize; const fileSize = fileHeaderSize + informationHeaderSize + paletteDataSize + imageDataSize; const bmpData = new Uint8Array( fileSize ); let cursor = 0; // Bitmap File Header // for ( const bfType of "BM".split("").map( ch => ch.charCodeAt( 0 ) ) ) { bmpData[ cursor++ ] = bfType; } for ( const bfSize of UnsignedLong( fileSize ) ) { bmpData[ cursor++ ] = bfSize; } for ( const bfReserved1 of UnsignedInt( 0 ) ) { bmpData[ cursor++ ] = bfReserved1; } for ( const bfReserved2 of UnsignedInt( 0 ) ) { bmpData[ cursor++ ] = bfReserved2; } for ( const bfOffBits of UnsignedLong( imageDataOffset ) ) { bmpData[ cursor++ ] = bfOffBits; } // Bitmap Information Header // for ( const biSize of UnsignedLong( informationHeaderSize ) ) { bmpData[ cursor++ ] = biSize; } for ( const biWidth of Long( width ) ) { bmpData[ cursor++ ] = biWidth; } for ( const biHeight of Long( height ) ) { bmpData[ cursor++ ] = biHeight; } for ( const biPlanes of UnsignedInt( 1 ) ) { bmpData[ cursor++ ] = biPlanes; } for ( const biBitCount of UnsignedInt( 24 ) ) { bmpData[ cursor++ ] = biBitCount; } for ( const biCompression of UnsignedLong( 0 ) ) { bmpData[ cursor++ ] = biCompression; } for ( const biSizeImage of UnsignedLong( imageDataSize ) ) { bmpData[ cursor++ ] = biSizeImage; } for ( const biXPixPerMeter of Long( width ) ) { bmpData[ cursor++ ] = biXPixPerMeter; } for ( const biYPixPerMeter of Long( height ) ) { bmpData[ cursor++ ] = biYPixPerMeter; } for ( const biClrUsed of UnsignedLong( 0 ) ) { bmpData[ cursor++ ] = biClrUsed; } for ( const biCirImportant of UnsignedLong( 0 ) ) { bmpData[ cursor++ ] = biCirImportant; } // Palette Data // for ( const palette of paletteData ) { bmpData[ cursor++ ] = palette; } // Image Data // for ( const image of imageData ) { bmpData[ cursor++ ] = image; } const blob = Utilities.newBlob( bmpData, MimeType.BMP ); //Javascriptでは下記の方法でBlobが作れる。 // const blob = new Blob( [ bmpData.buffer ], { type: "image/bmp" } ); return blob; }最後はスプレッド構文にしたら関数呼び過ぎで落ちてしまったので、for文たくさんに置き換えましたけど、もうちょっと上手い書き方がありそうですね。
参考
Bitmapファイルフォーマット
http://www.umekkii.jp/data/computer/file_format/bitmap.cgiBMP ファイルフォーマット
https://www.setsuki.com/hsp/ext/bmp.htm
- 投稿日:2020-11-16T19:37:11+09:00
【JavaScript】基本的なモジュール(ESM)の使い方についてまとめ
モジュール(ESM)とは
・ソースコードを整理、分割する仕組み
・ブラウザ上で動作
・ES6から導入
・import / exportを使用
・IEでは未対応これを使用することにより、別ファイルで定義した変数や関数を扱うことができる
import
モジュールを読み込む
export
モジュールを出力
主な用途
例としてmoduleA.jsとmoduleB.jsのファイルを作成。
moduleA.jsでexport
したモジュールをmoduleB.jsにimport
します。フォルダ構成 |--index.html |--moduleA.js |--moduleB.js
htmlファイルの準備
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body><!-- ↓typeを"module"に。 ↓moduleB.jsを読み込む。 --> <script type="module" src="moduleB.js"></script> </body> </html>
moduleA.jsの準備
適当な変数と関数を定義moduleA.jsconst hoge = 'ほげ'; const fuga = () => { console.log('ふが'); }現状もちろんこれらの変数、関数はmoduleA.js内でしか使用できません。
これをexport
を使いmoduleB.js内で使用できる様に準備します。moduleA.jsexport const hoge = 'ほげ'; export const fuga = () => { console.log('ふが'); }
moduleB.jsの準備
moduleA.jsで定義した
hoge
とfuga
をimport
しています。moduleB.jsimport { hoge, fuga } from './moduleA.js' console.log(hoge); // => ほげ fuga() // => ふができました!
ちなみにこうすることで
import
した変数関数の名前を変更できます。moduleB.jsimport { hoge as h, fuga as f} from './moduleA.js' console.log(h); // => ほげ f() // => ふが
オブジェクトとして格納することも可能。
moduleB.jsimport *as moduleA from './moduleA.js' console.log(moduleA.hoge); // => ほげ; moduleA.fuga(); // => ふが;デフォルトエクスポート
さっきまでのは名前付きエクスポートという機能でした。
デフォルトエクスポートもやってみます。moduleA.js及びmoduleB.jsの記述を変更します。
moduleA.jsexport default 'ほげ';moduleB.jsimport hoge from './moduleA.js' console.log(hoge); // => ほげデフォルトエクスポートはモジュールごとにひとつしか作れません。
- 投稿日:2020-11-16T19:32:54+09:00
Javascriptの配列操作
Javascriptの配列まわりの関数や構文を抜粋してみました。
スプレッド構文
配列(正確にはIterableな値全般)を展開します。
単に展開したいときはもちろん、配列のコピーを作れるため配列を直接変更するメソッドも配列を変更せずに利用できます。
配列を結合するArray.prototype.concatの代わりにもなります。const newarray = [value1, ...array, value2]; const copyarray = [...array];Array
コンストラクタです。特定の長さの配列が作れます。
スプレッド構文かArray.prototype.fillなどで初期化しないと、Array.prototype.mapが動かないので注意が必要です。const newarray = Array(8);Array.prototype.fill
全要素を指定した値で埋めます。直接変更されます。
開始位置と終了位置は省略可能で、省略して場合は先頭から末尾までです。const newarray = [...array].fill( value, start, end );Array.prototype.reverse
配列の順序を反転させます。直接変更されます。
const reversed = [...array].reverse();Array.prototype.find / Array.prototype.findIndex
条件を満たす要素、あるいはその要素のindexを返します。
どちらも先頭から走査して、最初に条件を満たした要素について処理します。const value = array.find( (element, index, array) => boolean ); const index = array.findIndex( (element, index, array) => boolean );Array.prototype.filter
findに似ています。条件を満たす全ての要素からなる新しい配列を返します。
const newarray = array.filter( (element, index, array) => boolean );Array.prototype.join
String.prototype.splitと対になるようなメソッドです。
全要素を連結した1つの文字列にします。
区切り文字が指定でき、省略した場合は「,」(カンマ)になります。
空文字を指定することで区切り文字無しで連結することができます。const text = array.join(separator);String.prototype.split
Array.prototype.joinと対になるようなメソッドです。
区切り文字を省略した場合、または文字列中に区切り文字が見つからなかった場合は分割せずに1要素の配列を返します。
空文字を指定した場合は1文字ずつに分割されますが、サロゲートペアも分割してしまうので、絵文字などが含まれる場合は注意が必要です。
また、分割限界数を指定することができます。省略した場合は無制限です。const array = string.split(separator, limit);Array.prototype.map
全要素を変換して新しい配列を返します。
const newarray = array.map( (element, index, array) => value );Array.prototype.reduce
全要素を変換して新しい値を返します
他のメソッドの実質的な上位互換で、なんでもできます。const newvalue = array.reduce( (accumulator, element, index, array) => newaccumulator, initialvalue );おわりに
今回の投稿はreduce()の用途がわからなかったので、他のメソッドを調べて比較したのがきっかけです。
自分がよく使うものを中心にまとめたので、網羅はできてないかもしれませんが、みなさんの助けになれば幸いです。参考
Array - JavaScript | MDN
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array困ったらね、MDN見たらいいんですよ。
- 投稿日:2020-11-16T14:37:20+09:00
【入門者向け】Canvas入門講座#10 円をボタンを押して動かそう【JavaScript】
問題10
中心が(150, 150)、半径50の円を塗りつぶしなさい。
塗りつぶす色はマゼンタ色(#ff00ff)であること。
rightボタン押下時に円がX方向へ-30,leftボタン押下時に円がX方向へ+30移動すること。
なお、以下のHTMLを使うこと。<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>問題10</title> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script> $(() => { // ここにプログラムを書く }); </script> </head> <body> <canvas id="my-canvas" width="500" height="300"></canvas> <br> <input id="left-button" type="button" value="right" /> <input id="right-button" type="button" value="left" /> </body> </html>答え
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>問題10</title> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script> $(() => { let moveX = 0; // 初期表示 drawCircle(moveX); $('#left-button, #right-button').on('click', e => { if($(e.target).prop('id') === 'left-button') {// left-button がクリックされた moveX -= 30; } else {// right-button がクリックされた moveX += 30; } drawCircle(moveX); }); function drawCircle(moveX) { // コンテキストを取得 const ctx = $('#my-canvas')[0].getContext('2d'); // canvasをクリア ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // 塗りつぶしの色をマゼンタ色にする ctx.fillStyle = '#ff00ff'; ctx.beginPath(); // 現在のパスをリセットする ctx.arc(150 + moveX, 150, 50, 0, Math.PI*2, true); // 円を描画する ctx.closePath(); // パスを閉じる ctx.fill(); // 現在のパスを塗りつぶす } }); </script> </head> <body> <canvas id="my-canvas" width="500" height="300"></canvas> <br> <input id="left-button" type="button" value="left" /> <input id="right-button" type="button" value="right" /> </body> </html>解説
円がどれだけ動いたか変数で保持します。
let moveX = 0;まず円を描画します。今回は円の描画を関数にしてあります。
// 初期表示 drawCircle(moveX);ボタンクリックをまとめてハンドリングします。
left-button がクリックされたら、moveXを-30します。
right-button がクリックされたら、moveXを+30します。$('#left-button, #right-button').on('click', e => { if($(e.target).prop('id') === 'left-button') {// left-button がクリックされた moveX -= 30; } else {// right-button がクリックされた moveX += 30; } drawCircle(moveX); });drawCircleは円を描画する関数です。
引数はmoveXでX方向にどれだけ移動したかを受け取ります。canvasへ描画する前にclearRectメソッドでcanvasをクリアしています。
これはクリアしないと1つ前の円の描画そのまま残るのを防ぐためです。
clearRectの代わりにfillRectによる塗りつぶしでも構いません。一般的にcanvasに何かを描画するときは、canvasをクリアした後に、canvasへ描画します。
function drawCircle(moveX) { // コンテキストを取得 const ctx = $('#my-canvas')[0].getContext('2d'); // canvasをクリア ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // 塗りつぶしの色をマゼンタ色にする ctx.fillStyle = '#ff00ff'; ctx.beginPath(); // 現在のパスをリセットする ctx.arc(150 + moveX, 150, 50, 0, Math.PI*2, true); // 円を描画する ctx.closePath(); // パスを閉じる ctx.fill(); // 現在のパスを塗りつぶす }補足
setTransformを使うと変数を描画処理で参照しなくてよいのですっきりします。
今回の問題は描画が単純ですので、このような工夫は必要ありませんが
setTransformは知っててほしいメソッドです。setTransformは行列を掛けるメソッドです。
平行移動する場合はctx.setTranform(1, 0, 0, 0, x, y);のように呼び出しましょう。今回のCanvas入門講座ではオブジェクトの変換は平行移動しかしないので、
setTransformの代わりにtranslateでもよいです。setTranformについて詳しく知りたい方はこちらの記事をお読みください。
以下、drawCircleをsetTransformを使って書き直しました。
function drawCircle(moveX) { // コンテキストを取得 const ctx = $('#my-canvas')[0].getContext('2d'); // canvasをクリア ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // 塗りつぶしの色をマゼンタ色にする ctx.fillStyle = '#ff00ff'; ctx.setTransform(1, 0, 0, 1, moveX, 0); ctx.beginPath(); // 現在のパスをリセットする ctx.arc(150, 150, 50, 0, Math.PI*2, true); // 円を描画する ctx.closePath(); // パスを閉じる ctx.fill(); // 現在のパスを塗りつぶす }
- 投稿日:2020-11-16T13:36:36+09:00
【入門者向け】Canvas入門講座#9 画像を描画しよう【JavaScript】
問題9
画像が読み込まれたときに、その画像をアスペクト比を保ったまま
画像全体が見えるようにcanvasに貼り付けなさい。
画像の左上隅はcanvasの左上隅に一致させること。
画像が貼り付けられていない箇所は黒色(#000000)で塗りつぶすこと。
読み込んだファイルのチェックは行わないものとする。
以下のHTMLを使うこと。<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>問題9</title> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script> $(() => { $('#my-file').on('change', e => { const img = new Image(); $(img).on('load', e => { // ここにプログラムを書く }); img.src = URL.createObjectURL(e.target.files[0]); }); }); </script> </head> <body> <canvas id="my-canvas" width="500" height="300"></canvas> <br> <input id="my-file" type="file" /> </body> </html>補足
canvasは幅が500,高さが300で固定です。
画像は幅、高さがいくつのものが来るかわかりません。
canvasが画像より横長、縦長の場合を考慮する必要があります。答え
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>問題9</title> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script> $(() => { $('#my-file').on('change', e => { const img = new Image(); $(img).on('load', e => { // コンテキストを取得 const ctx = $('#my-canvas')[0].getContext('2d'); // canvasを黒色で塗りつぶす ctx.fillStyle = '#000000'; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); // canvasと画像のアスペクト比を求める const canvasWidth = $('#my-canvas').prop('width'), canvasHeight = $('#my-canvas').prop('height'); canvasAspect = canvasWidth / canvasHeight; // canvasのアスペクト比 imgAspect = img.width / img.height; // imgのアスペクト比 // canvasと画像のアスペクト比を比較し、貼り付ける領域を決定する let dstWidth, dstHeight; if(canvasAspect > imgAspect) {// canvasの方が横長 dstHeight = canvasHeight; dstWidth = dstHeight * imgAspect; } else {// canvasの方が縦長 dstWidth = canvasWidth; dstHeight = dstWidth / imgAspect; } // 画像を貼り付ける ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, dstWidth, dstHeight); }); img.src = URL.createObjectURL(e.target.files[0]); }); }); </script> </head> <body> <canvas id="my-canvas" width="500" height="300"></canvas> <br> <input id="my-file" type="file" /> </body> </html>画像が縦長の場合、横長の場合でもうまく表示できました!
解説
まずはファイルが読み込まれたイベントをハンドリングします。
changeイベントをハンドリングします。$('#my-file').on('change', e => { // ここにファイルが読み込まれた処理を書く });次に画像をロードする必要があります。
Imageを作成し、srcに画像のurlを指定します。
読み込みが完了したら、loadイベントが発生するので、それをハンドリングします。const img = new Image(); $(img).on('load', e => { // ここに画像読み込み完了後の処理を書く }); img.src = URL.createObjectURL(e.target.files[0]);さあ、画像もロードできたのでcanvasに描画しましょう。
いつもどおり、コンテキストを取得します。// コンテキストを取得 const ctx = $('#my-canvas')[0].getContext('2d');そして、canvasを黒色で塗りつぶしましょう。
// canvasを黒色で塗りつぶす ctx.fillStyle = '#000000'; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);canvasと画像のアスペクト比を求めましょう。
// canvasと画像のアスペクト比を求める const canvasWidth = $('#my-canvas').prop('width'), canvasHeight = $('#my-canvas').prop('height'); canvasAspect = canvasWidth / canvasHeight; // canvasのアスペクト比 imgAspect = img.width / img.height; // imgのアスペクト比canvasに貼り付ける幅と高さを計算しましょう。
canvasが縦長か横長かで場合分けすればうまくいきそうです。canvasが画像より横長の場合、
貼り付ける高さはcanvasの高さを採用します。
貼り付ける幅は貼り付ける高さに画像のアスペクト比を掛けることで求まります。canvasが画像より縦長の場合、
貼り付ける幅はcanvasの幅を採用します。
貼り付ける高さは貼り付ける幅を画像のアスペクト比で割ることで求まります。// canvasと画像のアスペクト比を比較し、貼り付ける領域を決定する let dstWidth, dstHeight; if(canvasAspect > imgAspect) {// canvasの方が横長 dstHeight = canvasHeight; dstWidth = dstHeight * imgAspect; } else {// canvasの方が縦長 dstWidth = canvasWidth; dstHeight = dstWidth / imgAspect; }あとはcanvasに画像を貼り付けましょう。
drawImageメソッドで貼り付けます。このメソッドは超超重要です。
ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
image: 画像オブジェクト or canvasオブジェクト or 動画オブジェクト
sx, sy, sw, sh: 貼り付け元の左上隅座標とサイズ
dx, dy, dw, dh: 貼り付け先の左上隅座標とサイズ今回画像全体を表示するので
sx, sy, sw, shは0, 0, img.width, img.heightです。
画像の左上隅はcanvasの左上隅に一致するので
dx, dy, dw, dhは0, 0, dstWidth, dstHeightです。// 画像を貼り付ける ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, dstWidth, dstHeight);最後に
今回の問題はややこしく感じたかもしれませんが、この程度の計算はできるようにしておきましょう。
canvasを扱う時に計算はつきものです。
余裕のあった人は、canvasの中央に画像を貼り付けてみましょう。
これについては答えは示しませんが、以前記事を書いたことがあるのでそれが参考になるかもしれません。
- 投稿日:2020-11-16T13:00:36+09:00
three.jsでcanvasを使ってテキストを表示させる方法
three.jsを使ってテキストを表示させる方法はいくつかあると思います。
TextGeometry
を使う- Bitmap Fontsを使う
- canvasにテキストを描画してテクスチャとして使う
大きくこの3つの方法があるのかと思いますが今回はcanvasにテキストを描画してテクスチャとして使う方法を紹介していきます。
デモはこちら
ソースコードはこちら
(本編の記事と内容が若干異なります)テキストを表示させる大まかな流れ
- three.jsを描画するcanvasとは別にテキストを描画するcanvasを用意
- canvasにテキストを描画しテクスチャを生成
- テキストを描画したいメッシュを作成
- 作成したメッシュにテクスチャを送る
ざっとこんな流れ。あと、テクスチャに格納することをよくテクスチャに焼くなんて言います。
まずは下準備。今回は平面を描画するだけなのでパースのかからない
OrthographicCamera
を使います。export default class Canvas { constructor() { this.container = document.getElementById('CanvasContainer'); this.setConfig(); // レンダラを作成 this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: false, }); this.renderer.setSize(this.Config.width, this.Config.height); this.renderer.setPixelRatio(this.Config.dpr); this.container.appendChild(this.renderer.domElement); // 関数をthisでバインドして持っておく this.resizeFunction = this.resize.bind(this); this.updateFunction = this.update.bind(this); // リサイズイベントを設定 window.addEventListener('resize', this.resizeFunction); this.scene = new THREE.Scene(); this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, -1); // レンダリング開始 // 初期化 this.init(); } init() { this.createMesh(); this.start(); } createMesh() { // 後述 } setConfig() { this.Config = { width: window.innerWidth, // Canvasの幅 height: window.innerHeight, // Canvasの高さ cameraZ: 1000, // カメラのz座標 dpr: 1, // device pixel ratio aspectRatio: 1.0, // 画面アスペクト比 }; // 親要素のサイズを取得 const domRect = this.container.getBoundingClientRect(); const width = domRect.width; const height = domRect.height; this.Config.dpr = Math.min(window.devicePixelRatio, 2); this.Config.width = width; this.Config.height = height; this.Config.halfWidth = this.Config.width / 2; this.Config.halfHeight = this.Config.height / 2; this.Config.aspectRatio = this.Config.width / this.Config.height; } resizeScene() { this.renderer.setSize(this.Config.width, this.Config.height); } start() { this.resize(); this.update(); } resize() { this.setConfig(); this.resizeScene(); } update() { // 最大60fpsでレンダリングをループ requestAnimationFrame(this.updateFunction); this.time = performance.now() * 0.001; this.material.uniforms.time.value = this.time; this.renderer.render(this.scene, this.camera); } }canavasにテキストを描画してテクスチャを作る
canvasを使ってテクスチャを作るには
CanvasTexture
を使います。今回はmeasureText
を使って文字数に応じてcanvasの長さを可変にします。テクスチャを作る.js/** * 2D Canvasからテクスチャを作成する * @param {Object} options * @param {stirng} options.text 描画したい文字列 * @param {number} options.fontSize フォントサイズ * @return {Object} テクスチャを返す。 * @memberof Canvas */ createTexture(options) { // Canvas要素を作成 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // measureTextするためいったん設定 const fontFamily = 'monospace'; ctx.font = `bold ${options.fontSize * Config.dpr}px '${fontFamily}'`; const textWidth = ctx.measureText(options.text); // 文字の横幅を取得 // dprに対応したサイズを計算 const width = textWidth.width; const height = options.fontSize * Config.dpr * 0.8; // 文字に合わせて高さを調整。ここの高さは任意で // 幅を指定 canvas.width = width; canvas.height = height; // 中央にテキストを描画 ctx.font = `bold ${options.fontSize * Config.dpr}px '${fontFamily}'`; ctx.textAlign = 'left'; ctx.textBaseline = 'hanging'; ctx.fillStyle = 'rgba(255, 255, 255, 1.0)'; ctx.fillText(options.text, -5, 0); // 文字が途切れないように調整。数値はよしなに // ↓canvasの文字を確認したいとき。テキストを描画したcanvasをbodyに追加しているだけです。 // document.body.appendChild(canvas); // canvas.style.backgroundColor = '#933'; // canvas.style.position = 'relative'; // テクスチャを作成 const texture = new THREE.CanvasTexture(canvas); texture.needsUpdate = false; // ↓ここら辺の設定をしておかないとthree.jsでエラーが出る時がある texture.minFilter = THREE.LinearFilter; texture.magFilter = THREE.LinearFilter; texture.format = THREE.RGBAFormat; return texture; }テキストとcanvasのサイズについて
テキストぴったりのcanvasを作るのがなかなかうまくいかず、微調整をしています。フォントやテキストよって
ctx.fillText(options.text, -5, 0);
ここら辺の調整が必要になってきます。// ↓canvasの文字を確認したいとき。テキストを描画したcanvasをbodyに追加しているだけです。 // document.body.appendChild(canvas); // canvas.style.backgroundColor = '#933'; // canvas.style.position = 'relative';こうやってテキストを描画したcanvasを表示させてcanvasを調整しているのですがもっとうまい方法ないのかなー・・・
メッシュを作る
メッシュを作ります。今回はただテキストを表示させるだけなので、ジオメトリには
PlaneBufferGeometry
を、マテリアルにはRawShaderMaterial
を使用します。three.jsのShaderMaterialについての補足
three.jsのShaderMaterialには
- ShaderMaterial
- RawShaderMaterial
のふたつがあります。
ShaderMaterial
ではthree.jsビルトインのattributes
とuniforms
を自動で付加してくれます。自動で付加されたくない場合はRawShaderMaterial
を使います。ビルトイン
attributes
とuniforms
はこちら。builtInVertexShader.glsl// = object.matrixWorld uniform mat4 modelMatrix; // = camera.matrixWorldInverse * object.matrixWorld uniform mat4 modelViewMatrix; // = camera.projectionMatrix uniform mat4 projectionMatrix; // = camera.matrixWorldInverse uniform mat4 viewMatrix; // = inverse transpose of modelViewMatrix uniform mat3 normalMatrix; // = camera position in world space uniform vec3 cameraPosition; /////////////////////////////////////// // default vertex attributes provided by Geometry and BufferGeometry attribute vec3 position; attribute vec3 normal; attribute vec2 uv;詳しくは↓に書いてあります。
ShaderMaterialあらためてこちらがメッシュを作る部分。
メッシュを作る.jscreateMesh() { const segment = 1; this.geometry = new PlaneBufferGeometry(2, 2, segment, segment); // テクスチャの作成 前述の「テクスチャを作る.js」を参照 this.texture = this.createTexture({ text: 'HOGE', // 描画したいテキスト fontSize: 130, // フォントサイズ }); // マテリアルの作成 this.material = new RawShaderMaterial({ uniforms: { texture: { value: this.texture }, time: { value: 0.0 }, speed: { value: 1.0 }, resolution: { value: Config.aspectRatio }, }, vertexShader: vertexShader, fragmentShader: fragmentShader, transparent: false, }); this.mesh = new Mesh(this.geometry, this.material); this.scene.add(this.mesh); }シェーダー
シェーダーを見ていきます。今回は
fract
関数を使いテキストをタイル状に表示させ左方向に流します。frag.glslprecision mediump float; uniform sampler2D texture; uniform float time; uniform float resolution; uniform float speed; varying vec2 vUv; void main() { float t = time * speed; vec2 repeat = vec2(8.0, 8.0); // (行, 列)回繰り返し vec2 uv = fract(vUv * repeat + vec2(t, 0.0)); vec3 color = texture2D(texture, uv).rgb; gl_FragColor = vec4(color, 1.0); }vertexShaderは特に変わったことはしていません。
vert.glslprecision mediump float; uniform mat4 modelViewMatrix; uniform mat4 projectionMatrix; attribute vec3 position; attribute vec2 uv; varying vec2 vUv; void main() { vUv = uv; vec3 pos = position; gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); }あとは実行するだけです。
main.jsimport Canvas from './Canvas/_index'; document.addEventListener('DOMContentLoaded', () => { new Canvas(); });まとめ
というわけでcanvasを使ってテキストを描画する方法を紹介しました。割と簡単な方法なのですが、スマホだったり高解像度ディスプレイで見るとテキストがかなりギザギザしてしまいます。もっと綺麗に描画したい!となったらBitmap Fontaを使ったほうがいいかもしれません。
↓再掲
デモはこちら
ソースコードはこちら
(本編の記事と内容が若干異なります)
参考
techniques-for-rendering-text-in-threejs
Creating text
CanvasTexture
- 投稿日:2020-11-16T12:22:49+09:00
select2 is not a functionの解決策
- 投稿日:2020-11-16T12:03:56+09:00
【入門者向け】Canvas入門講座#8 円を塗りつぶそう【JavaScript】
問題8
中心が(200, 200)、半径100の円を塗りつぶしなさい。
塗りつぶす色はマゼンタ色(#ff00ff)であること。
なお、以下のHTMLを使うこと。<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>問題8</title> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script> $(() => { // ここにプログラムを書く }); </script> </head> <body> <canvas id="my-canvas" width="500" height="300"></canvas> </body> </html>答え
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>問題8</title> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script> $(() => { // コンテキストを取得 const ctx = $('#my-canvas')[0].getContext('2d'); // 塗りつぶしの色をマゼンタ色にする ctx.fillStyle = '#ff00ff'; ctx.beginPath(); // 現在のパスをリセットする ctx.arc(200, 200, 100, 0, Math.PI*2, true); // 円を描画する ctx.closePath(); // パスを閉じる ctx.fill(); // 現在のパスを塗りつぶす }); </script> </head> <body> <canvas id="my-canvas" width="500" height="300"></canvas> </body> </html>解説
コンテキストを取得します。これは問題1と同じです。
// コンテキストを取得 const ctx = $('#my-canvas')[0].getContext('2d');次に塗りつぶしの色を指定します。
塗りつぶしの色はfillStyleで指定します。// 塗りつぶしの色をマゼンタ色にする ctx.fillStyle = '#ff00ff';後はパスを作成し、最後にfillを呼びます。
円弧の描画arcメソッドについては問題7の解説をご覧ください。ctx.beginPath(); // 現在のパスをリセットする ctx.arc(200, 200, 100, 0, Math.PI*2, true); // 円を描画する ctx.closePath(); // パスを閉じる ctx.fill(); // 現在のパスを塗りつぶす
- 投稿日:2020-11-16T11:50:29+09:00
【入門者向け】Canvas入門講座#7 円を描こう【JavaScript】
問題7
中心が(200, 200)、半径60の円を描画しなさい。
線分は青色(#0000ff)で太さが3であること。
なお、以下のHTMLを使うこと。<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>問題7</title> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script> $(() => { // ここにプログラムを書く }); </script> </head> <body> <canvas id="my-canvas" width="500" height="300"></canvas> </body> </html>答え
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>問題7</title> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script> $(() => { // コンテキストを取得 const ctx = $('#my-canvas')[0].getContext('2d'); // 線の色を青色にする ctx.strokeStyle = '#0000ff'; // 線の太さを3にする ctx.lineWidth = 3; ctx.beginPath(); // 現在のパスをリセットする ctx.arc(200, 200, 60, 0, Math.PI*2, true); // 円を描画する ctx.closePath(); // パスを閉じる ctx.stroke(); // 現在のパスを描画する }); </script> </head> <body> <canvas id="my-canvas" width="500" height="300"></canvas> </body> </html>解説
コンテキストを取得します。これは問題1と同じです。
// コンテキストを取得 const ctx = $('#my-canvas')[0].getContext('2d');次に線の色と太さを指定します。
線の色はstrokeStyleで指定します。
線の太さはlineWidthで指定します。// 線の色を青色にする ctx.strokeStyle = '#0000ff'; // 線の太さを3にする ctx.lineWidth = 3;後はパスを作成し、最後にstrokeを呼びます。
本問題では、パスの始点と終点が一致しているのでclosePathを呼んでも呼ばなくても結果は同じです。円弧の書き方ですが、
ctx.arc( x, y, 半径, 開始角度, 終了角度, 回り方)のように指定します。
x, yは円の中心点の座標を指定します。
開始角度、終了角度はラジアンで指定します。
回り方はtrueで反時計回り、falseで時計回りであり、今回はどちらを指定しても結果は同じです。ctx.beginPath(); // 現在のパスをリセットする ctx.arc(200, 200, 60, 0, Math.PI*2, true); // 円を描画する ctx.closePath(); // パスを閉じる ctx.stroke(); // 現在のパスを描画する
- 投稿日:2020-11-16T11:32:03+09:00
Webアプリでカメラ操作したいときに見るメモ(javascript)
HTML
<video id="myvideo" width="160" height="160" muted autoplay playsinline></video>・width:横幅
・height:縦幅
・muted:動画の音をでないように設定
・autoplay:動画を自動再生
・playsinline:インライン再生を可能にする参考
【videoタグ】HTMLで動画を埋め込む方法を徹底まとめJS
async function main() { // デバイス(カメラ・マイク)からのデータ取得を試みる // 映像や音声が使えるデバイスが確定するまで時間がかかるためawaitを使う const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true, }); // IDが"myvideo"であるDOMを取得 const video = document.getElementById('myvideo'); // myvideoなvideo要素のsrcObject(映像オブジェクトを入れるところ)にデータ(メディアストリーム)をセットする video.srcObject = stream; } // 実行する main();参考
javaScriptでWEBカメラの映像をブラウザで表示する(PC/iPhone)
video要素、audio要素をJavaScriptから操作するフロントカメラとリアカメラを切り替える方法(スマホで使う)
フロントカメラ=内側のカメラ(インカメ)
リアカメラ=外側のカメラ■フロントカメラ
navigator.mediaDevices.getUserMedia({ audio: false, video: { width: 640, height: 480, facingMode: "user" } })■リアカメラ
navigator.mediaDevices.getUserMedia({ audio: false, video: { width: 640, height: 480, facingMode: { exact: "environment" } } })
- 投稿日:2020-11-16T10:50:30+09:00
【JavaScript】splice()メソッド
プログラミング勉強日記
2020年11月16日
spliceメソッドを学習したので、記録していきます。spliceメソッドとは?
配列の中身を削除したり、置き換えたり、新しく値を追加できるメソッドです。
構文
num.splice('start','deleteCount', item1, item2....);1.start
配列を変更する開始位置を表すインデックス番号を記述します。
2.deleteCount
startから値を取り除く数を記述します。
記述しなかった場合、start以降(startの値も含めます)の値を全て削除します。
0の場合、値は削除されませんが、itemに新しく追加する値を記述する必要があります。
3.item1,item2....
startから配列に追加する値を記述します。
例文
インデックス番号3(50)から、2個(50と60)の値を削除して、新しく35を追加する。
script.jsconst scores = [80,90,40,50,60,45]; scores.splice(3, 2, 35);出力結果.[80,90,40,35,45]参考資料
- 投稿日:2020-11-16T10:47:02+09:00
GoogleMapに複数のマーカーを表示する
GoogleMapに対象店舗と周辺店舗を表示させたいと思ったため調べたことを記録します。
当記事は、主にJavascriptの記述についての記載になるためGoogleMapのAPIキー取得等に関する記述は省略します。
条件としては以下のとおりです。・地図の中心に対象店舗が表示される。(マーカーを立てる)
・対象店舗を中心として半径5km以内にある店舗にもマーカーを表示する。
・対象店舗と周辺店舗はマーカーの色を分ける。
・周辺店舗のマーカーをクリックしたときには吹き出しが表示される。
・吹き出しには店舗名が入っており、店名をクリックすると該当店舗の詳細ページに遷移する。開発環境
ruby 2.6.5
Ruby on Rails 6.0.3.3
OS: macOS Catalinaはじめに
GoogleMapApiの使い方に関してこちらの記事がわかりやすくまとめてあり参考にさせていただきました。
Google Maps APIの使い方実装コード
コードは以下のとおりです。
let map; let mainMarker; let marker =[]; let infoWindow = []; function initMap(){ let markerData = gon.places; let latlng = {lat: gon.latitude, lng: gon.longitude}; // 初期位置の指定 map = new google.maps.Map(document.getElementById('map'), { center: latlng, zoom: 14 }); // 初期位置にマーカーを立てる mainMarker = new google.maps.Marker({ map: map, position: latlng }); // 近隣店舗にマーカーを立てる for (var i = 0; i < markerData.length; i++) { const image = "https://maps.google.com/mapfiles/ms/icons/yellow-dot.png"; const id = markerData[i]['id']; // 緯度経度のデータを作成 let markerLatLng = new google.maps.LatLng({lat: markerData[i]['latitude'], lng: markerData[i]['longitude']}); // マーカーの追加 marker[i] = new google.maps.Marker({ position: markerLatLng, map: map, icon: image, }); // 吹き出しの追加 infoWindow[i] = new google.maps.InfoWindow({ // 吹き出しに店舗詳細ページへのリンクを追加 content: `<a href=/laundries/${id}>${markerData[i]['name']}</a>` }); markerEvent(i); } // マーカークリック時に吹き出しを表示する function markerEvent(i) { marker[i].addListener('click', function() { infoWindow[i].open(map, marker[i]); }); } } document.addEventListener('turbolinks:load', function(){ initMap(); });1つずつ見ていきます。
1.変数の定義let map; let mainMarker; let marker =[]; let infoWindow = [];対象店舗と周辺店舗のマーカーを分けたかったため2つ変数を用意しました。
marker
には周辺店舗の情報が複数入るため配列にしています。infoWindow
も同様に、各店舗のマーカーに対して吹き出しを付けたいため配列にしています。2.地図の作成〜マーカーを立てる
let markerData = gon.places; let latlng = {lat: gon.latitude, lng: gon.longitude}; // 初期位置の指定 map = new google.maps.Map(document.getElementById('map'), { center: latlng, zoom: 14 }); // 初期位置にマーカーを立てる mainMarker = new google.maps.Marker({ map: map, position: latlng });latlngという変数を定義して店舗の緯度と経度を指定しています。
gon
というのはgemの1つでrailsで定義した変数を、javascriptで使える変数に変換してくれます。
(https://github.com/gazay/gon)
new google.maps.Map
とすることで新しく地図を作成し、オプションで中心地やズームレベルを指定します。
地図の作成に関してはLet'sプログラミングやMaps JavaScriptAPIのドキュメントを参考にさせていただきました。次に作成した地図にマーカーを立てていきます。
こちらもnew google.maps.Marker
でマーカーを作成してどのmapにマーカーをたてるか、どこにマーカーを立てるかを指定することでマーカーを立てることができます。3.周辺店舗にマーカーを立てる + 吹き出しを追加する
for (var i = 0; i < markerData.length; i++) { const image = "https://maps.google.com/mapfiles/ms/icons/yellow-dot.png"; const id = markerData[i]['id']; // 緯度経度のデータを作成 let markerLatLng = new google.maps.LatLng({lat: markerData[i]['latitude'], lng: markerData[i]['longitude']}); // マーカーの追加 marker[i] = new google.maps.Marker({ position: markerLatLng, map: map, icon: image, }); // 吹き出しの追加 infoWindow[i] = new google.maps.InfoWindow({ // 吹き出しに店舗詳細ページへのリンクを追加 content: `<a href=/laundries/${id}>${markerData[i]['name']}</a>` }); markerEvent(i); } // マーカークリック時に吹き出しを表示する function markerEvent(i) { marker[i].addListener('click', function() { infoWindow[i].open(map, marker[i]); }); }行っていることとしては
let markerData = gon.places;
で定義した変数の値を1つずつ取り出してマーカーと吹き出しを追加しています。
そしてマーカーの色を変えたかったためimage
という定数にアイコンを定義してicon: image
で定義したアイコンを使用しています。
new google.maps.LatLng({lat: markerData[i]['latitude'], lng: markerData[i]['longitude']});
とすることでそれぞれの要素の緯度経度に対してマーカーを立てるように指定します。続いて吹き出しを追加します。
content
で吹き出しの中に入れる文字を指定できます。
今回は各店舗名を表示+クリックすることで詳細ページに遷移をさせたかったため、aタグを使いました。
最後に吹き出しを作成しただけでは表示されないためaddListener
を利用してマーカーがクリックされたときに吹き出しが開くようにしました。以上、GoogleMapに複数マーカーを表示する方法でした。
- 投稿日:2020-11-16T09:54:12+09:00
Apollo ClientのReactive variablesが良さげ [Local resolvers deprecated] [Local-only fields]
久々にドキュメント読んだらLocal resolversがdeprecatedになっていた事件
なんてこった。
Local resolversの代わりにLocal-only fields使うといいらしい。Local-only fields
今までのようにapollo cacheに値を保存する方法と、Reactive variablesという新しいものがあるという。
この記事ではReactive variablesのほうについて書く。Reactive variables
apollo clientのキャッシュ以外の場所でローカル値を保有できるらしく、キャッシュと分離してるがゆえに、どんな型だろうと保存できる。
また、GraphQL queryを書かずにアクセスできるのも特徴。
うーん、良い。作る
makeVar
関数で作ることができる。import { makeVar } from '@apollo/client'; // 空の配列を作る。 const cartItemsVar = makeVar([]);簡単。
アクセス
関数のように呼べば値を取得でき、引数を渡せば変更できる。
// 値を出力。 console.log(cartItemsVar()); // 値を変更 cartItemsVar([...cartItemsVar(), newItem]);簡単。
Reactive
値が変更されたらコンポーネントの更新もしてほしいのが普通。
useReactiveVar
直接値を使うときは
useReactiveVar
hook。import { useReactiveVar } from '@apollo/client'; export function Cart() { const cartItems = useReactiveVar(cartItemsVar); return ( <div class="cart-items"> {cartItems.map(productId => ( <CartItem key={productId} /> ))} </div> ); }簡単。
GraphQL queryで問い合わせる
export const cache = new InMemoryCache({ typePolicies: { Query: { fields: { cartItems: { read() { return cartItemsVar(); } } } } } });cacheに登録をすれば今まで通りGraphQL query投げて値を取得できる
export const GET_CART_ITEMS = gql` query GetCartItems { cartItems @client } `; export function Cart() { const { data, loading, error } = useQuery(GET_CART_ITEMS); return ( <div class="cart-items"> {data && data.cartItems.map(productId => ( <CartItem key={productId} /> ))} </div> ); }が、果たしてこれをする必要があるのかは疑問。
特有のメリットとしては@exportが使えるようになること。@export
↓ローカルにある値を取得し、それをサーバーに投げるクエリの引数として使うことを一つのGraphQL queryで出来る。
query CurrentAuthorPostCount($authorId: Int!) { # currentAuthorIdの戻り値をauthorIdとしてエクスポート。 currentAuthorId @client @export(as: "authorId") # エクスポートされたauthorIdを通常のクエリの引数に使う。 postCount(authorId: $authorId) }便利っちゃ便利。
感想
手軽そう。
今までの仕組み(Local resolvers)だと、ローカルのデータへのアクセスも基本的にGraphQLの仕組みで行われていて、GraphQL queryを書く必要があった。
サーバー側のデータと同じように扱えるという一貫性はあった一方で、面倒でもあった。
クエリ書かずにローカルステートの管理ができるのは非常にありがたい。
- 投稿日:2020-11-16T09:16:32+09:00
ブックマークレット(JavaScript)で湯婆婆を実装してみる
書いたあとで見つけたんですが、同じjs実装で圧倒的にスマートな例が別の湯婆婆のコメント欄にありました。
https://qiita.com/TD12734/items/743e90fb867fee8c562e#comment-5abe544380a4b2d29af3
上記、@shinji709様のコードが先となりますorzはじめに
「Javaで湯婆婆を実装してみる」のフォロワーネタです。
JavaScript版は既出なので、ブックマークレットにしました。ブックマークレットは昔ゲーム・オブ・スローンズの画面が(演出上)暗くて見えにくい事にイライラして、アマプラの動画の輝度を調整するChromeExtensionを作ろうとした時にちょっと勉強した記憶があります。
(輝度調整のChrome拡張機能は既にあった)コード
javascript:(function(){let n=window.prompt("契約書だよ。そこに名前を書きな。");if (!n.trim()) n=null;window.alert(`フン。${n}というのかい。贅沢な名だねぇ。`);n=n.substr(Math.floor(Math.random() * Math.floor(n.length)), 1);window.alert(`今からお前の名前は${n}だ。いいかい、${n}だよ。分かったら返事をするんだ、${n}!!`)})()適当なサイトなどでブックマークを作り、リンク先を上記コードに置き換えるとブックマークレットとして動作します。
faviconを湯婆婆に変えるとよりそれっぽくなるかもしれません。注記
ブックマークレットは、開いているページ内容(タブ)に影響します。
(文字列置換ブックマークレットとかありますよね)
Chromeの「新しいタブ」など、サイトではなくChromeシステム配下のページではJavaScriptは動作しないようです。
参考:http://mk.miko.jp/blog/archives/4497
yahooやqiitaなど適当なサイトを開き、そのタブで実行してください。コードの解説
// ブックマークレットは今開いているサイトのスコープと干渉するので即時関数にする ( function(){ let n=window.prompt("契約書だよ。そこに名前を書きな。"); // 名無しの場合は異常終了させるためにnullを代入。Cannot read property 'substr' of nullで落ちます。 if (!n.trim()) n=null; window.alert(`フン。${n}というのかい。贅沢な名だねぇ。`); // 抽選。先に抽選すればダイアログ2個で済みますが、あえて分けました。 n=n.substr(Math.floor(Math.random() * Math.floor(n.length)), 1); window.alert(`今からお前の名前は${n}だ。いいかい、${n}だよ。分かったら返事をするんだ、${n}!!`) } )()実行結果
最後に
大昔にいじったDelphiとか引っ張り出そうかと思いましたが流石にめんどうでやめましたw
- 投稿日:2020-11-16T04:43:25+09:00
【JavaScript】CSV 書き出しの際に必要なエスケープ処理と二次元配列→文字列変換の方法
CSV 書き出しの際に必要なエスケープ処理と二次元配列→文字列変換の方法
こんにちは、ndj です。
二次元配列から変換したCSV
(カンマと改行コードで区切ってあるアレです)をファイルなどに書き出す際に値にエスケープ処理を施す必要があったので、そのときの方法を記録しておきます。
また、合わせて二次元配列をCSV
形式の文字列へ変換する方法についても記録しておきます。なぜエスケープ処理が必要なのか
CSV
形式のファイルを作成する際、なぜエスケープ処理が必要なのかといいますと、値の中に,(カンマ)や\n(改行)が含まれてしまっていると、Excel
や他のアプリケーションなんかでCSV
を読み込んだときに値中に含まれている,(カンマ)や\n(改行)と区切り文字である,(カンマ)や\n(改行)を区別できないわけです。
なので、値は「""(ダブルクォーテーション)」で囲んで値であるということを明示する必要があります。エスケープ処理を行う方法
escape.js// エスケープ処理 const escapeForCSV = (s) => { return `"${s.replace(/\"/g, '\"\"')}"` }正規表現を用いて、引数として与えられた文字列をダブルクォーテーションで囲っています。
引数として与えられた文字列中に、「"」が含まれているとCSV
に変換した際に値がずれてしまうため、「""」としてエスケープします。二次元配列から CSV 文字列へ変換する方法
convert.jsconst arrToString = (arr, colDelimeter=',', rowDelimeter='\n') => { return arr.map((row) => row.map((cell) => escapeForCSV(cell)).join(colDelimeter)).join(rowDelimeter); }まず、二次元配列を
map()
メソッドで行(row
)を取り出す。
row
は一次元配列になっているので、さらにmap()
メソッドを用いてセル(cell
)を取り出す。
このとき、各値にエスケープ処理を施す。
そして、row
を区切り文字で連結(この場合は「,」)する。
最後に行区切り(この場合の区切り文字は「\n」)を行い、完了。サンプルコード
先述したように、
arrToString()
内で各セルに対してescapeForCSV()
を実行して、エスケープ処理を施しています。
TSV
形式にしたい場合は、arrToString()
に引数を渡せばOK。sample.js// エスケープ処理 const escapeForCSV = (s) => { return `"${s.replace(/\"/g, '\"\"')}"` } //二次元配列 -> CSV形式の文字列に変換 const arrToString = (arr, colDelimeter=',', rowDelimeter = '\n') => { return arr.map((row) => row.map((cell) => escapeForCSV(cell)).join(colDelimeter)).join(rowDelimeter); } // CSV 形式文字列に変換 let sample = [['a', 'b', 'c'], ['"a"', '"b"', '"c"']]; console.log(arrToString(sample)); // "a","b","c" // """a""","""b""","""c""" // TSV 形式にしたい場合 console.log(arrToString(sample, colDelimeter='\t')); // "a" "b" "c" // """a""" """b""" """c"""最後に
どちらも汎用的な処理なので、おそらくライブラリがあると思いますが、手法を知っておくのは必要だと思ったので、残しておきます。
誤字脱字やアドバイス、ご指摘などございましたらコメントや編集リクエストなどくださると幸いです。
以上です。
ここまで読んでくださりありがとうございました。参考
CSV について
正規表現について
Qiita: JavaScript 正規表現まとめ @iLLviA 様
非常に参考になりました。ありがとうございました。
- 投稿日:2020-11-16T03:15:47+09:00
Vue.jsを使って簡単なカウンターアプリを作ってみた
最近Vue.jsの勉強を本格的に始めました。ひとまず何か簡単なものを作ろう!ということで、今回はカウンターアプリを作ってみました。良ければお試し下さい→カウンターアプリ
実現したいこと
1.ボタンをカチカチして数値を変更し、計算させる。
2.計算方法は足し算、引き算、掛け算、割り算を用意する。
3.計算方法の表記は、切り替えたタイミングで上に表示させる。コード
index.html<!DOCTYPE html> <html lang="jp"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>カウンターアプリ</title> <link rel="preconnect" href="https://fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css2?family=Oswald:wght@400;700&display=swap" rel="stylesheet"> <link rel="stylesheet" href="style.css"> </head> <body> <div id="app"> <div id="container"> <h1 class="title">{{title}}</h1> <div class="inputArea"> <div class="top"> <button class="btn" @click="firstIncrement">+</button> <button class="btn" @click="secondIncrement">+</button> </div> <h1 class="cal">{{first}} {{key}} {{second}} = {{resultAdd(key)}}</h1> <div class="bottom"> <button class="btn" @click="firstDecrement">-</button> <button class="btn" @click="secondDecrement">-</button> </div> </div> <div class="format"> <button class="changeBtn" @click="key = '+'">+</button> <button class="changeBtn" @click="key = '-'">-</button> <button class="changeBtn" @click="key = '×'">×</button> <button class="changeBtn" @click="key = '÷'">÷</button> <button class="changeBtn" @click="first = 0, second = 0">C</button> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script> <script src="main.js"></script> </body> </html>style.css*{ margin: 0; padding: 0; font-family: 'Oswald', sans-serif; touch-action: manipulation; } body{ color: white; background-color: black; } #container{ height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; } button{ color:black; background-color: white; outline: none !important; cursor: pointer; } .title{ font-size: 45px; margin-bottom: 60px; } .cal{ font-size: 50px; text-shadow: 3px 3px rgb(121, 121, 121); } .inputArea{ width: 100%; height: 250px; display: flex; flex-direction: column; justify-content: space-around; align-items: center; margin-bottom: 60px; & .btn{ font-size: 30px; font-weight: 700; padding: 5px 10px; margin: 20px; } & .top, .bottom{ margin-right: 70px; } } .changeBtn{ font-size: 30px; font-weight: 700; padding: 5px 10px; margin: 0px 10px; }main.jsnew Vue({ data:{ first:0, second:0, sum:0, title:'addition', format:['Addition','Subtraction','Multiplication','Division'], key:'+', }, methods:{ firstIncrement(){ this.first += 1; }, firstDecrement(){ if(this.first > 0){ this.first -= 1; } }, secondIncrement(){ this.second += 1; }, secondDecrement(){ if(this.second > 0){ this.second -= 1; } }, }, computed:{ resultAdd(){ return function(f){ if(f === '+'){ this.title = this.format[0]; return this.first + this.second; }else if(f === '-'){ this.title = this.format[1]; return this.first - this.second; }else if(f === '×'){ this.title = this.format[2]; return this.first * this.second; }else{ this.title = this.format[3]; this.sum = this.first / this.second; return Math.round(this.sum * 1000) / 1000; } } }, }, }).$mount("#app");methodsプロパティ
+ボタン、-ボタンをクリックすると、methodsプロパティのIncrementメソッド、Decrementメソッドが発火します。メソッド内ではdataプロパティに設定したfirstプロパティとsecondプロパティを参照し、値を加算、減算します。
メソッドからdataプロパティを参照する時はthisを忘れずに(よく忘れてエラーになります・・・)computedプロパティ
computedプロパティのresultAddではfirstやsecondの変更を検出して計算結果を返してくれます。
さらに今回は計算方法が4パターン欲しいので、resultAddに引数を持たせて、条件分岐をさせてみました。例えば渡された引数の文字列が「+」だったら、足し算の結果を返す。「×」だったら、掛け算の結果を返す。という感じです。また、計算方法の変更は、下の「+」「-」「×」「÷」のボタンで変更しますが、これらのボタンが押された際も、resultAddが発火し、表記の変更をしつつ、変更した方法で計算結果を返してくれます。(computed便利!)
割り算で四捨五入
割り算で割り切れない場合、結果が「3.33333333・・・」という感じで表示され、レイアウトが大変なことになるので、今回はMath.round関数を使って、小数点第四位を四捨五入しています。
作ってみて
という感じで、簡単なカウンターアプリを作ってみました。
JavaScriptと比べるとVue.jsはコードが短く、簡潔に記述できるので、(もっと簡潔に書けるかもしれませんが、、)改めてフレームワークすご!!と思いましたね。引き続きVue.js頑張りたいと思います!
- 投稿日:2020-11-16T02:44:58+09:00
【JavaScript】CSV 書き出しの際に必要なエスケープ処理
CSV 書き出しの際に必要なエスケープ処理を記録しておく
こんにちは、ndj です。
二次元配列から変換したCSV
(カンマと改行コードで区切ってあるアレです)をファイルなどに書き出す際に値にエスケープ処理を施す必要があったので、そのときの方法を記録しておきます。注意点
今回対象となる
CSV
フォーマットは、
列を「,(カンマ)」、
行を「\n(改行)」
で区切っているものです。なぜエスケープ処理が必要なのか
CSV
形式のファイルを作成する際、なぜエスケープ処理が必要なのかといいますと、値の中に,(カンマ)や\n(改行)が含まれてしまっていると、Excel
や他のアプリケーションなんかでCSV
を読み込んだときに値中に含まれている,(カンマ)や\n(改行)と区切り文字である,(カンマ)や\n(改行)を区別できないわけです。
なので、区切り文字ではない文字列として,(カンマ)や\n(改行)はきちんと印をつけておく(エスケープしておく)必要があります。エスケープ処理を行う JavaScript Function
Function
escape.jsconst escapeForCSV = (s) => { // エスケープ対象 const targetLst = ['\"', ',', '\n', '\r']; // 受け取った引数で戻り値を初期化する let escapedValue = s; // エスケープ処理を行う targetLst.forEach((elm) => { let reg = new RegExp(elm, 'g'); escapedValue = escapedValue.replace(reg, `\"${elm}\"`); }); return escapedValue }正規表現を用いて、エスケープの対象となる文字列をダブルクォーテーションで囲っています。
「"」に関しては、値を「""」で囲む場合使用方法と結果
変換したい文字列を引数として与えるだけです。
CSV 文字列変換後(区切り文字を追加した後)だと区切り文字か値中の文字か判断できないので注意
二次元配列→一次元配列の変換の際に使うとよい。sample.js// 基本 let sample1 = '「\n」は、改行コードです。'; sample1 = escapeForCSV(sample1); // 配列の場合 let sample2dArr = [['\n', '\r', '\"', ','], ['改行', '行頭復帰', 'ダブルクォーテーション', 'カンマ']]; let sampleArr = sample2dArr.map((arr) => escapeForCSV(arr.join(','))); let csv = sampleArr.join('\n');インプットとアウトプットを確認してみます。
sample.jsconst logging = (attr, callback) => { let processed = callback(attr); console.log(attr); console.log(processed); } let sample1 = '「\n」は、改行コードです。'; let sample2 = '「,」は CSV のセパレータです。'; let sample3 = '「"」はダブルコーテーションかダブルクォーテーションどちらで表記するか迷う。'; logging(sample1, escapeForCSV); // output before:「\n」は、改行コードです。 // output after:「"\n"」は、改行コードです。 logging(sample2, escapeForCSV); // output before: 「,」は CSV のセパレータです。 // output after: 「","」は CSV のセパレータです。 logging(sample3, escapeForCSV); // output before: 「"」はダブルコーテーションかダブルクォーテーションどちらで表記するか迷う。 // output after: 「"""」はダブルコーテーションかダブルクォーテーションどちらで表記するか迷う。最後に
汎用的な処理として使えそうだったので、残しておきます。
アレ、呼び出しのときはどうすればいいんだ...?
おそらくすぐ必要になるかと思うので、近いうちに記事にしたいと思います。
誤字脱字やアドバイス、ご指摘などございましたらコメントや編集リクエストなどくださると幸いです。
以上です。
ここまで読んでくださりありがとうございました。参考
CSV フォーマットについて
正規表現について
Qiita: JavaScript 正規表現まとめ @iLLviA 様
非常に参考になりました。ありがとうございました。
- 投稿日:2020-11-16T00:02:09+09:00
Javascriptのコールバック関数について整理する
Javascriptを勉強中、コールバック関数を理解するのに無駄に時間がかかってしまったのでメモとして整理しておきます。私の様な初心者の方の理解の一助となれば幸いです。内容はコールバック関数の非常に初歩的な部分についてのみとなっています。
ちなみにこれがQiita初投稿でプログラミング自体も初心者なので、間違いや的外れな箇所があるかもしれません。何かあればコメントでご指摘ください。
コールバック関数とは?
定義は「他の関数の引数として渡される関数」のことです。Javascriptだけでなく他の言語でも使われます。
この定義だけみるとシンプルで何のことはないのですが、いざプログラムに書くと見た目がかなり複雑になって何をしているのかが分かりづらくなり、勉強中の私は非常に混乱しました。
単純な関数との比較
コールバック関数が出てくるとカッコの数や行の数が多くなり混乱しやすいですが、数値などの引数を持つ通常の関数との比較という形で考えると理解しやすいと思います。
例えば、以下のadd関数は二つの数値を引数に取り、その和をコンソールに表示する関数です。(なお、アロー関数に関してはこちらの記事が分かりやすいと思います。)
let add = (a, b) => { console.log(a + b); };そして
add (1, 2);
などと実行すると、aとbにそれぞれ1, 2が代入されその和である3がコンソールに表示されます。同様に、以下のfuncAは、funcBという関数を引数としてとる関数です。
let funcA = (funcB) => { funcB('Hello!'); };funcAは、「funcBという関数を"Hello!"という引数で実行する関数」です。このfuncBがコールバック関数に当たります。
この時点では、funcBがどんな関数なのかは分かりません。それはこれからfuncAの実行時に引数として指定します。
add関数の引数である(a, b)と同じです。実行してみる
それでは適当にfuncBを指定して実行してみましょう。
funcA((message) => { console.log(message); }); //結果: Hello!なんだか見たことのある形になりましたね。カッコの数や行数が多くなって分かりづらいですが、
add(1, 2);
などとやっていることは同じです。要はfuncB = (message) => { console.log(message); };という感じで、引数であるfuncBの実体となる関数を指定し、funcAを実行しているだけです。
ここでfuncBに指定した関数は、messageという変数を引数に取り、それをコンソールに表示します。そして下に再掲した通り、funcAというのは元々「funcBを"Hello!"という引数で実行する関数」でしたから、結果としてコンソールに"Hello!"が出力されます。
let funcA = (funcB) => { funcB('Hello!'); };余談
ChromeのDeveloper Toolsでは、現在の変数の値などを逐一表示しながらプログラムを一行ずつ実行できるので、学習に非常に有効でした。
今後
元々PromiseやAsync, Awaitなどの非同期処理を勉強しようとしていたところだったので、そちらについてもそのうち書きたいと思っています。
参考
- 投稿日:2020-11-16T00:02:09+09:00
Javascriptのコールバック関数でつまずいた
Javascriptを勉強中、コールバック関数を理解するのに無駄に時間がかかってしまったのでメモとして整理しておきます。私の様な初心者の方の理解の一助となれば幸いです。内容はコールバック関数の非常に初歩的な部分についてのみとなっています。
ちなみにこれがQiita初投稿でプログラミング自体も初心者なので、間違いや的外れな箇所があるかもしれません。何かあればコメントでご指摘ください。
コールバック関数とは?
定義は「他の関数の引数として渡される関数」のことです。Javascriptだけでなく他の言語でも使われます。
この定義だけみるとシンプルで何のことはないのですが、いざプログラムに書くと見た目がかなり複雑になって何をしているのかが分かりづらくなり、勉強中の私は非常に混乱しました。
単純な関数との比較
コールバック関数が出てくるとカッコの数や行の数が多くなり混乱しやすいですが、数値などの引数を持つ通常の関数との比較という形で考えると理解しやすいと思います。
例えば、以下のadd関数は二つの数値を引数に取り、その和をコンソールに表示する関数です。(なお、アロー関数に関してはこちらの記事が分かりやすいと思います。)
let add = (a, b) => { console.log(a + b); };そして
add (1, 2);
などと実行すると、aとbにそれぞれ1, 2が代入されその和である3がコンソールに表示されます。同様に、以下のfuncAは、funcBという関数を引数としてとる関数です。
let funcA = (funcB) => { funcB('Hello!'); };funcAは、「funcBという関数を"Hello!"という引数で実行する関数」です。このfuncBがコールバック関数に当たります。
この時点では、funcBがどんな関数なのかは分かりません。それはこれからfuncAの実行時に引数として指定します。
add関数の引数である(a, b)と同じです。実行してみる
それでは適当にfuncBを指定して実行してみましょう。
funcA((message) => { console.log(message); }); //結果: Hello!なんだか見たことのある形になりましたね。カッコの数や行数が多くなって分かりづらいですが、
add(1, 2);
などとやっていることは同じです。要はfuncB = (message) => { console.log(message); };という感じで、引数であるfuncBの実体となる関数を指定し、funcAを実行しているだけです。
ここでfuncBに指定した関数は、messageという変数を引数に取り、それをコンソールに表示します。そして下に再掲した通り、funcAというのは元々「funcBを"Hello!"という引数で実行する関数」でしたから、結果としてコンソールに"Hello!"が出力されます。
let funcA = (funcB) => { funcB('Hello!'); };余談
ChromeのDeveloper Toolsでは、現在の変数の値などを逐一表示しながらプログラムを一行ずつ実行できるので、学習に非常に有効でした。
今後
元々PromiseやAsync, Awaitなどの非同期処理を勉強しようとしていたところだったので、そちらについてもそのうち書きたいと思っています。
参考