20210608のJavaScriptに関する記事は26件です。

【javascript】シンプルなtableの動的行追加・削除(ボタンクリックで行追加・削除)

やりたいこと tableを、ボタンクリックで行追加、削除する ●次の記事 【javascript】シンプルな動的tableのinputの値取得 完成形、動作 See the Pen table-clickadd/clickdel by Hirofumi Sato (@hfmst) on CodePen. ざっくり解説 行追加 tableとthだけ用意 createElementで、追加するtr(行要素)を作っておく 繰り返しで「td作る→tdに何か入れる→trにどんどん追加 3で作ったtrをtdに入れる 削除 最新のtableの最終行取得 deleteRowで削除 コード index.html <!--ボタン配置--> <div> <button onclick="add()">行追加</button> <button onclick="del()">行削除</button> </div> <!--table作成とヘッダーだけ先に--> <table id="tbl" border="1" style="border-collapse:collapse;"> <tr> <th>名前</th> <th>趣味</th> <th>好きなもの</th> </tr> </table> js.html //繰り返し使うtableだけ先に定数に格納 const tbl = document.getElementById("tbl"); //行追加 function add(){ //空の行要素を先に作成tr let tr = document.createElement("tr"); //以下、繰り返し処理 for(let i=0;i<3;i++){ let td = document.createElement("td"); //新しいtd要素を作って変数tdに格納  let inp = document.createElement("input"); //tdに何か追加。ここは例としてinput  td.appendChild(inp); //tdにinpを追加 tr.appendChild(td); //trにtdを追加 } //完成したtrをtableに追加 tbl.appendChild(tr); } //以下、末尾行削除処理 function del(){ let rw = tbl.rows.length;//行数取得 tbl.deleteRow(rw-1);//最終行を指定して削除 }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascriptでシンプルなtableの動的行追加・削除(ボタンクリックで行追加・削除)

やりたいこと tableを、ボタンクリックで行追加、削除する 完成形、動作 See the Pen table-clickadd/clickdel by Hirofumi Sato (@hfmst) on CodePen. ざっくり解説 行追加 tableとthだけ用意 createElementで、追加するtr(行要素)を作っておく 繰り返しで「td作る→tdに何か入れる→trにどんどん追加 3で作ったtrをtdに入れる 削除 最新のtableの最終行取得 deleteRowで削除 コード index.html <!--ボタン配置--> <div> <button onclick="add()">行追加</button> <button onclick="del()">行削除</button> </div> <!--table作成とヘッダーだけ先に--> <table id="tbl" border="1" style="border-collapse:collapse;"> <tr> <th>名前</th> <th>趣味</th> <th>好きなもの</th> </tr> </table> js.html //繰り返し使うtableだけ先に定数に格納 const tbl = document.getElementById("tbl"); //行追加 function add(){ //空の行要素を先に作成tr let tr = document.createElement("tr"); //以下、繰り返し処理 //新しいtd要素を作って変数tdに格納 //tdに何か追加。ここは例としてinput //trにtdを繰り返し追加 for(let i=0;i<3;i++){ let td = document.createElement("td");  let inp = document.createElement("input");  td.appendChild(inp); tr.appendChild(td); } //完成したtrをtableに追加 tbl.appendChild(tr); } //以下、末尾行削除処理 function del(){ let rw = tbl.rows.length;//行数取得 tbl.deleteRow(rw-1);//最終行を指定して削除 }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで物理エンジンを構築1

ソースコードはこちら https://github.com/horitaku1124/physics_sample/blob/main/sample5/index.html まずはアニメーションなので、以下の様に requestAnimationFrame を使用します。これにより大体の環境で 1/60秒毎に animationFrame が呼ばれるようになります。フレームの描画で Canvas タグに描画します。 const animationFrame = () => { // 1フレーム毎の物理計算 ... request = requestAnimationFrame(animationFrame) } requestAnimationFrame(animationFrame); 重力加速度 gravity が重力加速度です。この世界では距離がピクセルの世界ですので、 0.1$px/s^2$ を重力加速度としています。 // 重力によって加速する obj.speedY += gravity; // 加速された速度によって移動していく obj.x += obj.speedX; obj.y += obj.speedY; // 横の壁に当たったら進行方向を逆に if (obj.x < radius) { obj.x = radius; obj.speedX *= -1; } else if (obj.x > canvasWidth - radius) { obj.x = canvasWidth - radius; obj.speedX *= -1; } // 床に接触するたびに上方向に反発する。 if (obj.y > canvasHeight - radius) { obj.y = canvasHeight - radius; obj.speedY *= -0.5; obj.speedX *= 0.9; // 床に接触した際の摩擦を計算 } また壁にぶつかった場合は進行方向を逆転したり、床に接触した場合は速度を減らして反転したりしています。 これを実装した時のデモが以下になります。 ボール同士の衝突判定 今度はボール同士が衝突した場合を考えます。 衝突した時のエネルギー保存則として、衝突前のエネルギーと衝突後のエネルギーは等しいとして以下の式で表せます。 $ma \times va+mb \times vb = ma' \times va' + mb' \times vb'$ そしてもう一つが反発係数です。 $C = -\frac{va' - vb'}{va - vb}$ この2つの公式を変形して解くと衝突後の速度 va',vb'は $va' = \frac{va + vb - C(va - vb)}{2}$ $vb' = \frac{va + vb - C(vb - va)}{2}$ として計算できます。これをコードにしたのが以下になります。 // 2つのオブジェクト間の距離が0以下なら衝突判定 let distance = Math.sqrt(Math.pow(obj.x - obj2.x, 2) + Math.pow(obj.y - obj2.y, 2)) - 2 * radius; if (distance < 0) { distance = Math.abs(distance); // 2つのオブジェクトの角度 const theta = Math.atan2(obj2.y - obj.y, obj2.x - obj.x); // オブジェクト間の直線速度を計算する const va = Math.sqrt(Math.pow(obj.speedX, 2) + Math.pow(obj.speedY, 2)); const vb = -Math.sqrt(Math.pow(obj2.speedX, 2) + Math.pow(obj2.speedY, 2)); // 衝突後の速度の計算 const va2 = (va + vb - Bounce * va + Bounce * vb) / 2; const vb2 = (va + vb + Bounce * va - Bounce * vb) / 2; このソースの動作デモは以下になります。 振り子 振り子の実装に関しては参考にしているものは無く、自己流となりました。 distanceというのは固定部分までの距離で、先端が重力で下に落ちた時に固定部分までの距離の増分をx成分とy成分に分解してそれぞれ加速度としています。 let distance = Math.sqrt( Math.pow(obj.x - obj.consTo.x, 2) + Math.pow(obj.y - obj.consTo.y, 2) ); // 計算の結果、制約から離れてしまった分を補正する let ratio = distance / obj.distance; // その時の差分を移動エネルギーとする let x2 = (obj.x - obj.consTo.x) / ratio + obj.consTo.x; let y2 = (obj.y - obj.consTo.y) / ratio + obj.consTo.y; obj.speedX += x2 - obj.x; obj.speedY += y2 - obj.y; obj.x = x2; obj.y = y2; こちらがその時の動作です。 次は斜面の転がりや、質量保存の法則などを実装していく予定です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptにおけるデザインパターン(作成系)

デザインパターンを整理する デザインパターンを次の3つに分けて考えます 作成系デザインパターン (コンストラクター、ファクトリー、シングルトンなど) 構造系デザインパターン (デコレータ、ファサードなど) 振舞い系デザインパターン (イテレータ、メディエータ、オブザーバーなど) 作成系デザインパターン 簡単にいうと、オブジェクト作成メカニズムを扱うデザインパターンであり、状況に即した方法でオブジェクトを作成しようとするもの。 コンストラクターパターン Learning JavaScript Design Patternsより コンストラクターは、メモリが割り当てられた後、新しく作成されたオブジェクトを初期化するために使用される特別なメソッドです。 function Person(first, last) { this.first = first this.last = last this.greet = function () { console.log('こんにちは、' + this.last + this.first + 'です。') } } let person = new Person('太郎', '山田') let person2 = new Person('次郎', '鈴木') person.greet() // こんにちは、山田太郎です。 person2.greet() // こんにちは、鈴木次郎です。 この実装には問題があります。 他の開発者が同じソースコード内のどこかで新たな関数を追加すると下記のように変更されてしまう可能性があります。 person.greet = function () { console.log('さようなら!') } person.greet() // さようなら person2.greet() // こんにちは、鈴木次郎です。 同じメソッドならばどのオブジェクトに対しても同じように機能する必要があります。 そのためコンストラクター内で定義せず、コンストラクターの外で、プロトタイプというプロパティーに定義する必要があります。 function Person(first, last) { this.first = first this.last = last // this.greet = function () { // console.log('こんにちは、' + this.last + this.first + 'です。') // } } Person.prototype.greet = function () { console.log('こんにちは、' + this.last + this.first + 'です。') } let person = new Person('太郎', '山田') let person2 = new Person('次郎', '鈴木') person.greet() person2.greet() ファクトリーパターン Learning JavaScript Design Patternsより ファクトリ パターンは、オブジェクトを作成するという概念に関係するもう 1 つの作成パターンです。カテゴリ内の他のパタ​​ーンと異なる点は、コンストラクターを使用する必要がないことです。代わりに、ファクトリはオブジェクトを作成するためのジェネリック インターフェイスを提供でき、作成するファクトリ オブジェクトのタイプを指定できます。 // https://github.com/pkellz/devsage/blob/master/DesignPatterns/FactoryDesignPattern.js より function Developer(name) { this.name = name this.type = "Developer" } function Tester(name) { this.name = name this.type = "Tester" } function EmployeeFactory() { this.create = (name, type) => { switch(type) { case 1: return new Developer(name) case 2: return new Tester(name) } } } function say() { console.log("Hi, I am " + this.name + " and I am a " + this.type) } const employeeFactory = new EmployeeFactory() const employees = [] employees.push(employeeFactory.create("Patrick", 1)) employees.push(employeeFactory.create("John", 2)) employees.push(employeeFactory.create("Jamie", 1)) employees.push(employeeFactory.create("Taylor", 1)) employees.push(employeeFactory.create("Tim", 2)) employees.forEach( emp => { say.call(emp) }) // 結果 // Hi, I am Patrick and I am a Developer // Hi, I am John and I am a Tester // Hi, I am Jamie and I am a Developer // Hi, I am Taylor and I am a Developer // Hi, I am Tim and I am a Tester このようにname、typeの違う複数のobjectを作成し利用する場合に、 employeeFactory.create(name, type)の一行を追加するだけで新たに追加できます。 シングルトンパターン Learning JavaScript Design Patternsより シングルトン パターンは、クラスのインスタンス化を単一のオブジェクトに制限する。 let instance = null function Person(first, last) { if (!instance) { this.first = first this.last = last instance = this } else { return instance } } let person = new Person('太郎', '山田') let person2 = new Person('次郎', '鈴木') console.log('person: ', person) // person: Person { first: '太郎', last: '山田' } console.log('person2: ', person2) // person2: Person { first: '太郎', last: '山田' } この例では2回目以降のnewがelse {return instance}により新たなインスタンスが作成されないように制御しています。 参考文献 Learning JavaScript Design Patterns 関連記事 JavaScriptにおけるデザインパターン(構造系)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

InDesign スクリプト 位置と大きさ(構造の選択された要素のページアイテムの)

構造の選択された要素のページアイテムの位置と大きさのスクリプトは、これで良いのかな・・・? /* 構造の選択された要素のページアイテムの位置と大きさ 更新 2021/06/09 */ // アプリ指定 #target "indesign"; // スクリプト名 var scriptName = "位置と大きさ(構造の選択された要素のページアイテムの)"; //スクリプト動作指定(一つのアンドゥ履歴にする及びアンドゥ名の指定) app.doScript(function () { // ダイアログ var dialogueFlg = confirm(" 構造の選択された要素のページアイテムの位置と大きさをページの左上を原点に最後に選択された要素のページアイテムを基準に指定します。" + "\r\r" + " テキストフレームにのみ対応。", "", scriptName); // Noの場合 if (dialogueFlg == false) { // スクリプトを終了 exit(); } // 選択されているオブジェクト selectObjects = app.activeDocument.selection; // すべての選択を解除 app.activeDocument.selection = null; // 指定数 var assignedNumber = 0; // 選択が0より多い場合 if(selectObjects.length > 0){ // 基準のオブジェクトの位置と大きさ var baselineObjectGeometricBounds; // 基準のオブジェクトのページの位置と大きさ var baselineObjectPageBounds; // 基準のオブジェクトのフラグにfalseを入れる var baselineObjectFlg = false; // 最後の選択がXMLElementの場合 if(selectObjects[selectObjects.length - 1].constructor.name == "XMLElement"){ // (最後の選択の)XML要素の参照がStoryの場合 if(selectObjects[selectObjects.length - 1].xmlContent.constructor.name == "Story"){ // 基準のオブジェクトの位置と大きさに(最後の選択の)XML要素の参照の持つ最初のテキストフレームの位置と大きさを入れる baselineObjectGeometricBounds = selectObjects[selectObjects.length - 1].xmlContent.textContainers[0].geometricBounds; // (最後の選択のXML要素の参照の)parentPageに値がある場合 if (selectObjects[selectObjects.length - 1].xmlContent.textContainers[0].parentPage){ // 元のオブジェクトのページの位置と大きさに(最後の選択の)XML要素の参照の持つ最初のテキストフレームのあるページの位置と大きさを入れる baselineObjectPageBounds = selectObjects[selectObjects.length - 1].xmlContent.textContainers[0].parentPage.bounds; // 選択に追加 selectObjects[selectObjects.length - 1].select(SelectionOptions.addTo); // 基準のオブジェクトのフラグにtrueを入れる baselineObjectFlg = true; } // 以外の場合 }else{ // 画像の場合とか作成予定 } } // 基準のオブジェクトのフラグがfalseの場合 if(baselineObjectFlg == false){ // 選択状態を元に戻す app.activeDocument.selection = selectObjects; // 結果表示 alert("最後に選択された要素のページ(ページアイテム)を取得出来ませんでした。",scriptName); // スクリプトを終了 exit(); } // 選択の数引く1だけ繰り返す for(var i = 0; i < selectObjects.length - 1; i++){ // 選択がXMLElementの場合 if(selectObjects[i].constructor.name == "XMLElement"){ // (選択の)XML要素の参照がStoryの場合 if(selectObjects[i].xmlContent.constructor.name == "Story"){ // テキストフレームの数だけ繰り返す for(var ii = 0; ii < selectObjects[i].xmlContent.textContainers.length; ii++){ // (選択のXML要素の参照の)parentPageに値がある場合 if (selectObjects[i].xmlContent.textContainers[ii].parentPage){ // (選択のXML要素の参照の持つ)テキストフレームがロックされていない場合 if(selectObjects[i].xmlContent.textContainers[ii].locked == false){ // (選択のXML要素の参照の持つ)テキストフレームの位置と大きさに位置と大きさを指定する関数の結果を入れる(配列全体で入れるないとうまくいかない) selectObjects[i].xmlContent.textContainers[ii].geometricBounds = assignBounds(baselineObjectGeometricBounds,baselineObjectPageBounds,selectObjects[i].xmlContent.textContainers[ii].parentPage.bounds) // 指定数追加 assignedNumber++; // 選択に追加 selectObjects[i].select(SelectionOptions.addTo); } } } // 以外の場合 }else{ } } } // 結果表示 alert("指定の元の要素と指定された要素を選択しました。" + "\r" + "指定数 " + assignedNumber,scriptName); } //スクリプト動作指定(一つのアンドゥ履歴にする及びアンドゥ名の指定)の続き }, ScriptLanguage.JAVASCRIPT, [scriptName], UndoModes.ENTIRE_SCRIPT, scriptName); /* 位置と大きさを指定する関数、引数(元のオブジェクトの位置と大きさ、元のオブジェクトのページの位置と大きさ、選択オブジェクトのページの位置と大きさ)の宣言*/ function assignBounds(baselineObjectGeometricBounds,baselineObjectPageBounds,selectObjectPageBounds){ // 指定するオブジェクトの位置と大きさ var assignObjectBounds = [0,0,0,0]; // 指定するオブジェクトの位置と大きさの左上のYに基準のオブジェクトの位置と大きさの左上のY引く基準のオブジェクトのページの位置と大きさの左上のY足す選択オブジェクトのページの位置と大きさの左上のYを入れる assignObjectBounds[0] = baselineObjectGeometricBounds[0] - baselineObjectPageBounds[0] + selectObjectPageBounds[0]; // 指定するオブジェクトの位置と大きさの左上のXに基準のオブジェクトの位置と大きさの左上のX引く基準のオブジェクトのページの位置と大きさの左上のX足す選択オブジェクトのページの位置と大きさの左上のXを入れる assignObjectBounds[1] = baselineObjectGeometricBounds[1] - baselineObjectPageBounds[1] + selectObjectPageBounds[1]; // 指定するオブジェクトの位置と大きさの左上のYに基準のオブジェクトの位置と大きさの左上のY引く基準のオブジェクトのページの位置と大きさの左上のY足す選択オブジェクトのページの位置と大きさの左上のYを入れる assignObjectBounds[2] = baselineObjectGeometricBounds[2] - baselineObjectPageBounds[0] + selectObjectPageBounds[0]; // 指定するオブジェクトの位置と大きさの左上のXに基準のオブジェクトの位置と大きさの左上のX引く基準のオブジェクトのページの位置と大きさの左上のX足す選択オブジェクトのページの位置と大きさの左上のXを入れる assignObjectBounds[3] = baselineObjectGeometricBounds[3] - baselineObjectPageBounds[1] + selectObjectPageBounds[1]; // 指定するオブジェクトの位置と大きさを返す return assignObjectBounds; } /* 位置と大きさを指定する関数の宣言終了*/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

InDesign スクリプト 位置と大きさを指定(構造の選択された要素の)

構造の選択された要素の位置と大きさを指定するスクリプトは、これで良いのかな・・・? /* 構造の選択された要素の位置と大きさを指定 更新 2021/6/8 */ // アプリ指定 #target "indesign"; // スクリプト名 var scriptName = "位置と大きさを指定(構造の選択された要素の)"; //スクリプト動作指定(一つのアンドゥ履歴にする及びアンドゥ名の指定) app.doScript(function () { // ダイアログ var dialogueFlg = confirm("構造の選択された要素の位置と大きさをページの左上を原点に最後に選択された要素を元に指定します(テキストフレームにのみ対応)。", "", scriptName); // Noの場合 if (dialogueFlg == false) { // スクリプトを終了 exit(); } // 選択されているオブジェクト selectObjects = app.activeDocument.selection; // すべての選択を解除 app.activeDocument.selection = null; // 指定数 var assignedNumber = 0; // 選択が0より多い場合 if(selectObjects.length > 0){ // 元のオブジェクトの位置と大きさ var originObjectGeometricBounds; // 元のオブジェクトのページの位置と大きさ var originObjectPageBounds; // 元のオブジェクトのフラグにfalseを入れる var originObjectFlg = false; // 最後の選択がXMLElementの場合 if(selectObjects[selectObjects.length - 1].constructor.name == "XMLElement"){ // (最後の選択の)XML要素の参照がStoryの場合 if(selectObjects[selectObjects.length - 1].xmlContent.constructor.name == "Story"){ // 元のオブジェクトの位置と大きさに(最後の選択の)XML要素の参照の持つ最初のテキストフレームの位置と大きさを入れる originObjectGeometricBounds = selectObjects[selectObjects.length - 1].xmlContent.textContainers[0].geometricBounds; // (最後の選択のXML要素の参照の)parentPageに値がある場合 if (selectObjects[selectObjects.length - 1].xmlContent.textContainers[0].parentPage){ // 元のオブジェクトのページの位置と大きさに(最後の選択の)XML要素の参照の持つ最初のテキストフレームのあるページの位置と大きさを入れる originObjectPageBounds = selectObjects[selectObjects.length - 1].xmlContent.textContainers[0].parentPage.bounds; // 選択に追加 selectObjects[selectObjects.length - 1].select(SelectionOptions.addTo); // 元のオブジェクトのフラグにtrueを入れる originObjectFlg = true; } // 以外の場合 }else{ // 画像の場合とか作成予定 } } // 元のオブジェクトのフラグがfalseの場合 if(originObjectFlg == false){ // 選択状態を元に戻す app.activeDocument.selection = selectObjects; // 結果表示 alert("最後に選択された要素のページを取得出来ませんでした。",scriptName); // スクリプトを終了 exit(); } // 選択の数引く1だけ繰り返す for(var i = 0; i < selectObjects.length - 1; i++){ // 選択がXMLElementの場合 if(selectObjects[i].constructor.name == "XMLElement"){ // (選択の)XML要素の参照がStoryの場合 if(selectObjects[i].xmlContent.constructor.name == "Story"){ // テキストフレームの数だけ繰り返す for(var ii = 0; ii < selectObjects[i].xmlContent.textContainers.length; ii++){ // (選択のXML要素の参照の)parentPageに値がある場合 if (selectObjects[i].xmlContent.textContainers[ii].parentPage){ // (選択のXML要素の参照の持つ)テキストフレームがロックされていない場合 if(selectObjects[i].xmlContent.textContainers[ii].locked == false){ // (選択のXML要素の参照の持つ)テキストフレームの位置と大きさに位置と大きさを指定する関数の結果を入れる(配列全体で入れるないとうまくいかない) selectObjects[i].xmlContent.textContainers[ii].geometricBounds = assignBounds(originObjectGeometricBounds,originObjectPageBounds,selectObjects[i].xmlContent.textContainers[ii].parentPage.bounds) // 指定数追加 assignedNumber++; // 選択に追加 selectObjects[i].select(SelectionOptions.addTo); } } } // 以外の場合 }else{ } } } // 結果表示 alert("指定の元の要素と指定された要素を選択しました。" + "\r" + "指定数 " + assignedNumber,scriptName); } //スクリプト動作指定(一つのアンドゥ履歴にする及びアンドゥ名の指定)の続き }, ScriptLanguage.JAVASCRIPT, [scriptName], UndoModes.ENTIRE_SCRIPT, scriptName); /* 位置と大きさを指定する関数、引数(元のオブジェクトの位置と大きさ、元のオブジェクトのページの位置と大きさ、選択オブジェクトのページの位置と大きさ)の宣言*/ function assignBounds(originObjectGeometricBounds,originObjectPageBounds,selectObjectPageBounds){ // 指定するオブジェクトの位置と大きさ var assignObjectBounds = [0,0,0,0]; // 指定するオブジェクトの位置と大きさの左上のYに元のオブジェクトの位置と大きさの左上のY引く元のオブジェクトのページの位置と大きさの左上のY足す選択オブジェクトのページの位置と大きさの左上のYを入れる assignObjectBounds[0] = originObjectGeometricBounds[0] - originObjectPageBounds[0] + selectObjectPageBounds[0]; // 指定するオブジェクトの位置と大きさの左上のXに元のオブジェクトの位置と大きさの左上のX引く元のオブジェクトのページの位置と大きさの左上のX足す選択オブジェクトのページの位置と大きさの左上のXを入れる assignObjectBounds[1] = originObjectGeometricBounds[1] - originObjectPageBounds[1] + selectObjectPageBounds[1]; // 指定するオブジェクトの位置と大きさの左上のYに元のオブジェクトの位置と大きさの左上のY引く元のオブジェクトのページの位置と大きさの左上のY足す選択オブジェクトのページの位置と大きさの左上のYを入れる assignObjectBounds[2] = originObjectGeometricBounds[2] - originObjectPageBounds[0] + selectObjectPageBounds[0]; // 指定するオブジェクトの位置と大きさの左上のXに元のオブジェクトの位置と大きさの左上のX引く元のオブジェクトのページの位置と大きさの左上のX足す選択オブジェクトのページの位置と大きさの左上のXを入れる assignObjectBounds[3] = originObjectGeometricBounds[3] - originObjectPageBounds[1] + selectObjectPageBounds[1]; // 指定するオブジェクトの位置と大きさを返す return assignObjectBounds; } /* 位置と大きさを指定する関数の宣言終了*/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【2021】How to resize an image from the web and send it to Cloud Storage for Firebase

【2021】How to resize an image from the web and send it to Cloud Storage for Firebase I have implemented a process to resize a logo image and send it to Cloud Storage for Firebase. I struggled a bit with the conversion to base64, blob, etc., so I'll share it so that new implementers can realize the process they want to do as quickly as possible. index.html <!-- Optional image file button (This is implemented in my case because the file selection button is an image instead of the default button, but the normal file function is also acceptable. --> <label> <!-- Show part --> <span class="filelabel" title="Select a file"> <img src="images/camera.png"" width="32" height="26" alt="Image"> <!-- Selsect --> </span> <!-- Hide the original selection form. --> <input type="file" name="datafile" id="logo-img-input" onChange="imgPreView(event)"> </label> <div id="preview"></div> <canvas id="for-resize-canvas" style="display: none;"></canvas> <button id="send" class="btn btn-primary">SEND</button> style="display: none; As for the canvas, I hide it because without it, another image view would be created. The resizing process can be done without displaying it. index.js //===Preview Image function imgPreView(event) { let file = event.target.files[0]; let reader = new FileReader(); let preview = document.getElementById("preview"); let previewImage = document.getElementById("previewImage"); if(previewImage != null) { preview.removeChild(previewImage); } reader.onload = function(event) { let img = document.createElement("img"); img.setAttribute("src", reader.result); /// Size of the image to be displayed in the preview (Warning : not the size to be resized) img.width = 200; img.height = 200; img.setAttribute("id", "previewImage"); preview.appendChild(img); }; reader.readAsDataURL(file); } ///When the "SEND" button is pressed document.getElementById("send").onclick = function() { let files = document.getElementById('logo-img-input').files; ///=== register/update to DB /// If a file is selected if (files.length == 1) { ///===Resize the image -> Drawto the canvas -> Conversion process to blobe format (get and use previewImage image instead of file) let orgImage = document.getElementById('previewImage'); let orgWidth = orgImage.width; // Save the original width let orgHeight = orgImage.height; // Save the original height let targetImg = new Image(); targetImg.src = $("#previewImage").attr('src'); ///Specify the horizontal length as width, and calculate the reduced height while maintaining the aspect ratio. let width = 300; let ratio = width / targetImg.width; let height = targetImg.height * ratio; ///Draw to the canvas let canvas = $("#canvas"); let ctx = canvas[0].getContext('2d'); $("#canvas").attr("width", width); $("#canvas").attr("height", height); ctx.drawImage(targetImg, 0, 0, width, height); /// Get the image from canvas as base64 let base64 = canvas.get(0).toDataURL('image/jpeg'); /// Uncomment this section if you want to see the base64 values displayed. /// console.log("base64 : " , base64); /// Creating image data from base64 let barr, bin, i, len; bin = atob(base64.split('base64,')[1]); len = bin.length; barr = new Uint8Array(len); i = 0; while (i < len) { barr[i] = bin.charCodeAt(i); i++; } blob = new Blob([barr], {type: 'image/jpeg'}); /// Uncomment this section if you want to see the blob values displayed. // console.log("blob : " , blob); let file_path = "/anyPathOf/" + "/outputImg.jpg"; let logImgRef = firebase.storage().ref().child(file_path); logImgRef.put(blob).then(function(snapshot) { /// If you want to know the URL where the image is saved logImgRef.getDownloadURL().then(img_url => { console.log("savedImgUrl : ", img_url); /// Once the URL of the image is obtained, it is registered in the DB. /// [Any Codes] }); }); } //If no file is selected else { /// [Any Codes] } } I'm hoping this article will help some of you out! Please be nice to me on Twitter! https://twitter.com/funny_man_daa
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【2021】WEBから画像を縦横比を維持してリサイズし、Cloud Storage for Firebaseに送信する方法

【2021】WEBから画像をリサイズしてCloud Storage for Firebaseに送信する方法 ロゴ画像をリサイズしてCloud Storage for Firebaseに送信する処理を実装しました。 base64, blobへの変換などに少し苦戦したので、新たに実装する方が少しでも早く行いたい処理を実現できるように共有します。 index.html <!-- 任意画像ファイルボタン(自分の場合はファイル選択ボタンをデフォルトのボタンではなく画像にしているためこのような実装になっているが、通常のファイル機能でもOK。) --> <label> <!-- ▽見せる部分 --> <span class="filelabel" title="ファイルを選択"> <img src="images/camera.png"" width="32" height="26" alt="+画像"> <!-- 選択 --> </span> <!-- 本来の選択フォームは隠す --> <input type="file" name="datafile" id="logo-img-input" onChange="imgPreView(event)"> </label> <div id="preview"></div> <canvas id="for-resize-canvas" style="display: none;"></canvas> <button id="send" class="btn btn-primary">SEND</button> style="display: none; に関しては、これをつけないともう一つ画像のViewができてしまうので、canvasを非表示にしています。 表示をしなくともリサイズ処理は問題なくできますので。 index.js //===画像プレビュー function imgPreView(event) { let file = event.target.files[0]; let reader = new FileReader(); let preview = document.getElementById("preview"); let previewImage = document.getElementById("previewImage"); if(previewImage != null) { preview.removeChild(previewImage); } reader.onload = function(event) { let img = document.createElement("img"); img.setAttribute("src", reader.result); /// プレビューに表示する画像のサイズ(Warning : リサイズするサイズではない) img.width = 200; img.height = 200; img.setAttribute("id", "previewImage"); preview.appendChild(img); }; reader.readAsDataURL(file); } ///アカウント情報の登録ボタンが押されたら document.getElementById("send").onclick = function() { let files = document.getElementById('logo-img-input').files; //=== DBへ登録/更新 if (files.length == 1) { console.log("ファイル選択ありの場合"); ///===画像をリサイズ -> canvasへDraw -> blobe形式への変換処理(fileからではなくpreviewImageの画像を取得して使用する) let orgImage = document.getElementById('previewImage'); let orgWidth = orgImage.width; // 元の横幅を保存 let orgHeight = orgImage.height; // 元の高さを保存 let targetImg = new Image(); targetImg.src = $("#previewImage").attr('src'); //横の長さをwidthとして指定し、縦横比を維持した縮小後の高さを計算 let width = 300; let ratio = width / targetImg.width; let height = targetImg.height * ratio; //canvasに描画 let canvas = $("#canvas"); let ctx = canvas[0].getContext('2d'); $("#canvas").attr("width", width); $("#canvas").attr("height", height); ctx.drawImage(targetImg, 0, 0, width, height); /// canvasから画像をbase64として取得する let base64 = canvas.get(0).toDataURL('image/jpeg'); /// base64の値を表示して見る場合はここのコメントアウトを外す /// console.log("base64 : " , base64); /// base64から画像データを作成する let barr, bin, i, len; bin = atob(base64.split('base64,')[1]); len = bin.length; barr = new Uint8Array(len); i = 0; while (i < len) { barr[i] = bin.charCodeAt(i); i++; } blob = new Blob([barr], {type: 'image/jpeg'}); /// blobの値を表示して見る場合はここのコメントアウトを外す // console.log("blob : " , blob); let file_path = "/anyPathOf/" + "/outputImg.jpg"; let logImgRef = firebase.storage().ref().child(file_path); logImgRef.put(blob).then(function(snapshot) { /// 画像の保存先URLを知りたい場合 logImgRef.getDownloadURL().then(img_url => { console.log("savedImgUrl : ", img_url); /// 画像のURLが取得できたらDBへ登録の処理など /// [省略] }); }); } else { //===画像が設定されていない時の処理 /// [省略] } } 少しでも救われる方がいればと思います! Twitterでも仲良くしてください! https://twitter.com/funny_man_daa
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【vue.js】イベントを条件付きで発火させる

※この記事ではテンプレートについては全てpugで記載します。 コード button(@click='isClickable && clickEvent') 解説 isClickable が truthy なら clickEvent が発火します。 なぜここで論理式(&&)が出てくるのか?truthyとは何か?というのは以下の記事が大変参考になります。 参考: 3歳娘「いつから論理式が真偽値のみを返すと錯覚していた?」 ただ、button の @click の場合は以下のように disabled に条件を指定してやるほうが良いと思います。 button(@click='clickEvent', :disabled='!isClickable') 今回のような条件分岐は @mouseup など、少し特殊な用途で役に立つかもしれません。 参考 Conditional event binding - vuejs
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

readonlyのセレクトボックスを値変更不可にするjquery

概要 htmlのselectタグにreadonly属性をつけても、見た目それっぽくなるだけで変更できてしまう。 それを解決する方法の1つである「選択中以外の選択肢をすべてdisabledにする」の汎用コード。 本題 $(function(){}) の中に入れておけばいい感じにやってくれる $("form select[readonly] > option:not(:selected)").attr('disabled', 'disabled'); ポエムな背景 そこそこ汎用的だと思うのだけどそのへんに転がってると思ったら見当たらなくて久しぶりにセレクタ調べました
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jest + Property based Testing

はじめに 今読み進めている「関数型プログラミングの基礎 JavaScriptを使って学ぶ」の中で紹介されていた Property based testing に興味がわいたのでJavaScript環境で試した話です。 なお、Property based testing とはなんぞや という話については、本稿では詳しく触れません。そちらを知りたい方は、諸先輩方が投稿しているQiita記事などで調べて見てください。 環境 Node.js : v14.17.0 https://nodejs.org/ja/ npm : v6.14.13 jest : v27.0.1 https://jestjs.io/ja/ Facebook のオープンソース フルスタックテスティングフレームワーク fast-check : v2.14.0 https://dubzzz.github.io/fast-check.github.com/ Property based testing のフレームワーク jest-fast-check : v1.0.2 https://github.com/dubzzz/jest-fast-check jest と fast-check の統合用ライブラリ(記述を簡素化できるだけで必須ではない) 準備 作業ディレクトリを用意 (Node.js はインストール済みの前提です。) $ mkdir property-based-testing $ cd property-based-testing $ npm init --yes ライブラリをインストール $ npm install --save-dev jest fast-check jest-fast-check テスティングフレームワークの使用準備 編集前 package.json { "name": "property-based-testing", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "fast-check": "^2.15.0", "jest": "^27.0.3", "jest-fast-check": "^1.0.2" } } "scripts" の "test" を以下のように書き換える package.json抜粋 "scripts": { "test": "jest" }, 実践 題材 「関数型プログラミングの基礎 JavaScriptを使って学ぶ」 でも使われている 後者関数 を使って以下の3つのケースに分けて各テスティングフレームワークを使って行きます。 jest のみを使用 jest + fast-check を使用 jest + fast-check + jest-fast-check を使用 テスト対象 const succ = (x) => { return x + 1; }; テスト内容 上記関数において succ(0) + succ(x) = succ(succ(x)) という命題をProperty based testingを使って確認する 共通部分 3つのケースで共通となるテスト対象の関数を作成します。 $ mkdir src $ touch src/app.js src/app.js const succ = (x) => { return x + 1; }; module.exports = { succ, }; jest のみ使用 テストコードを追加します $ mkdir test $ touch test/app-jest.test.js test/app-jest.test.js const { succ } = require('../src/app'); const iterate = (init) => { return (step) => { return [init, (_) => { return iterate(step(init))(step); }]; }; }; /* 無限の整数列を生成する */ const enumFrom = (n) => { return iterate(n)(succ); }; /* ストリームのmap関数 */ const map = (transform) => { return (aStream) => { console.log(aStream); const head = aStream[0]; return [transform(head), (_) => { return map(transform)(aStream[1]()); }]; }; }; /* ストリームの先頭から引数n分だけ取り出す */ const take = (n) => { return (aStream) => { if (n === 0) { return null; } else { return [aStream[0], (_) => { return take(n-1)(aStream[1]()); }]; } }; }; /* ストリームの全ての要素がtrueであるか判定する */ const all = (aStream) => { const allHelper = (aStream, accumulator) => { const head = aStream[0]; const newAccumulator = accumulator && head; if (aStream[1]() === null) { return newAccumulator; } else { return allHelper(aStream[1](), newAccumulator); } }; return allHelper(aStream, true); }; /* 検証の対象となる命題 */ const proposition = (n) => { return succ(0) + succ(n) === succ(succ(n)); }; /* 100個の整数について命題が正しいか */ test( 'should succ(0) + succ(x) = succ(succ(x))', () => { expect( all( take(100)( map(proposition)(enumFrom(0)) ) ) ).toBe(true); } ); テストの実行は以下のコマンドで行います $ npm test 実行結果 上記のコードでは、1から100の100個の整数で命題が正しいかチェックしています このようにProperty based testingはフレームワークを使用しないでも実現することができます ただし、整数をランダムに生成したり、事前条件などを追加したりしようとすると柔軟性にかけます つまり、検証用のテストデータ生成部分の柔軟性に難がある(手間がかかる)ということです jest + fast-check を使用 では、Property based testingのフレームワークを使用した場合にどうなるかみてみます なお、fast-check の詳細は公式を確認してみてください $ touch test/app-fast-check.test.js test/app-fast-check.test.js const { succ } = require('../src/app'); const fc = require('fast-check'); test( 'should succ(0) + succ(x) = succ(succ(x))', () => fc.assert( fc.property( fc.integer({ min:1 }), (x) => { // console.log(x); expect(succ(0) + succ(x)).toBe(succ(succ(x))); } ) ) ); fast-checkの各関数の詳細については、公式およびGithubを参照してください 上記コードではfc.integer()が検証用のテストデータを生成している部分になります コメントアウトしてる部分を有効にすると、1以上のランダムな整数がテストデータとして使われていることが確認できます jest + fast-check + jest-fast-check を使用 fast-check を使うことにより、テストデータの生成がとてもシンプルに記述することができました ただ、fast-check のみ使用した場合だと、fc.assert()やfc.property()など、fast-check独自の記法が必要となり、jestとの記述の差異が生まれます この差異を埋めるのがjest-fast-checkです jest-fast-checkの詳細はGithubを参照してください jest-fast-checkを使ったコードが以下になります $ touch test/app-jest-fast-check.test.js test/app-jest-fast-check.test.js const { succ } = require('../src/app'); const { testProp, fc } = require('jest-fast-check'); testProp( 'should succ(0) + succ(x) = succ(succ(x))', fc.integer({ min:1 }), (x) => { expect(succ(0) + succ(x)).toBe(succ(succ(x))); } ); jest-fast-checkのソースコードを読むとわかりますが、 testProp()がfc.assert()とfc.property()をtest()内で実行するようなラッパーになっています testProp()を使うと記述量を抑え、よりjestの記法に近い形でProperty based testingのコードを記述できます 参考記事 今回Property based testingを学習する際にこちらの記事を参考にさせていただきました Property based testing を試してみよう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

外部のHTML,CSS,JavaScriptをRailsで表示させる。

自作のWebアプリケーションを作成するときに、あらかじめHTMLテンプレートでテンプレートを作成した後に、それをRailsのアプリケーションに反映させよう、と言う考えのもと、作成しました。 結論めっちゃ失敗した 簡単に失敗したところ記載します。 単純にassets/stylesheet/に配置 CSSは読み込んでくれました。 ただ中身に注意。 特にimage-urlなどrubyのcssと通常のcssで記法が異なる点があります。 ここまではまだ良かったです。 ネックはJavaScript もうこれは思い出したくないくらいエラーが出ました。 その度に解決方法を模索して、結果として下記を実行したら、解消されました。 ①jQueryの導入 これは割と初期でやりました。 ②複数のjsファイルを全部application.jsに転記 これは全然ダメでした。 ③application.jsの初期で記述されているrequireをコメントアウトして、importで追加のjsファイルを読み込む これが効果抜群でほぼ解決しました。 言ってもまだ全部解決してない まだ解決してないし、なぜ初期で記述されているrequireがダメだったのかは考察が必要ですが、一旦、やりたいことできたので、ほんとにホッとしてます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

外部のHTML,CSS,JavaScriptをRailsで表示させる(Turbolinksが原因っぽい)

自作のWebアプリケーションを作成するときに、あらかじめHTMLテンプレートでテンプレートを作成した後に、それをRailsのアプリケーションに反映させよう、と言う考えのもと、作成しました。 結論めっちゃ失敗した 簡単に失敗したところ記載します。 単純にassets/stylesheet/に配置 CSSは読み込んでくれました。 ただ中身に注意。 特にimage-urlなどrubyのcssと通常のcssで記法が異なる点があります。 ここまではまだ良かったです。 ネックはJavaScript もうこれは思い出したくないくらいエラーが出ました。 その度に解決方法を模索して、結果として下記を実行したら、解消されました。 ①jQueryの導入 これは割と初期でやりました。 ②複数のjsファイルを全部application.jsに転記 これは全然ダメでした。 ③application.jsの初期で記述されているrequireをコメントアウトして、importで追加のjsファイルを読み込む これが効果抜群でほぼ解決しました。 追記:Turbolinksが悪さをしていたっぽい 特にエラーが出ていた時はjQueryが読み込まれていないことが原因でした。 Uncaught TypeError: webpack_require(...) is not a functionが出ていました。 該当の行を確認すると、確かにjQueryが読み込まれていないことが原因。 で、調べてみるとTurbolinksが悪さをしている可能性が。 【Rails】初心者向け!画面遷移の高速化を行うTurbolinksについて図を用いて詳しく解説 Turbolinksは簡単に言うとページの表示を高速化させるためのライブラリです。 いわゆる非同期処理というものです。 しかし一方で、readyやwidowのイベントが発火しない、といったトラブルが発生しているようです。 今回はこれがほぼ可能性が高い。 なぜならapplication.jsに window.jQuery = window.$ = require("./jquery.min") というwindowのイベントを定義していたにもかかわらず、イベントが発火していなかったから。 特にRails4以降は標準で入っているライブラリなので、jsが読み込まない、ということであれば、デフォルトで入っているライブラリを一つずつコメントアウトしてみると良いかもしれません。 (もちろんページの表示速度は遅くなるだろうが。) 言ってもまだ全部解決してない まだ解決してないし、なぜ初期で記述されているrequireがダメだったのかは考察が必要ですが、一旦、やりたいことできたので、ほんとにホッとしてます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OpenCV.jsで動画から時間指定で画像を取得する方法

OpenCV.jsの公式ではビデオの処理をsetTimeoutを利用してループを組んで処理しています。 ここでreadに時間がかかったり、setTimeoutの微妙なズレなんかで、きっちり30FPS毎にフレームを取ろうと思っても取れないことがあります。 let src = new cv.Mat(height, width, cv.CV_8UC4); let dst = new cv.Mat(height, width, cv.CV_8UC1); let cap = new cv.VideoCapture(videoSource); const FPS = 30; function processVideo() { let begin = Date.now(); cap.read(src); cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY); cv.imshow("canvasOutput", dst); // schedule next one. let delay = 1000/FPS - (Date.now() - begin); setTimeout(processVideo, delay); } // schedule first one. setTimeout(processVideo, 0); 正確にフレーム画像を取る方法 フレームの取得はVideoCaptureで行っていますが、VideoCaptureは実際次のような実装になっています。 Module['VideoCapture'] = function(videoSource) { var video = null; if (typeof videoSource === 'string') { video = document.getElementById(videoSource); } else { video = videoSource; } if (!(video instanceof HTMLVideoElement)) { throw new Error('Please input the valid video element or id.'); return; } var canvas = document.createElement('canvas'); canvas.width = video.width; canvas.height = video.height; var ctx = canvas.getContext('2d'); this.video = video; this.read = function(frame) { if (!(frame instanceof cv.Mat)) { throw new Error('Please input the valid cv.Mat instance.'); return; } if (frame.type() !== cv.CV_8UC4) { throw new Error('Bad type of input mat: the type should be cv.CV_8UC4.'); return; } if (frame.cols !== video.width || frame.rows !== video.height) { throw new Error('Bad size of input mat: the size should be same as the video.'); return; } ctx.drawImage(video, 0, 0, video.width, video.height); frame.data.set(ctx.getImageData(0, 0, video.width, video.height).data); }; }; 内部でcanvasを準備してそこにHTMLVideoElementを指定してデータを書き込んでいます。 ctx.drawImage(video, 0, 0, video.width, video.height) コンテキストに書き込むためのデータはcanplayイベントのタイミングで取得することができます。 指定した時間のフレームを正確に取るには、HTMLElementのcurrentTimeを指定して、そのタイミングの画像が取れれば正確です。 currentTimeを新たに指定すると、バッファが読み込まれcanplayイベントが発火します。 このcanplayイベントをPromise化してthenないしawaitした後フレームデータを取得すれば正確に取ることができます。 let fps = 30 let step = 1.0 / fps let t = 0.0 let w = this.getWidth() let h = this.getHeight() let src = new cv.Mat(h, w, cv.CV_8UC4) for (let i = 0; i < num; i++) { this.setCurrentTime(t) await this.createPromise() this.read(src) cv.imshow("canvasOutput", src); src.delete() t += step } Promiseは次の関数で返します。 // プロミスを返す private createPromise(): Promise<void> { return new Promise<void>((resolve) => { const handler = () => { this.video?.removeEventListener("canplay", handler) resolve() } this.video?.addEventListener("canplay", handler) }) } これで正確に、この時間の画像を取得することができました。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js入門

始めに Vue.jsの基礎をまとめました。 初学者の方でも分かるように書いていますので、よければ参考にしてください! 参考記事 [URL]https://qiita.com/kiyokiyo_kzsby/items/ce9fe8b72953584fecee 環境 JSFiddle(HTML・CSS・JavaScriptを簡単に試せるオンラインIDE) Viuインスタンスの生成 ①  html側にVueを適応したい箇所に<div id="id名"></div>を記載する。(jsffidleでは、headやbodyタグがあらかじめ読み込まれているので、ここでは記載していない。) ② divタグ外に<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>と記載する。 これによりVue.jsを使用できるようになる。 詳細は公式ホームページ参照 https://jp.vuejs.org/v2/guide/installation.html#CDN ③new VueでVueインスタンスを生成し el でhtml側のどの部分でVueを適用するのか指定できます。 html <div id='app'> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> js new Vue ({ el: '#app' }) 変数 data jsに以下のように記述することで、data要素に変数を定義することができます。 html側に{{変数名}}とすることで、変数を表示できます。※{{}}はマスタッシュ構文 html <div id='app'> {{ message }} </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> js new Vue ({ el: '#app', data: [ message: '初めてのVue' ] }) ディレクティブ v-on  任意のイベントによってメソッドを実行させる Vueインスタンスにmethodsを記載することで、メソッドを使用できます。 ①html側にbuttonタグを配置し、v-on:アクション="アクション名"のようにv-onディレクティブを指定する。 これによりボタンをクリックした際に、②で作成するmethodsのbuttonメソッドを実行させることができる。 ②methods: {~~~}以下を記述することで、buttonメソッドを作成する。 ここではメソッドが実行された際、alertを表示させるようにしている。 html <div id='app'> {{ message }} <button v-on:click="button"> ここをクリック </button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> js new Vue ({ el: '#app', data: [ message: '初めてのVue' ], methods: { button: function { alert(); } } }) v-vind タグ属性の値をVueインスタンス内で定義した変数で表現する際に使います。 ①html側に、aタグを配置しv-bindディレクティブを指定する。(v-bind:属性="属性名") ②js側で、data内に変数url(属性名)を記述する。 ③リンクをクリックすると、任意のページを表示する。 html <div id='app'> <p><div id='app'> {{ message }} <button v-on:click="button"> ここをクリック </button> <a v-bind:href="url">Vue公式</a> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> js new Vue ({ el: '#app', data: [ message: '初めてのVue', url: 'https://jp.vuejs.org/v2/guide/installation.html#CDN' ], methods: { button: function { alert(); } } }) v-model 双方向データバインディング。 双方向データバインディングとはvue.jsの機能で、data変数とフォームの値を連動させることが出来ます。 v-bindでは変数を表示させるだけでしたが、v-modelではフォームの値を変更すると変数の値も変更されます。 ① html側で、<input>タグを記述しv-modelディレクティブを指定する。(v-model="変数名") v-modelの動作を確認する為、hello変数を{{ hello }}で表示させる。 ②js側で、hello変数を記述する。 ③フォームの値を変更すると、hello変数の値が変化していることが確認できる。 html <div id='app'> <input v-model="hello"> {{ hello }} </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> js new Vue ({ el: '#app', data: [ hello: 'こんにちは' ], }) v-for 配列を要素にマッピングする。 v-forはdata内で、記述した配列よhtml側で表示させることができます。 ①js側で、languages配列を作成する。 ②ulタグ内にliタグを作成する。 liタグにv-forディレクティブを指定する。(v-for="要素 in 配列名") ③<li v-for="la in languages"></li>内に{{ la }}と記述して配列の要素の表示させる。 html <div id='app'>  <ul> <li v-for="la in languages">{{ la }}</li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> js new Vue ({ el: '#app', data: [ languages: ['Ruby', 'PHP', 'Python'], ], methods: { button: function { alert(); } } }) 終わりに Vue.jsの基礎をまとめました。 次回は一歩進んだ内容をまとめるので良ければ見てください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

夢小説/名前変換小説でdocument.writeを使わずに変換する

夢小説界を牛耳っていた(?)DreamMakerさんの機能がたぶんもう使えなくなってしまう。 でも誰も書いてくれないなぁ、もうオワコンなのかなぁ… 私たちは迫害されて死にゆく運命なんや… 新しい仕組みを作ってくださってるサイトさんもありますが、 あんまり広まってるようには感じられない(まぁ他のサイトさん見ないから知らんけど) わいずあとりえさん…(検索避け)のシステムを利用しているのですが、 Javascript に長けていないので中身みても難しくて改変などができないのであった。 それで仕方ないので自分で書きました。誰かの参考になれば幸いであります。 ※ 私はちょっとプログラムを触るのでちょっと書けたけど、あんまりそういうのができない人には参考になるかもしれない程度であり、プロ(?)のひとが見たらクソみたいなコードかもしれないです。ゆるして。 やりたかったこと ローマ字名をいただいて、イニシャルを小説中で使うということがしたかった。 もう二度と使わないかもしれないから一時的にDream1(プロンプト式+document.write)を使ってたけど また使うかもしれないし長い目で見たらやっぱりdocument.writeは使いたくないので勉強することにした。 まあ、ここではとりあえずダイアログで一つ名前をいただいて とりあえずCookieに保存したり消したりする方法をご紹介いたします。 結論:innnerHTMLをつかう まぁ話が長くなるのでとりあえずHTMLを載せますから見てくださいよ。 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html lang="ja"> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta http-equiv="content-style-type" content="text/css"> <META NAME="GOOGLEBOT" CONTENT="NOINDEX, NOFOLLOW"> <META NAME="ROBOTS" CONTENT="NOARCHIVE,NOINDEX,NOFOLLOW"> <title>名前変換テスト1</title> </head> <body> <!-- ■ ■ ■ ↓ わからない人向け/ここからコピー(1) ↓ ■ ■ ■ --> <script language="JavaScript"> <!-- var fname; //今回覚えさせたい変換名を入れる変数のなまえ function GetCookie(name){ var result = null; var cookieName = name + '='; var allcookies = document.cookie; var position = allcookies.indexOf( cookieName ); if( position != -1 ){ var startIndex = position + cookieName.length; var endIndex = allcookies.indexOf( ';', startIndex ); if( endIndex == -1 ){ endIndex = allcookies.length; } result = decodeURIComponent( allcookies.substring( startIndex, endIndex ) ); } return result; } function DeleteCookie(){ document.cookie = 'firstName=;max-age=0'; location.reload(); } fname = GetCookie("firstName"); //fnameが空だったときダイアログを出す if(fname == null || fname == ""){ fname = prompt("名前を教えてくださいね"); if(fname == null || fname ==""){ fname = "花子"; } //Cookie書き込み var expires = new Date(); expires.setTime(expires.getTime() + (6 * 30 * 24 * 60 * 60 * 1000)); document.cookie = 'firstName='+fname+'; expires='+expires.toUTCString(); } // --> </script> <!-- ■ ■ ■ ↑ わからない人むけ/ここまでコピー(1) ↑ ■ ■ ■ --> なまえ:<span class="yourfirstname"></span> <a href="#" onClick="DeleteCookie()">【変更する】</a><br /> <br /> <br /> <br /> 「あなたの名前は<span class="yourfirstname"></span>さんですか?」<br /> 「はいそうです」<br /> <br /> ~ 完 ~<br /> <!-- ■ ■ ■ ↓ わからない人むけ/ここからコピー(2) ↓ ■ ■ ■ --> <script language="JavaScript"> <!-- var names = document.getElementsByClassName('yourfirstname'); for (i = 0; i < names.length; i++) { names[i].innerHTML = fname; } //--> </script> <!-- ■ ■ ■ ↑ わからない人むけ/ここまでコピー(2) ↑ ■ ■ ■ --> </body></html> 上記コードのサンプルが、下記URLの内容です。(スタイルとかは上のは省いてる) ほんとうに分からないひと向け HTML自体がわからないひとは…、勉強してくれ… 1:ここからコピー(1)と書いてあるのの中身を<body>のすぐ下に貼る 2:ここからコピー(2)と書いてあるのの中身を</body>のすぐ上に貼る 3:名前部分を<span class="yourfirstname"></span>に変換する ふたつ以上(苗字と名前とか)変換したいひとは、言ってください。 ちょっとなら分かるというひとは↓のおまけが参考になるかもしれない… やってること解説と言い訳(不親切) メインのJavascriptコードはbody内に書いたけど(最初に参考にしたDreamMakerさんのがそうだったので…)headの中でもいいのかもしれない(Javascriptに明るくないのでよくしらない) firstName, fname, yourfirstnameなど適当に変数とかの名前をつけたけど、どれとどれが同じかを差別化する目的であって深い意味はない function GetCookie() → クッキーの使用方法 | JavaScript プログラミング解説 さんの「指定クッキーの値の取得」を丸々コピーさせていただいた。ほしいcookieの変数名?を入れるとその値を返してくれる。今回はfirstNameという名前のCookieの変数をGetしたので、そこに入ってる名前を返してもらえることになった function DeleteCookie() → 呼び出すと、firstNameを消してCookieの有効期限を0にすることでクッキーを削除する その下では、初めて(というかCookieがまだない際)ファイルにアクセスしたときとかDeleteCookieの直後とか(とにかく名前がまだないとき)にifの中を通って、ダイアログを出してfnameの中に入力された名前を代入します。なんにも入力されないとデフォルトで「花子」が入ります。その下はそのまんまですがCookieを書き込む文です。 Webページとして表示される部分 → spanタグのclass名でyourfirstnameを指定すると、そこの中身が、今回はCookieが覚えている名前で置きかわる。【変更する】の部分は押すとDeleteCookie()が呼び出されます(現在の名前を消して新しく入力させる)。 body下部のJavascript → document.getElementsByClassName()とかいう便利な関数があったんやなあ…。詳しく調べてないけどそのファイル中にあるyourfirstnameという名前のクラスがついてるとこの何かをgetしてnamesに入れれるんでしょうね(てきとう)(ほぼこちら→ページ内の特定クラスに属する要素をJavaScriptで置き換える方法 | TechMemo さんのコピペ) その次でnames.lengthというかんじに総数を取得することができるようなので、yourfirstnameをいくつあるか取得してfor文でひとつずつfname(変換名)に置き換えているみたいです。 以上! おまけ:私のやりたかったイニシャルのやつ ローマ字で入力すると頭文字を取得するやつ(べつに日本語でも1文字目を取るんですけど…) 間違ってたらアレだからスタイルもそのまんま載せるぜ! 変数名がクソすぎですが… <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html lang="ja"> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta http-equiv="content-style-type" content="text/css"> <META NAME="GOOGLEBOT" CONTENT="NOINDEX, NOFOLLOW"> <META NAME="ROBOTS" CONTENT="NOARCHIVE,NOINDEX,NOFOLLOW"> <title>名前変換テスト2</title> <style type="text/css"> <!-- body{ padding: 100px; font-family: 'MS UI GOTHIC'; letter-spacing: 3px; line-height: 150%; } br{ letter-spacing: normal; } --> </style> </head> <body> <script language="JavaScript"> <!-- var eigo; //今回覚えさせたい英語名を入れる変数のなまえ function GetCookie(name){ var result = null; var cookieName = name + '='; var allcookies = document.cookie; var position = allcookies.indexOf( cookieName ); if( position != -1 ){ var startIndex = position + cookieName.length; var endIndex = allcookies.indexOf( ';', startIndex ); if( endIndex == -1 ){ endIndex = allcookies.length; } result = decodeURIComponent( allcookies.substring( startIndex, endIndex ) ); } return result; } function DeleteCookie(){ document.cookie = 'eigoName=; max-age=0'; location.reload(); } eigo = GetCookie("eigoName"); //eigoが空だったときダイアログを出す if(eigo == null || eigo == ""){ eigo = prompt("おなまえをローマ字で"); if(eigo == null || eigo ==""){ eigo = "HANAKO"; } //Cookie書き込み var expires = new Date(); expires.setTime(expires.getTime() + (6 * 30 * 24 * 60 * 60 * 1000)); document.cookie = 'eigoName='+eigo+'; expires='+expires.toUTCString(); } // --> </script> <div style="width:750px; margin-left:auto; margin-right:auto;"><font size="3"> 英語名:<span class="english"></span> <a href="#" onClick="DeleteCookie()">【変更する】</a><br /> <br /> <br /> <br /> <span class="english"></span>さん<br /> イニシャルは <span class="inisyaru"></span><br /> <script language="JavaScript"> <!-- var name1 = document.getElementsByClassName("english"); var name2 = document.getElementsByClassName("inisyaru"); for (i = 0; i < name1.length; i++) { name1[i].innerHTML = eigo; } for (i = 0; i < name2.length; i++) { name2[i].innerHTML = eigo.slice(0, 1); } // --> </script> </font></div></body></html> おわりに 複数変換とかDreamMaker2?みたいなフォーム式とかもできると思いますが、 もしも万が一ご要望があったら考えますね。。 とりあえず今回はこれだけ。 参考 HTML要素の中身を変えるinnerHTML | JavaScript入門編 - ウェブプログラミングポータル クッキーの使用方法 | JavaScript プログラミング解説 ページ内の特定クラスに属する要素をJavaScriptで置き換える方法 | TechMemo
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[雑談]プログラマー、エンジニアの2社目としての振り返り

久々の更新になります。 今、派遣エンジニアとして働いてます。(登録制の派遣です。) 今回は6月末で現場を終了することになりましたので(9か月間 契約満了)、 『現場で吸収できたこと』の"振り返り"をしたいなと思います。 まずは、現場の方々お世話になりました。またありがとうございました!! ★★契約終了理由★★ ①コロナでお客さんの売上下がって受注が厳しくなりお客さんとの契約もどうするか状態であること ②契約が継続するかのタイミングだった ③担当していた案件が利益が低いことが 理由みたいです。。。 担当したシステムは、 1. 中小企業診断士向けのWEBシステム 2. 映画館チケットのWEB販売システム(複数社)/受託パッケージシステム の保守開発を担当しました。 担当工程は、 詳細設計、実装、単体テスト、結合テスト、総合テスト、リリース作業、保守(バク、エラー調査、改修) 使用言語ですが、 Java Javascript HTML JSP SQL を経験できました。 次にフレームワークは、 1. JSF 2. struts 3. jQuery ツールに関しては、 1. Eclipse 2. コマンドプロンプト 3. Teraterm 4. Apache tomcat 5. Oracle 6. A5:SQL Mk-2 7. Zoom 8. teams 9. Backlog 10. SVN 11. powershell 12. git 13. AWS(Elastic Load Balancing等) 14. MySQL 使用しました。 業務内容に関しては、 1. システム調査(エラー、バグ) 2. 追加実装 3. プログラム修正 4. ページ作成 5. 動作確認 6. リファクタリング 7. ドキュメント作成 8. テスト業務 9. リリース作業のサポート 全体的な感想として、 入ったばかりの時は、テストが多かった印象です。 受託開発の会社だったので(系列会社はSierの大手会社)、 別の部隊のシステムやアプリのテスト作業などのサポートに入った思い出があります。(バグやデグレがひどくかなり予定より遅れてる案件に入りました。汗) 比率的に テスト : 実装and詳細設計 = 7 : 3 の割合でした。 2か月くらい過ぎてくらいから、 比率が テスト : 実装and詳細設計 = 3 : 7  の割合になりました。 紹介している現場の前の現場も、 フロントエンド側だったこともあり(たまにバックエンドもやってました)、 今回もフロントエンド側の追加実装とか改修はほぼほぼこなしました。 バックエンドも少し追加実装したり、改修作業やりました。 あとは、Excelで実装したあとの資料作り(詳細設計andテストエビデンス)作りは初めてやったので、(自社開発でやったときはjira softwareのwikiに軽く記載程度でしたので)いい経験になったかなと思ってます。 古い技術や環境の現場ではありましたが、 周りの方は優しい方多かったです。 エンジニアは細かいところに気付くが故に、 1. マウンティングしてしまう方 2. 表現が言葉足らずになり切れてしまう人 3. 知識やスキルがわからない人を置いてけぼりにする人 多いので、この現場では比較的少なかったですね。。。笑 逆に細かいところに気付くことで、 1. バグが起きにくい 2. コミュニケーションが行き届く?笑 3. トラブルが起きたとき対応できる 等々みたいなメリットもあると思いますが、やりにくいので助かりました。 話はそれましたが、 今回は要件定義、基本設計以外は関わることができたかなと思います。 次は、Go言語勉強しているのでGo言語の現場で働きたいのが希望です。 とはいえ無理なら他の言語や今まで使ってきた言語でもよいかなと....。 ※プライベートPC(DELLのWindows10 4年強くらい使用)が調子悪くなりました。 なので、PCをMacに変更予定です。買ったら、本格的にWEBアプリケーション(チャットツールっぽいもの)開発予定です。。 今回、2社目(厳密に言うと5社目。最初の3つはSES事業で"経歴詐称"や"客先は面談内容と全く違う事させる"、"パワハラ、モラハラ、セクハラは当たり前"等々があるヤバい会社でした。すぐ辞めました。汗)でしたが、よかったです。 IT関連の会社を選びときは、慎重に!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ajaxでファイルダウンロード

jqueryでshiftjisでできたファイルのダウンロードのやり方。 $ajaxだとうまくいかなかったけどこれならうまくいった fetch(url, {method: 'GET'}) .then((res) => { if (!res.ok) { throw new Error(`${res.status} ${res.statusText}`); } return res.blob(); }) .then((blob) => { const url = URL.createObjectURL(blob); const a = document.createElement("a"); document.body.appendChild(a); a.download = filename; a.href = url; a.click(); a.remove(); setTimeout(() => { URL.revokeObjectURL(url); }, 1E4); }) .catch((reason) => { console.error(reason); alert('予期せぬエラーが発生しました。'); });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SendGridを用いて複数宛先に宛先毎の本文をカスタマイズしメールを送信する方法

概要 SendGridで複数の宛先かつ宛先毎に本文の内容をカスタマイズし、自動でメールを送信する方法を紹介します。 kintone WebhookとAWS Lamdaの連携方法をより詳しく説明する!で紹介したKintoneの保存イベント時にAWS LamdaからSendGridのAPIを呼び出すことを想定しておりますが、このケース以外でも共通の部分はあるかと思います。 基本的にはSendGrid公式ライブラリ(Node.js)を使用して作成しました。 https://github.com/sendgrid/sendgrid-nodejs#usage メール送信の実行ファイル 'use strict'; const sgMail = require('@sendgrid/mail'); /** * 対象のユーザにメールの送信 */ async function sendMail(event) { // AWS Lamdaの環境変数に登録したSendGridのAPIキーを取得 sgMail.setApiKey(process.env.SENDGRID_API_KEY); // kintoneから送信された値の取得 event.body = JSON.parse(event.body); const record = event.body.record; // 宛先1 const to1 = record.to1.value; // 宛先2 const to2 = record.to2.value; // メール本文 // to1に送るメール(htmlメール用) const html1 = '<p>こんにちは</p>' + '<p>いつもありがとうございます!</p>'; // to1に送るメール(textメール用) const text1 = 'こんにちは\n' + 'いつもありがとうございます!'; // to2に送るメール(htmlメール用) const html2 = '<p>はじめまして</p>' + '<p>よろしくお願いします!</p>'; // to2に送るメール(textメール用) const text2 = 'はじめまして\n' + 'よろしくお願いします!'; let messageFields = { // 件名 subject: 'サンプル', // 送信元のメールアドレス from: process.env.FROM_MAIL_ADDRESS }; messageFields['personalizations'] = [ // 宛先1用の宛先とメール本文を設定 { to: to1, substitutions: { htmlMessage: html1, textMessage: text1 } }, // 宛先2用の宛先とメール本文を設定 { to: to2, substitutions: { htmlMessage: html2, textMessage: text2 } } ]; messageFields['html'] = '%htmlMessage%'; // htmlメール文 messageFields['text'] = '%textMessage%'; // textメール文 messageFields['substitutionWrappers'] = ['%', '%'];// 置換タグの指定 await sgMail .send(messageFields) .then(() => { console.log('OK'); }) .catch((error) => { console.log('NG'); throw error; }); } /** * メイン処理 */ async function main(event, context) { try { // 送信 await sendMail(event); } catch (error) { console.log('main:NG'); throw error; } } 解説 環境変数の設定 // AWS Lamdaの環境変数に登録したSendGridのAPIキーを取得 sgMail.setApiKey(process.env.SENDGRID_API_KEY); Lamdaの設定タブから環境変数が設定できるため、そこから取得します。 宛先毎のカスタマイズ messageFields['personalizations'] = [ // 宛先1用の宛先とメール本文を設定 { to: to1, substitutions: { htmlMessage: html1, textMessage: text1 } }, // 宛先2用の宛先とメール本文を設定 { to: to2, substitutions: { htmlMessage: html2, textMessage: text2 } } ]; personalizationsを使用して宛先毎のメールアドレスと本文を設定します。 メール本文のカスタマイズ messageFields['html'] = '%htmlMessage%'; // htmlメール文 messageFields['text'] = '%textMessage%'; // textメール文 messageFields['substitutionWrappers'] = ['%', '%'];// 置換タグの指定 personalizationsのsubstitutionsで宛先毎に本文を設定しておき、設定した本文をmessageFieldsに設定します。%で囲ったキーワードにsubstitutionsを埋める仕組みとなっています。例えば、htmlMessageに「い」と設定し、「あ%htmlMessage%う」となっていたならば、「あいう」となります。宛先毎のメール本文が大きく異なる場合は最初と最後に%で囲む方法が良いかと思います。 余談 AWS Lamdaで実装を進める際はconsole.log()でログを吐き出しながら進めました。普段ディベロッパーツールを使用してデバックをしているためなかなか苦労しました。。console.log()はこまめに設置して進めるのがよいかと思います! 参考サイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

インタープリタを作る その16

概要 インタープリタを作ってみた。 avrインタープリター書いてみた。 インとアウトを取り付ける。 方針 アウトは、レジスタ31を監視する。 インは、内部表現に、合わせる。 インの実装 レジスタの内部表現が [false, false, false, false, false, false, false, false] なので、変換する関数を書いた。 function hen(x) { var a, b, c, d, e, f, g, h; x = x.charCodeAt(0); if ((x & 0x01) > 0) { a = true; } else { a = false; } if ((x & 0x02) > 0) { b = true; } else { b = false; } if ((x & 0x04) > 0) { c = true; } else { c = false; } if ((x & 0x08) > 0) { d = true; } else { d = false; } if ((x & 0x10) > 0) { e = true; } else { e = false; } if ((x & 0x20) > 0) { f = true; } else { f = false; } if ((x & 0x40) > 0) { g = true; } else { g = false; } if ((x & 0x80) > 0) { h = true; } else { h = false; } K = [a, b, c, d, e, f, g, h]; return K; } アウトの実装 レジスタ31の変換関数、書いた。 function kan(K) { var x = 0; if (K[0]) x += 0x01; if (K[1]) x += 0x02; if (K[2]) x += 0x04; if (K[3]) x += 0x08; if (K[4]) x += 0x10; if (K[5]) x += 0x20; if (K[6]) x += 0x40; if (K[7]) x += 0x80; return String.fromCharCode(x); } 投入したソース avr.ldi(31, 'H') avr.ldi(31, 'e') avr.ldi(31, 'l') avr.ldi(31, 'l') avr.ldi(31, 'o') avr.ldi(31, ' ') avr.ldi(31, 'w') avr.ldi(31, 'o') avr.ldi(31, 'r') avr.ldi(31, 'l') avr.ldi(31, 'd') avr.ldi(31, '!') 結果 Hello world! 成果物 以上。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptの読み込まれる・実行されるタイミング

リクエスト→レスポンス→レンダリングまで [ブラウザのネットワークレイヤー] ①ブラウザがサーバーにリクエスト(HTMLとCSSとJSをおくれ) ②サーバーがHTMLファイルを生成(作るから待って) ③レスポンスが返ってくる(HTMLとCSSとJSファイルをダウンロード) [レンダリングエンジン] ④最初にhtmlを読み込むと、windowオブジェクトが生成される(widowオブジェクトは各ページごとにあるお) ⑤windowオブジェクトのプロパティとしてDocumentオブジェクトがある。DocumentオブジェクトがhtmlをパースしてDOMツリーを作っていこうとする 読み込み状況を示す文字列を返すreadyStateプロパティがあり、この時の文字列はloading ⑥パースはhtmlの上から順に行われていく。div, h1, ul, liなどの各要素はNodeと呼ばれる。レンダリングエンジンはNodeをDOMツリーに追加していく(documentオブジェクトが根Node) ⑦この時、script要素があるとそのコードをパースし、エラーがなければ同期的に実行する。つまり、JavaScriptの実行が終わるまで、htmlのパースが一時停止される。ここのJSの実行はJavaScriptエンジンで行われる。 ⑧htmlが全てパースされ、DOMツリーが完成すると、readyStateプロパティはinteractiveになる ⑨ブラウザはDocumentオブジェクトに対して、DOMツリーの構築完了を告げるDOMContentLoadedイベントを発生させる ⑩imgなどの外部ソースを読み込み、すべての読み込みが完了した時点でdocument.readyStateプロパティはcomplateになる 最後にブラウザは、Windowオブジェクトに対してloadイベントを発生させる [JavaScriptエンジン] JavaScriptの実行は同期的に行われると言ったが、非同期に行う方法も存在する。 ①ASYNC、DEFERこの辺りがよく使われるんじゃないか 非同期に実行するため、HTMLのレンダリングが一時停止せずに、ユーザーに少しずつ画面を表示(線画)できる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Plotly.jsのEvent Handlerを使う

先日Plotly.jsに入門したんですが、ただグラフを描くだけならJavaScriptのライブラリを使う必要はないわけで、やっぱりEvent Handler使って動的にグラフを変化させてナンボだろうという事でEvent Handler関係のサンプルコードを勉強したまとめです 入門編の記事は以下です https://qiita.com/studio_haneya/items/b689b4c27acbd12a888d やり方 以下の2つのやり方があるようです 1. Plotly.jsのEventHandlerを使う 2. updatemenusに動作を指定する 1. Plotly.jsのEventHandlerを使う Ploly.jsイベントを受け取って関数を実行するよく見るやり方です Plotly.js公式のEvent Referenceのページに使用例がいろいろ載ってます https://plotly.com/javascript/plotlyjs-events/ 1-1. 基本の使い方 Plotly.jsグラフの書き出し先になっている要素のon()メソッドにPlotly.jsイベントを指定してやればPlotly.jsグラフ上で起こったイベントに対応して関数を実行してくれます 以下の場合は'plotly_click'でalert()を呼ぶように指定しています event1.html <head> <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> </head> <body> <div id="myDiv"><!-- Plotly chart will be drawn here --></div> <script> var myPlot = document.getElementById('myDiv'), x = [1, 2, 3, 4, 5], y = [10, 20, 30, 20, 10], data = [{x:x, y:y, type:'scatter', mode:'markers', marker:{size:20} }], layout = {hovermode:'closest', title:'Click on Points' }; Plotly.newPlot('myDiv', data, layout); myPlot.on('plotly_click', function(){ alert('You clicked this Plotly chart!'); }); </script> </body> 1-2. グラフの表示を変える 既に表示しているグラフの表示内容を変更するには以下の2つのやり方があるようです 1. Plotly.restyle()に変更内容を与える 2. Plotly.newPlot()で上書きする 以下はクリックしたときにrestyle()を実行して色を変え、ダブルクリックしたときにrestyle()で元の色に戻す例です <head> <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> <script src="https://d3js.org/d3.v6.min.js"></script> </head> <body> <div id="myDiv"><!-- Plotly chart will be drawn here --></div> <script> var myPlot = document.getElementById('myDiv'), x = [1, 2, 3, 4, 5, 6], y = [1, 2, 3, 2, 3, 4], colors = ['#00000','#00000','#00000', '#00000','#00000','#00000'], data = [{x:x, y:y, type:'scatter', mode:'markers', marker:{size:16, color:colors}}], layout = { hovermode:'closest', title:'Click on a Point to Change Color<br>Double Click (anywhere) to Change it Back' }; Plotly.newPlot('myDiv', data, layout); myPlot.on('plotly_click', function(data){ var pn='', tn='', colors=[]; for(var i=0; i < data.points.length; i++){ pn = data.points[i].pointNumber; tn = data.points[i].curveNumber; colors = data.points[i].data.marker.color; }; colors[pn] = '#C54C82'; var update = {'marker':{color: colors, size:16}}; Plotly.restyle('myDiv', update, [tn]); }); myPlot.on('plotly_doubleclick', function(data){ console.log('double click'); var orgColors = ['#00000','#00000','#00000', '#00000','#00000','#00000']; var update = {'marker':{color: orgColors, size:16}}; Plotly.restyle('myDiv', update); }); </script> </body> 1-3. イベントいろいろ クリック myPlot.on('plotly_click', function(){}) ダブルクリック myPlot.on('plotly_doubleclick', function(){}) 凡例クリック myPlot.on('plotly_legendclick', function(){}) マウスホバー myPlot.on('plotly_hover', function(){}) マウスホバーが外れた myPlot.on('plotly_unhover', function(){}) グラフ要素が選択された myPlot.on('plotly_selected', function(){}) グラフが表示完了した myPlot.on('plotly_afterplot', function(){}) 凡例クリック myPlot.on('plotly_legendclick', function(){}) 凡例クリック myPlot.on('plotly_legendclick', function(){}) 1-4. 作例いろいろ 1-4-1. 凡例クリックを利用した例 点をクリックしたら色が付き、凡例をクリックすると元に戻る例です <head> <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> </head> <body> <div id="myDiv"><!-- Plotly chart will be drawn here --></div> <script> var myPlot = document.getElementById('myDiv'), x = [1, 2, 3, 4, 5, 6], y = [1, 2, 3, 2, 3, 4], y2 = [1, 4, 7, 6, 1, 5], colors = [['#5C636E','#5C636E','#5C636E','#5C636E','#5C636E','#5C636E'], ['#393e46','#393e46','#393e46','#393e46','#393e46','#393e46']], data = [{x:x, y:y, type:'scatter', mode:'line', line:{ color:'#5C636E'},marker:{size:16, color:colors[0]}}, {x:x, y:y2, type:'scatter', mode:'line',line:{ color:'#393e46'}, marker:{size:16, color:colors[1]}}], layout = { showlegend: true, hovermode:'closest', title:'Click on a Point to Change Color<br>Click on a Trace in the Legend to Change Back One Trace Only' }; Plotly.newPlot('myDiv', data, layout); myPlot.on('plotly_click', function(data){ var pn='', tn='', colors=[]; for(var i=0; i < data.points.length; i++){ pn = data.points[i].pointNumber; tn = data.points[i].curveNumber; colors = data.points[i].data.marker.color; }; colors[pn] = '#C54C82'; var update = {'marker':{color: colors, size:16}}; Plotly.restyle('myDiv', update,[tn]); }); myPlot.on('plotly_legendclick', function(data){ var trColors = [['#5C636E','#5C636E','#5C636E','#5C636E','#5C636E','#5C636E'], ['#393e46','#393e46','#393e46','#393e46','#393e46','#393e46']]; var update = {'marker':{color: trColors[data.curveNumber], size:16}}; Plotly.restyle('myDiv', update,[data.curveNumber]); return false; }); </script> </body> 1-4-2. hoverで色を変える例 hoverで色を変え、hoverが外れると色が戻る例です <head> <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> </head> <body> <div id="myDiv"><!-- Plotly chart will be drawn here --></div> <script> var myPlot = document.getElementById('myDiv'), x = [1, 2, 3, 4, 5, 6, 7], y = [1, 2, 3, 2, 3, 4, 3], colors =['#00000','#00000','#00000', '#00000','#00000','#00000', '#00000'], data = [{x:x, y:y, type:'scatter', mode:'markers', marker:{size:16, color:colors}}], layout = { hovermode:'closest', title:'Hover on a Point<br>to Change Color' }; Plotly.newPlot('myDiv', data, layout); myPlot.on('plotly_hover', function(data){ var pn='', tn='', colors=[]; for(var i=0; i < data.points.length; i++){ pn = data.points[i].pointNumber; tn = data.points[i].curveNumber; colors = data.points[i].data.marker.color; }; colors[pn] = '#C54C82'; var update = {'marker':{color: colors, size:16}}; Plotly.restyle('myDiv', update, [tn]); }); myPlot.on('plotly_unhover', function(data){ var pn='', tn='', colors=[]; for(var i=0; i < data.points.length; i++){ pn = data.points[i].pointNumber; tn = data.points[i].curveNumber; colors = data.points[i].data.marker.color; }; colors[pn] = '#00000'; var update = {'marker':{color: colors, size:16}}; Plotly.restyle('myDiv', update, [tn]); }); </script> </body> 1-4-3. 選択範囲のヒストグラムを描く例 plotly_selectedイベントを取得して選択されたデータのみを対象にヒストグラムを描きます <head> <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> </head> <body> <div id="myDiv"><!-- Plotly chart will be drawn here --></div> <script> var graphDiv = document.getElementById('myDiv'); var N = 1000; var color1 = '#7b3294'; var color1Light = '#c2a5cf'; var colorX = '#ffa7b5'; var colorY = '#fdae61'; function randomArray() { var out = new Array(N); for(var i = 0; i < N; i++) { out[i] = Math.random(); } return out; } var x = randomArray(); var y = randomArray(); Plotly.newPlot(graphDiv, [{ type: 'scatter', mode: 'markers', x: x, y: y, xaxis: 'x', yaxis: 'y', name: 'random data', marker: {color: color1, size: 10} }, { type: 'histogram', x: x, xaxis: 'x2', yaxis: 'y2', name: 'x coord dist.', marker: {color: colorX} }, { type: 'histogram', x: y, xaxis: 'x3', yaxis: 'y3', name: 'y coord dist.', marker: {color: colorY} }], { title: 'Lasso around the scatter points to see sub-distributions', dragmode: 'lasso', xaxis: { zeroline: false, }, yaxis: { domain: [0.55, 1], }, xaxis2: { domain: [0, 0.45], anchor: 'y2', }, yaxis2: { domain: [0, 0.45], anchor: 'x2' }, xaxis3: { domain: [0.55, 1], anchor: 'y3' }, yaxis3: { domain: [0, 0.45], anchor: 'x3' } }); graphDiv.on('plotly_selected', function(eventData) { var x = []; var y = []; var colors = []; for(var i = 0; i < N; i++) colors.push(color1Light); eventData.points.forEach(function(pt) { x.push(pt.x); y.push(pt.y); colors[pt.pointNumber] = color1; }); Plotly.restyle(graphDiv, { x: [x, y], xbins: {} }, [1, 2]); Plotly.restyle(graphDiv, 'marker.color', [colors], [0]); }); </script> </body> 1-4-4. グラフの表示完了イベントを使う例 グラフを表示完了時にconsole.log()する例です <head> <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> <script src="https://d3js.org/d3.v6.min.js"></script> </head> <body> <div id="myDiv"><!-- Plotly chart will be drawn here --></div> <script> var myPlot = document.getElementById('myDiv'), N = 20, x = [1, 2, 3, 4, 5, 6, 7], y = [1, 2, 3, 2, 3, 4, 3], data = [{x:x, y:y, type:'scatter', mode:'markers', marker:{size:14}} ]; Plotly.newPlot('myDiv', data); myPlot.on('plotly_afterplot', function(){ console.log('done plotting'); }); </script> </body> 2. updatemenusに動作を指定する Plotly.newPlot()に渡すlayoutにupdatemenus要素を設定しておくことで、ボタンやドロップダウンを配置してグラフの表示情報やスタイルを変更する事が出来ます 詳しくはAPIリファレンスを参照してください https://plotly.com/javascript/reference/layout/updatemenus/ 公式ページに例も載ってます https://plotly.com/javascript/dropdowns/#add-two-dropdown-menus-to-a-chart-with-plotly.js 2-1. ドロップダウンを配置する例 typeを省略するか、'dropdown'にすることでドロップダウンを配置して操作できます <head> <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> <script src="https://d3js.org/d3.v6.min.js"></script> </head> <body> <div id="myDiv"><!-- Plotly chart will be drawn here --></div> <script> function makeTrace(i) { return { y: Array.apply(null, Array(10)).map(() => Math.random()), line: { shape: 'spline' , color: 'red' }, visible: i === 0, name: 'Data set ' + i, }; } Plotly.newPlot('myDiv', [0, 1, 2, 3].map(makeTrace), { updatemenus: [{ type: 'dropdown', y: 0.8, yanchor: 'top', buttons: [{ method: 'restyle', args: ['line.color', 'red'], label: 'red' }, { method: 'restyle', args: ['line.color', 'blue'], label: 'blue' }, { method: 'restyle', args: ['line.color', 'green'], label: 'green' }] }, { type: 'dropdown', y: 1, yanchor: 'top', buttons: [{ method: 'restyle', args: ['visible', [true, false, false, false]], label: 'Data set 0' }, { method: 'restyle', args: ['visible', [false, true, false, false]], label: 'Data set 1' }, { method: 'restyle', args: ['visible', [false, false, true, false]], label: 'Data set 2' }, { method: 'restyle', args: ['visible', [false, false, false, true]], label: 'Data set 3' }] }], }); </script> </body> 2-2. ボタンを配置する例 typeのところをbuttonsに変えればボタンにすることも出来ます <head> <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> <script src="https://d3js.org/d3.v6.min.js"></script> </head> <body> <div id="myDiv"><!-- Plotly chart will be drawn here --></div> <script> // drop down1 function makeTrace(i) { return { y: Array.apply(null, Array(10)).map(() => Math.random()), line: { shape: 'spline' , color: 'red' }, visible: i === 0, name: 'Data set ' + i, }; } Plotly.newPlot('myDiv', [0, 1, 2, 3].map(makeTrace), { updatemenus: [{ type: 'buttons', y: 0.4, yanchor: 'top', buttons: [{ method: 'restyle', args: ['line.color', 'red'], label: 'red' }, { method: 'restyle', args: ['line.color', 'blue'], label: 'blue' }, { method: 'restyle', args: ['line.color', 'green'], label: 'green' }] }, { type: 'buttons', y: 1, yanchor: 'top', buttons: [{ method: 'restyle', args: ['visible', [true, false, false, false]], label: 'Data set 0' }, { method: 'restyle', args: ['visible', [false, true, false, false]], label: 'Data set 1' }, { method: 'restyle', args: ['visible', [false, false, true, false]], label: 'Data set 2' }, { method: 'restyle', args: ['visible', [false, false, false, true]], label: 'Data set 3' }] }], }); </script> </body> 2-3. csvを読み込んで表示する例 d3を使ってCSVを読んで表示する例です 公式ページのコードだと動かなかったので少し変更しました <head> <script src="https://cdn.plot.ly/plotly-latest.min.js"></script> <script src="https://d3js.org/d3.v6.min.js"></script> </head> <body> <div id="myDiv"><!-- Plotly chart will be drawn here --></div> <div class="showcase__section" id="bubble"> <div class="spacer --small"></div> <div id="bubbleplots"> <div class="bubbleplot" data-num="0"> <div class="plot" id="plotdiv"></div> <div class="control-row"> Country: <select class="countrydata"> </select> </div> </div> </div> </div> <script> d3.csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv', function(data, i){ return { index: i, data: data } }).then(function(data) { function unpack(data, key) { return data.map(function(row) { return row['data'][key]; }); } var allCountryNames = unpack(data, 'country'), allYear = unpack(data, 'year'), allGdp = unpack(data, 'gdpPercap'), listofCountries = [], currentCountry, currentGdp = [], currentYear = []; for (var i = 0; i < allCountryNames.length; i++ ){ if (listofCountries.indexOf(allCountryNames[i]) === -1 ){ listofCountries.push(allCountryNames[i]); } } function getCountryData(chosenCountry) { currentGdp = []; currentYear = []; for (var i = 0 ; i < allCountryNames.length ; i++){ if ( allCountryNames[i] === chosenCountry ) { currentGdp.push(allGdp[i]); currentYear.push(allYear[i]); } } }; // Default Country Data setBubblePlot('Afghanistan'); function setBubblePlot(chosenCountry) { getCountryData(chosenCountry); var trace1 = { x: currentYear, y: currentGdp, mode: 'lines+markers', marker: { size: 12, opacity: 0.5 } }; var data = [trace1]; var layout = { title:'Line and Scatter Plot', height: 400, width: 480 }; Plotly.newPlot('myDiv', data, layout); }; var innerContainer = document.querySelector('[data-num="0"'), plotEl = innerContainer.querySelector('.plot'), countrySelector = innerContainer.querySelector('.countrydata'); function assignOptions(textArray, selector) { for (var i = 0; i < textArray.length; i++) { var currentOption = document.createElement('option'); currentOption.text = textArray[i]; selector.appendChild(currentOption); } } assignOptions(listofCountries, countrySelector); function updateCountry(){ setBubblePlot(countrySelector.value); } countrySelector.addEventListener('change', updateCountry, false); } ); </script> </body> まとめ この辺が出来るとJavaScriptらしくなってきますね レッツトライ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで簡易メトロノーム

スクリーンショット 設置デモ (デモ版ではテンポ変更にJogShuttleUIを使用しています) スクリプト index.html <!DOCTYPE html> <html lang='ja'> <head> <meta charset='utf-8'> <meta name='viewport' content='user-scalable=no,width=device-width,initial-scale=1'> <title>metronome</title> <style> #container { position: relative; box-sizing: border-box; left: 0; width: 300px; height: 360px; } input[type=number] { width: 50px; } #st { position: absolute; box-sizing: border-box; top: 60%; left: 48%; width: 4%; height: 70%; border: 1px solid #444; background-color: #ccc; border-radius: 3px; transform: rotate(0deg) translateY(-55%); } #sg { position: absolute; right: 0; bottom: 0; } #sg [id*=sg] { position: relative; display: inline-block; border: 1px solid gray; border-radius: 40%; width: 20px; height: 20px; } .control { position: fixed; top: 8px; left: 8px; } </style> </head> <body> <div id='container'> <div id='st'></div> <div id='sg'> <div id='sg0'></div> <div id='sg1'></div> </div> </div> <div class='control'> <div id='control'> TIMING: <input type='range' id='adjust' min='0' max='1' value='0.5' step='0.025'><br> BEATS: <span id='vb'>0</span> <input type='range' min='0' max='8' value='0' id='beat'><br> TEMPO: <input type='number' min='10' max='800' value='120' id='tempo' oninput='tn.value=this.value'>BPM <select id='tn'></select><br> <button id='s'>START</button><br> </div> <button onclick='sh(this)'>△</button> </div> <script> 'use strict'; const st = document.getElementById('st'); const tempo = document.getElementById('tempo'); const adjust = document.getElementById('adjust'); const beat = document.getElementById('beat'); const tn = document.getElementById('tn'); const sa = 35; let start = 0; let id = 0; let _count = 0; document.getElementById('s').addEventListener('click', () => { se('blank'); start = Date.now(); if(id) id = 0; else id = requestAnimationFrame(loop); }); function loop() { const t = id ? (Date.now() - start) / 60000 : 0; const r = Math.sin(t * tempo.value * Math.PI) * sa; st.style.transform = `rotate(${r}deg) translateY(-55%)`; const count = Math.floor(t * tempo.value + +adjust.value); if(count !== _count) { _count = count; blink('sg1', '#8cf'); se('click'); if(beat.value > 0 && (count - 1) % beat.value === 0) { blink('sg0', '#48f'); se('bell'); } } if(id) id = requestAnimationFrame(loop); } function blink(id, color) { const d = document.getElementById(id); d.style.backgroundColor = color; setTimeout(() => { d.style.backgroundColor = ''; }, 100); } beat.addEventListener('input', () => { document.getElementById('vb').textContent = beat.value; }); function sh(e) { const d = document.getElementById('control'); if(d.dataset.hide === undefined) { d.dataset.hide = true; d.style.display = 'none'; e.textContent = '▽'; } else { delete d.dataset.hide; d.style.display = 'block'; e.textContent = '△'; } } const seList = { 'click': './click.mp3', 'bell' : './bell.mp3', 'blank': './blank.mp3', }; let context; let webSoundApiFlg = 0; window.AudioContext = window.AudioContext || window.webkitAudioContext; if(window.AudioContext !== undefined) { context = new AudioContext(); } const getAudioBuffer = (url, fn) => { const req = new XMLHttpRequest(); req.responseType = 'arraybuffer'; req.onreadystatechange = () => { if (req.readyState === 4) { if (req.status === 0 || req.status === 200) { context.decodeAudioData(req.response, buffer => { fn(buffer); }); } } }; req.open('GET', url, true); req.send(''); }; const playSound = buffer => { const source = context.createBufferSource(); source.buffer = buffer; source.connect(context.destination); source.start(0); }; function waOnload() { if(context !== undefined) { for(let i in seList) { getAudioBuffer(seList[i], buffer => { const bufId = i; document.querySelector('body').appendChild(document.createElement('div')); document.querySelector('body>div:last-of-type').id = bufId; document.getElementById(bufId).onclick = () => { playSound(buffer); }; }); } webSoundApiFlg = 1; } } window.addEventListener('load', waOnload); if(window.AudioContext === undefined) { let audioTag = ''; for(let i in seList) { audioTag += ` <audio id='${i}' preload='auto'> <source src='${seList[i]}' type='audio/mp3'> </audio>`; } document.querySelector('body').insertAdjacentHTML('beforeend', audioTag); } function se(id) { if(webSoundApiFlg) { document.getElementById(id).onclick(); } else { if(id) { const _d = document.getElementById(id); if(_d.currentTime != 0 || !_d.paused) { _d.pause(); _d.currentTime = 0; } _d.play(); } } } window.addEventListener('DOMContentLoaded', () => { const l = [ [40, 'Grave'], [46, 'Largo'], [52, 'Lento'], [58, 'Adagio'], [60, 'Larghetto'], [66, 'Andante'], [76, 'Andantino'], [84, 'Moderato'], [108, 'Allegretto'], [120, "All'o Moderato"], [132, 'Allegro'], [144, "All'o Assai"], [152, "All'o Vivace"], [160, 'Vivace'], [184, 'Presto'], [200, 'Prestissimo'], ]; for(const i of l) tn.insertAdjacentHTML('beforeend', `<option value='${i[0]}'>${i[0]} : ${i[1]}</option>`); tn.value = tempo.value; tn.addEventListener('change', () => { tempo.value = tn.value; }); }); </script> </body> </html> 別途以下のmp3ファイルが必要です。 (実際の音は何でも構いません) click.mp3 クリック音 bell.mp3 ベル音 blank.mp3 無音
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Php】Sessionの学習ノート(2)

初めに Sessionについて学習した内容のoutput用記事です。 ※内容に間違いなどがある場合はご指摘をよろしくお願いします。 ※こちらの記事はあくまでも個人で学習した内容のoutputとしての記事になります。 前回の記事: https://qiita.com/redrabbit1104/items/88ed159ba107aeb54dac SessionとCookie session_start()を使うとsessionが生成されると同時にcookieも生成されます。 session_start(); $_SESSION['date'] = date('c'); echo $_SESSION['date']; echo '<br>'; //sessionを表示 echo '<pre>'; var_dump($_SESSION); echo '</pre>'; $_SESSION['date']の値が25文字のstring型で表示されます。 //cookieを表示 echo '<pre>'; var_dump($_COOKIE); echo '</pre>' cookieの場合も連想配列の形で["PHPSESSID"]というキーが自動生成され、値はランダムでstring型の26文字が与えられます。 google chromeでcookieの情報を確認することができます。コンテンツのところにcookieの値が入っています。 Cookieの削除 生成されたcookieを削除するにはsetcookie()メソッドを使います。setcookie()はcookieの作成時に使われますが、cookieの名前と値を指定し有効期限が切れるようにすればcookieがブラウザー上に残らなくなります。 if文でisset()メソッドを使い、$_COOKIE['PHPSESSID']が存在しない場合にsetcookie()で値を''にし、有効期限を現在の時間からマイナスの値(ここでは1時間)を指定します。 if(isset($_COOKIE['PHPSESSID'])){ setcookie('PHPSESSID', '', time() - 3600, '/'); } sessionの破棄 session_destroy()メソッドでsessionに登録されたデータを全て破棄することができます。 session_destroy(); 参考サイト https://www.php.net/manual/ja/features.cookies.php https://www.php.net/manual/ja/reserved.variables.cookies.php https://www.php.net/manual/ja/function.setcookie.php https://www.php.net/manual/ja/function.var-dump.php https://www.php.net/manual/ja/language.types.declarations.php#language.types.declarations.mixed https://www.php.net/manual/ja/function.date.php https://www.php.net/manual/ja/function.session-destroy.php
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

document.execCommand("copy") の代替

前提 document.execCommand("copy") が既に非推奨になっている。代替手段として以下。 HTML5のClipboard API IE対応ではwindow.clipboardData Clipboard API(IE以外) navigator.clipboard.writeText() var clipboardText = "clipboard"; navigator.clipboard.writeText(clipboardText); IE以外は対応している。 *Compatibility window.clipboardData(IE) window.clipboardData.setData var clipboardText = "clipboard"; window.clipboardData.setData('Text', clipboardText ); 両方対応する場合 var clipboardText = "clipboard"; if(navigator.clipboard == undefined) { window.clipboardData.setData('Text', clipboardText); } else { navigator.clipboard.writeText(clipboardText); } 参考 クリップボードとのやりとり - Mozilla | MDN ClipboardEvent.clipboardData - Web APIs | MDN JavaScript からクリップボードを扱える Async Clipboard API がそろそろ見えてきました Clipboard API - Web APIs | MDN JavaScript まとめ - クリップボード操作 以上メモのみ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

discord.js-buttonsを使ってButtonで操作出来るDiscordBotを作ってみた

経緯 このBotを作ろうとした理由と記事を書いた理由は下記の通りです。 Discordに新しく追加されたインタラクションであるButtonsを使ってみたかった discord.js-buttonsに関する情報がなかった 似たようなことをしたい人の参考になれば良いなと思います。 なお、discord.js-buttonsはリポジトリが削除されているため今後無くなる可能性がありますが discord.js自体のmasterブランチにButtonsに関するソースコードが追加されている為(2021/06/08現在)   discord.jsが今後のバージョンでButtonsに対応すると思われますのでそちらを待ってもいいかもしれません。 ソースコード GitHubにあります。 noriokun4649/DiscordSoundEffectBot 利用してるライブラリなどの依存関係はpackage.jsonに書いてありますが下記の通りです。 "@discordjs/opus": "^0.5.0" "opusscript": "^0.0.8" "config": "^3.3.6" "discord.js": "^12.5.3" "discord.js-buttons": "^1.0.0" "ffmpeg-static": "^4.3.0" "fs": "^0.0.1-security" discord.js-buttons の使い方 discord.js-buttons の使い方について書いていきます。 discord.js-buttonsのセットアップ const discord = require('discord.js'); const client = new discord.Client(); const discordbtn = require('discord.js-buttons')(client); これでdiscord.jsからButtonsを利用する準備は完了です。 Buttonのコンポーネントを作成 上記のセットアップで定義したdiscordbtnからMessageButton()を呼びだし新しいインスタンスを作ることで作成できます。 シンプルなbuttonコンポーネント const button = new discordbtn.MessageButton() .setStyle('green') .setLabel('ファイル更新・読込') .setID('scan'); シンプルな例だとこのようになり、次のようなボタンが作成されます。 そして、このボタンを押したときのButtonIDは'scan'になります。 このButtonIDは後述する「buttonのイベントを受け取る」で利用するので覚えておきましょう。 MessageButton()に設定できるパラメータは幾つかあるので例を挙げてみます。 無効化されたbuttonコンポーネント const disabledbutton = new discordbtn.MessageButton() .setStyle('green') .setLabel('ファイル更新・読込') .setID('scan') .setDisabled(); //このボタンを無効化された状態に設定する URLのbuttonコンポーネント const urlbutton = new discordbtn.MessageButton() .setStyle('url') //ボタンのスタイルをurlにする .setLabel('ファイル更新・読込') .setURL('https://hoge.hoge'); //スタイルがurlの場合setIDではなくsetURLを指定するので注意 指定できるスタイルに関してはDiscordのAPIドキュメントを参照してください。 setStyleとsetLabelとsetID若しくはsetURLは必須の項目なので注意が必要です。 ボタンの無効化は既に送信済みのボタンが無効化されるわけではないため、既に送信済みのメッセージを編集して無効化したボタンに置き換える時などがユーズケースだと思われます。 メッセージの送信時にButtonを追加する ここでは早速メッセージ送信時にButtonを付ける方法に触れていきます。 1つのbuttonを追加 message.channel.send('1つのボタンがついてるメッセージ', button); 意外に簡単です。この例では下記のようになります。 1つのbuttonを追加(返信) message.reply('1つのボタンがついてる返信メッセージ', button); このように返信を送る際にも同様の方法でボタンを追加出来ます。 そして、このbuttonはDiscordAPI仕様によりメッセージと一緒に送る必要があります。 また、同じくDiscordAPI仕様によりメッセージは空にできない為buttonだけを送信する方法は今のところありません。 さて、このbuttonですが1つのメッセージに対して最大で5個まで追加する事ができます。 複数のbuttonを追加 message.reply('複数のボタンがついてる返信メッセージ', { buttons: [button, disabledbutton, urlbutton] }); discord.jsから送信したメッセージのシンタックスハイライトを有効にする際などで使う文法をそのまま使えるので第2引数の連想配列には、buttonsという名前がついたbuttonコンポーネントの配列を渡してあげれば大丈夫です。 buttonのイベントを受け取る ここではbuttonがおされた後の処理を行います。 buttonが押された際のイベントを受け取るハンドラ client.on('clickButton', async (button) => { }); このように書き、discord.jsにおけるmessageイベントを受け取るときと同じ書き方が出来ます。 この時、仮引数であるbuttonにはDiscordAPIドキュメントのインタラクションに書かれているフィールドに加えて、レスポンスを返す為の関数が用意されています。 仮引数(button)に用意されてるフィールドと関数 client: Client; id: string; version: number; token: string; discordID: Snowflake; applicationID: Snowflake; guild: Guild; channel: Channel; clicker: {}; message: Message; webhook: WebhookClient; replied: boolean; deferred: boolean; defer(ephemeral: boolean): Promise<void>; think(ephemeral: boolean): Promise<void>; get reply(): { send: (content: string, options?: object) => Promise<void>; fetch: () => Promise<string>; edit: (content: any, options?: object) => Promise<any>; delete: () => Promise<void>; }; フィールドについて フィールドについては特筆すべきことは無いと思いますがあるとすればid: stringです。 これはbuttonコンポーネントが押されたときの押されたbuttonと紐付くButtonIDの文字列が入っています。 あとはclicker: {}ですが、これにはbuttonをおしたユーザーの情報が入っています。 clickerフィールドの利用例 const guild = button.guild; const member = guild.member(button.clicker.user); このように使うことで、buttonをおしたユーザのGuildMemberインスタンスを取得できます。 repliedとdeferredフィールドに関しては次の「関数について」で触れる関数が既に使われているかどうかを判定します。これは関数を使った後にすぐ状態が変化するわけではなく、もう一度buttonがおされた際に変化するので注意が必要です。 関数について このときthink(boolean)関数はユーザに処理中若しくは考え中を表示させる関数です。defer(boolean)関数はユーザに処理を先延ばしにする旨を伝える関数です。先延ばしに関する処理はデスクトップ版Discordがバグっているのかわかりませんが、モバイル版のDiscordのみボタンが押せない状態で処理が先延ばしされます。 この2つの関数において引数になるbooleanはこの考え中や先延ばしというBot側のメッセージをbuttonを押したユーザーだけに伝えるかどうかを指定できます。trueの場合はbuttonを押したユーザーだけに伝えます。   reply.send()ではbuttonのついたメッセージへ返信出来ます。 reply.delete()では返信を削除できます。これはthink(boolean)によって送信された「考え中・・・」というメッセージも対処です。 reply.edit()では返信を編集出来ます。 reply.fetch()では返信されているメッセージを取得出来ます。 そして注意が必要なのは、これらの関数のうちいずれかを利用しないと、Discord上で「インタラクションに失敗しました」というメッセージが表示される事です。そのため、buttonが押され正常な処理が出来る場合にはいずれかの関数を使いレスポンスを返してあげましょう。 では早速、具体的に機能を書いていきます。 const listButton = new disbut.MessageButton() //buttonコンポーネントを定義しておく .setStyle('green') .setLabel('再生可能ファイル一覧を表示') .setID('list'); client.on('clickButton', async (button) => { //button押された時に実行される await button.think(false); //botが考え中ということを全員にしらせる if (button.id === 'scan') { //ButtonIDがscanの場合 const fileList = readfile(); //ファイル読み込み関数を呼ぶ(buttonには関係ない処理) if (fileList.length > 0) { //ファイルが1以上だったら btn = createButtns(fileList); button.reply.edit('ファイル更新・読込に成功しました。', { code: true, buttons: [listButton] }); //考え中の返信を「ファイル更新・読込に成功しました。」というメッセージに編集する。 } else { button.reply.edit('ファイル更新・読込に失敗しました。', { code: true }); //考え中の返信を「ファイル更新・読込に失敗しました。」というメッセージに編集する。 } }); このようになります。わかりづらいかも知れませんが「考え中・・・」と出た後に書き換えられてるかと思います。 因みに上記プログラムのbutton.think(false);をbutton.think(true);に変更すると こうなります。「これらはあなただけに表示されています」という風になりますね。 discord.js-buttonsの使い方おわり 以上がdiscord.js-buttonsを使ったButtonsの使い方です。使いこなせるようになると非常にUXに優れたBotが作成できると思います。 Buttonsを多用しすぎてメッセージチャンネルをあらさないような配慮も考えつつ活用して頂ければ幸いです。 最後に 最後に、私が今回作ったBotについて軽く触れて終わりにします。 Discordに追加されたButtonのインタラクションを使って、効果音流せるBotつくってみた pic.twitter.com/VyZ7uGsxL1— #??????? "???煮込みすぎたおでん???.?" (@noriokun_blog) June 6, 2021 今回作ったのはDiscordのボイスチャンネル上でサウンドエフェクトを再生できるBotです。 Buttonsを多用して非常に使いやすくできたと思っていますのでよろしければ使ってみてください。 ソースコードに飛ぶ リンク discord.js-buttons - npm
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む