- 投稿日:2021-10-25T23:54:28+09:00
ES6から導入された分割代入について オブジェクト編
1.経緯 Reactを学習するためにTwitterで話題となっていた、上記の本を買い学習することになった。初めてJavascriptライブラリを使う人にも優しいようにES6復習してからReactを学習する内容となっていた。Vueをやっていた自分にとってClassやconst、アロー構文など紹介されていく中で分割代入??なんだそれとなったので分割代入について詳しくみていくことにした。 2.分割代入(オブジェクト) 分割代入には、大きく分けてオブジェクトで使われるケースと配列で使われるケースがあるのだが、まずはオブジェクトで使われるケースについてみていく。 const user={ name:'山田', age:23 } このようなごく一般的なオブジェクトを用意した. //分割代入を使用しない console.log(`名前は${user.name}です${user.age}歳です`) //名前は山田です23歳です 通常使われる際は以上のようにオブジェクト.プロパティ名と言ったようにして呼び出される。しかし、分割代入によって以下のように書き換えることができる。 //分割代入を使用する let {name,age}=user console.log(`名前は${name}です${age}歳です`) //名前は山田です23歳です オブジェクト.プロパティというように呼び出さなくてもnameとageというように変数のように呼び出すことができる。 let {name,age}=user name='田中' console.log(`名前は${name}です${age}歳です`) //名前は田中です23歳です 変数になっているため書き換えもすることができる。 let {age,name}=user console.log(`名前は${name}です${age}歳です`) //名前は山田です23歳です このようにageとnameを逆にした場合でも通常通り動いた。つまり元になっているオブジェクトのプロパティ名と一致する場合であれば順番はどうでもいいということである。 このようにオブジェクト.プロパティ名と書かなくても呼び出すことが可能になったのだが、これは果たして使い道があるのだろうか。オブジェクト.プロパティ名として記載した方がオブジェクトを使っているんだな〜ってことが理解できるのではないだろうか??変数として置き換わるため、後々同じような変数に対して代入するようなミスが起こりうるのではないかと思った。 3. オブジェクトのプロパティの名前の変更 let {name:userName,age:userAge}=user console.log(`名前は${userName}です${userAge}歳です`) //名前は山田です23歳です 以上のようにするとプロパティの名前とは違う名前を設定することができる。しかし、このケースにおいてもオブジェクト.プロパティ名の方がわかりやすくコードを書くことができているような気がする。それにわかりにくいプロパティ名であればオブジェクト自体を変更すればいいのではないだろうか?? 他にも使う理由として、長くなってしまったプロパティ名を少しでも短くできるや、オブジェクトが複数個ある場合は管理しずらいなどの点があったがこれはvscodeであれば補完機能が効くのでそれほど不便に感じるようなことはない。 4.僕が考えたオブジェクトの分割代入の利用方法 外部からオブジェクトを引っ張ってきた時のわかりにくいプロパティ名を簡単に変更することができる。 user{ n:'山田', a:23 } let {n:userName,a:userAge}=user console.log(`名前は${userName}です${userAge}歳です`) //名前は山田です23歳です この例では変更不可能な外部から入手したオブジェクトがわかりにくい名前だったケースを想定している。最もこのようなデータを使うのは好ましくないだろう。多くのデータは使う人がわかりやすいプロパティ名をつけてくれていると思う。しかし、もし自分にとってわかりずらいようなものであれば変更することが可能だ。 5.結論 オブジェクトの分割代入の有効的な使い方について自分でも思いつかなかった。 MDNを見たが有効的な使い方は載っていなかったように感じる。しかし、このような書き方を他の人がした場合に分割代入をしているんだなとわかることは大切であると思う。また、新しく導入されたからといってそれを全て使ってわかりにくいコードになってしまっては本末転倒である。新しい書き方については必要な場面に応じて使っていきたい。
- 投稿日:2021-10-25T23:21:19+09:00
属性ディレクティブ・構造ディレクティブを自作してみる
はじめに ディレクティブが全く理解できていなかったので勉強しました。 特に構造ディレクティブの考え方が分かりにくいので、*と[]の意味の違いに気を付ける必要があると感じました。 Angular公式などをみて書いていますが、あくまで勉強したときに書いたメモ書きなのであしからず。 属性ディレクティブを自作する 属性ディレクティブは適用した要素の見た目(CSS)を変えたりイベントハンドラを設定したりできる。ngStyle等が例。 構造ディレクティブは要素を追加したりできる。ngIfとかが例。 コマンド(例:ng generate directive myColored) ng generate directive ディレクティブ名 ここではelementRefを使ってDOMを操作してみる src\app\my-colored.directive.ts import { Directive, ElementRef, Input, OnInit } from '@angular/core'; import { style } from '@angular/animations'; @Directive({ selector: '[appMyColored]' }) export class MyColoredDirective implements OnInit { constructor(private elementRef: ElementRef) { } ngOnInit() { this.elementRef.nativeElement.style.backgroundColor = '#cf0'; } } モジュールに登録することでディレクティブが使えるようになる(declarationsに追加する) src\app\app.module.ts ... import { MyColoredDirective } from './my-colored.directive'; @NgModule({ declarations: [ AppComponent, ... GrepPipe, MyColoredDirective, ], ディレクティブを付けると適応できる app.component.html <form> <label appMyColored>キーワード: ... パラメーター付きのディレクティブ 単純に@Inputを使えばいい src\app\my-colored.directive.ts import { Directive, ElementRef, Input, OnInit } from '@angular/core'; import { style } from '@angular/animations'; @Directive({ selector: '[appMyColored]' }) export class MyColoredDirective implements OnInit { @Input() myBgColor = '#cf0'; //デフォルト値を設定している constructor(private elementRef: ElementRef) { } ngOnInit() { this.elementRef.nativeElement.style.backgroundColor = this.myBgColor; } } 属性名をそのまま指定しても良い。属性バインディングを使うこともできる src\app\app.component.html <form> <label appMyColored myBgColor="#ee0">キーワード: <input appMyColored [myBgColor]="'#cc0'" type="text" #txt size="15"> ディレクティブ名で属性を設定できるようにする ディレクティブ名で属性を指定できるようにする場合も多い。 ただし、ただ単に@Inputでディレクティブ名と属性名を同じにするだけなのでディレクティブ名だけでは1つのパラメーターしか受け取ることができない。 (@Inputはいくつでも使えるためディレクティブ名と属性名を合わせることにこだわらなければいくつでも属性を指定できる) src\app\my-colored.directive.ts @Directive({ selector: '[appMyColored]' }) export class MyColoredDirective implements OnInit { @Input(`appMyColored`) myBgColor = '#cf0'; ... src\app\app.component.html <form> <label appMyColored='#0cc'>キーワード: <input [appMyColored]="'#00e'" type="text" #txt size="15"> ディレクティブ名を属性名と同じにしつつ、他の属性も使う場合は以下のようにする src\app\my-colored.directive.ts @Directive({ selector: '[appMyColored]' }) export class MyColoredDirective implements OnInit { @Input(`appMyColored`) myBgColor = '#cf0'; @Input() hoge = 'hoge'; constructor(private elementRef: ElementRef) { } ngOnInit() { this.elementRef.nativeElement.style.backgroundColor = this.myBgColor; console.log(this.hoge); } src\app\app.component.html <form> <label appMyColored='#0cc'>キーワード: <input [appMyColored]="'#00e'" [hoge]="'hoo'" type="text" #txt size="15"> イベント処理をディレクティブに追加する 属性ディレクティブにはイベントリスナーを設定できる。 イベントリスナーは@HostListenerを使って定義する。 要素にクリックなどのイベントが発生したときの動作(イベントハンドラ)をディレクティブで設定することができる。 src\app\my-colored.directive.ts @Directive({ selector: '[appMyColored]' }) export class MyColoredDirective /*implements OnInit*/ { @Input(`appMyColored`) myBgColor = '#cf0'; @Input() hoge = 'hoge'; constructor(private elementRef: ElementRef) { } //マウスが要素内に入った時 @HostListener('mouseenter') enableBgColor() { this.elementRef.nativeElement.style.backgroundColor = this.myBgColor; } //マウスが要素内から外れたとき @HostListener('mouseleave') disableBgColor() { this.elementRef.nativeElement.style.backgroundColor = ''; } } この例ではマウスポインタが要素内に入った時背景が変わり、マウスポインタが要素内から出たとき背景が白('')に戻る src\app\app.component.html <form> <label appMyColored='#0cc'>キーワード: <input [appMyColored]="'#00e'" [hoge]="'hoo'" type="text" #txt size="15"> @HostListenerには引数を指定することができる。 引数は配列にまとめて@HostListenerに登録して、イベントハンドラの仮引数で利用する。 引数はstring[]で指定する必要があるため注意(以下の例でいうと'$event.target'の部分) @HostListener(name[, args]) src\app\my-colored.directive.ts @HostListener('mouseleave', ['$event.target', 'hoge']) disableBgColor(element, hoge) { this.elementRef.nativeElement.style.backgroundColor = ''; console.log(element); //<input _ngcontent-vvh-c55="" type="text" size="15" ng-reflect-my-bg-color="#00e" ng-reflect-hoge="hoo" style=""> console.log(hoge); } 構造ディレクティブを作成する 例えば、先ほど作成した属性ディレクティブを「*」構文で呼び出してみる。 src\app\my-colored.directive.ts @Directive({ selector: '[appMyColored]' }) export class MyColoredDirective /*implements OnInit*/ { @Input(`appMyColored`) myBgColor = '#cf0'; @Input() hoge = 'hoge'; constructor(private elementRef: ElementRef) { } //マウスが要素内に入った時 @HostListener('mouseenter') enableBgColor() { this.elementRef.nativeElement.style.backgroundColor = this.myBgColor; } //マウスが要素内から外れたとき @HostListener('mouseleave') disableBgColor() { this.elementRef.nativeElement.style.backgroundColor = ''; } } app.component.html <span *appMyColored>これは画面に表示されない</span> このように書くとエラーにはならないが、画面にはappMyColoredを使ったspanタグの部分は画面に表示されなくなる。これはAngularの*構文は、内部的にはターゲット要素を要素に展開するためのシンタックスシュガーであるため。 この例ではテンプレート化された要素(ng-template内に書かれた要素)を画面に反映させる処理が記載されていないため、画面には表示されない。 補足すると、先ほどのapp.component.htmlの例は以下のように書き換えられるイメージらしい。 app.component.html <ng-template appMyColored> <span>これは画面に表示されない</span> </ng-template> 構造ディレクティブを実装する 構造ディレクティブを作るためには、ディレクティブ内でTemplateRefオブジェクトとViewContainerRefオブジェクトを使う。 ViewContainerRefはテンプレート化された要素を挿入する領域を表す。(イメージ的にはng-templateがある場所を表す) TemplateRefはテンプレート化された要素(つまり、構造ディレクティブを付けた要素。言い方を変えるとng-templateの下にある要素)を表す。 基本的にはViewContainerRefで要素を表示したいところを参照して、ViewContainerRefにTemplateRef(画面に表示する要素)を追加する流れになる。 要素を領域に差し込むためには以下の構文を使う。 createEmbeddedViewメソッドは、引数のテンプレートを基に要素を作成してviewContainerRefの領域に埋め込む。 構文:viewContainerRef.createEmbeddedView(templateRef) 例 src\app\judge-number.directive.ts import { Directive, Input, OnChanges, TemplateRef, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[appJudgeNumber]' }) export class JudgeNumberDirective { //数字を設定する @Input('appJudgeNumber') number: number; constructor( private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef ) { } //@Input経由で入力値が(再)設定されたとき //appJudgeNumber属性を変更したときにテンプレートの表示・非表示を判定 ngOnChanges() { //数字が5より小さいときclearする if(this.number < 5) { this.viewContainerRef.clear(); } else { this.viewContainerRef.createEmbeddedView(this.templateRef); } } } src\app\app.component.html <div *appJudgeNumber="number"> <h2>これが構造ディレクティブです</h2> <p>*はng-templateに要素を展開するシンタックスシュガーです</p> </div> numberが5以上の時のみ画面に要素が表示される これが構造ディレクティブです *はng-templateに要素を展開するシンタックスシュガーです ``
- 投稿日:2021-10-25T23:07:51+09:00
detaにNodeJSで入門(ほぼ自分用メモ)
deta (baas) なんかこれが欲しかったけど今まではなかったもの感が否めない最高なBaas(?)。 無料&クレカなし&簡単。満足。最高。 データベース(NoSQL) Faas (lambdaとかFirebase functions みたいなやつ) ストレージ これらが本当にお手軽に使える。 大切なことなのでもう一度言う。 本当にお手軽に使える。 インストールもろもろ CLIインストール # Windowsの方 (PowerShellで実行してください!) iwr https://get.deta.dev/cli.ps1 -useb | iex # macの方 curl -fsSL https://get.deta.dev/cli.sh | sh 自分はWindowsユーザなのでmacは試せていません。エラーなどあったらごめんなさい。 パッケージインストール npm init -y yarn add deta 共通部分 いろいろインポートしておく。 const { Deta } = require("deta") ; const deta = Deta(process.env.DETA_PROJECT_KEY) ; micro Faas,瞬時的なクラウド上の実行環境 いろいろセットアップ microは別プロジェクトで立ち上げるか、トップフォルダ直下にプロジェクト作るほうがよさそう deta new --node プロジェクト名 コード const express = require('express') const app = express() app.get('/', (req, res) => { const reqBody = req.body ; res.status(200).json({ msg:"ok", reqBody, }); }) ; module.exports = app ; base DBaas,データベース const usersDb = deta.Base("users") ; //async 関数内で実行 usersDb.put( { name:"TBSten", job:"エンジニア", } ); const userList = await usersDb.fetch() ; userList.items.forEach(el=>{ console.log(el); }); Drive Storage,10GBくらいおけるらしい const photos = deta.Drive("photos") ; //async 関数内で実行 await photos.put( "test.png", {path: "test/GitHub-Mark.png"} ) ; 個人的にはこんなフォルダ構成で使いそう lib/ +- deta/ +-deta.js //detaをエクスポート +-base.js //deta.jsのdetaからbaseと各テーブルをエクスポート +-drive.js //deta.jsのdetaからdriveと各フォルダをエクスポート +- +- ...(自作ライブラリなど)... server/ +- node_module/ +- ... +- index.js //expressでルート分岐 参考サイト 公式ドキュメント(英語だけどほんやくしたら結構読める) 独り言 欲しい機能がこれでもかとあるので、今後作ろうとしているサービスで採用予定です。
- 投稿日:2021-10-25T21:16:49+09:00
配列の中にある連想配列の「キーの値」から「他のキーの値」を引き出したい! 「conversionData」の使い方
概要/やりたいこと 例えば以下のような配列。 const sampleArr = [{id: 1, name: 'hoge', note: '備考'}, {id: 2, name: 'huga', note:'備考2'}, {id: 3, name: 'piyo', note:'備考3'}]; この配列の中の連想配列から、任意のidのnameやnoteの値を取り出したいことってありませんか? 解決方法 const conversionData = {}; sampleArr.forEach((data) => { conversionData[data.id] = { name: data.name, note: data.note }; }); というわけではい。 このconversionDataの中身はこんな感じ。 {1: {name:"hoge",note:"備考"}, 2: {name:"huga",note:"備考2"}, 3: {name:"piyo",note:"備考3"}} ここから、例えばid2のnameを引き出したいときは、 console.log(conversionData[2].name); 出力:huga こんな感じでお好みで。 ブランケット記法のconversionDataの[]の中には変数を入れれるのが色々使えて嬉しいですね。 実用上の注意 ・実際に使う場合は、頻繁に使われると思われるので、ちゃんとlibsとかのフォルダに入れてどこからでも引き出せるようにすること。 ・なので、userConversionDataとか、companyConversionDataとかの命名がわかりやすい。 ・conversionDataのキーは一意な値でなければちゃんと動作してくれないことに注意。 ・よってconversionDataのキーを[data.name]とかにして名前でその他項目を引き出すようにするのはかなり非推奨。
- 投稿日:2021-10-25T19:59:19+09:00
【Vue.js】算出プロパティのキャッシュ
はじめに こんにちは! 今回は【Vue.js】算出プロパティのキャッシュについてアウトプットしていきます! キャッシュとは ・cpmputed・・・キャッシュされる(依存関係に元付きキャッシュされる) ・methods・・・キャッシュされない 書き方・解説 ランダムの数字を返す処理をcpmputedとmethodsで記述。 HTML <div id="app"> <h2> Computed </h2> <ol> <li>{{ computedNumber }}</li> <li>{{ computedNumber }}</li> <li>{{ computedNumber }}</li> <li>{{ computedNumber }}</li> </ol> <h2> Methods </h2> <ol> <li>{{ methodsNumber() }}</li> <li>{{ methodsNumber() }}</li> <li>{{ methodsNumber() }}</li> </ol> </div> Vue.js var app = new Vue({ el: '#app', computed: { computedNumber: function() { console.log('computed!') //⏫動作確認 return Math.random() //⏫ランダムな数字を生成できる } }, methods: { methodsNumber: function() { console.log('methods!') return Math.random() } } }) cpmputedとmethodsを記述します。処理内容は全く一緒です。 上記のようにcpmputedは全く同じ数字(キャッシュされている)、methodsは全て異なる数字(キャッシュされない)という風な特性があるからこうなります。 算出プロパティのキャッシュの再構築のトリガーとなるのはリアクティブなデータのみです。Math.random()関数はリアクティブではないので、複数回よんでもキャッシュされたデータか使われて同じ値が返される。 まとめ cpmputedはキャッシュされ全く同じ値が出力される。console表示は1回のみ。 methodsはキャッシュされないので全て異なる値が出力される。console表示は表示された値の数と一緒。 最後に 今回は算出プロパティのキャッシュについてアウトプットしました。 今回の記事を読んで質問、間違い等あればコメント等頂けると幸いです。 今後ともQiitaにてアウトプットしていきます! 最後までご愛読ありがとうございました!
- 投稿日:2021-10-25T19:58:45+09:00
InDesign スクリプト 索引の参照の重複を解消
索引の参照の重複を解消するスクリプトは、これで良いのかな・・・? /* このスクリプトを利用して起こった、どのような不具合にも責任は取れません。 ご了承下さい。 更新 2021/010/25 */ // アプリ指定 #target "indesign"; // スクリプト名 var scriptName = "索引の参照の重複を解消"; //スクリプトの動作指定(一つのアンドゥ履歴にする、及び、アンドゥ名) app.doScript(function () { // ダイアログ var dialogueFlg = confirm("索引の目次の参照の索引マーカーの前後の索引マーカーを調べ" + "\r" + "同じ目次の場合に参照が重複していると判断し削除します。" ,"", scriptName); // Noの場合 if (dialogueFlg == false) { // 終了 exit(); } // 数 var number = 0; // 重複している参照を削除 number = deleteOverlappingPageReferences(app.activeDocument.indexes.firstItem().topics,number); // 結果 alert("削除数 " + number); //スクリプトの動作指定の続き }, ScriptLanguage.JAVASCRIPT, [scriptName], UndoModes.ENTIRE_SCRIPT, scriptName); // 重複している参照を削除 function deleteOverlappingPageReferences(targetTopics,number){ // 対象のIndex var targetIndex; // 対象の文字 var targetCharacter; // 目次の数だけ繰り返す for(var i = 0; i < targetTopics.count(); i++){ // 参照の数引く1だけ後ろから繰り返す pageReferencesLabel: for(var ii = targetTopics.item(i).pageReferences.count() - 1; ii > 0; ii--){ // 対象のIndex targetIndex = targetTopics.item(i).pageReferences.item(ii).sourceText.index; // 最初の挿入点では無い場合 if(targetIndex != 0){ // 前に調べていく while(targetIndex >= 0){ // 減らす targetIndex--; // 対象の文字 targetCharacter = targetTopics.item(i).pageReferences.item(ii).sourceText.parent.characters.item(targetIndex); // 索引マーカーの場合 if(targetCharacter.contents == String.fromCharCode("0xFEFF")){ // 現在の参照のひとつ前から前に繰り返す for(var iii = ii - 1; iii >= 0; iii--){ // 対象の索引マーカーの参照点が目次の参照点と同じ場合 if(targetCharacter.insertionPoints.firstItem() == targetTopics.item(i).pageReferences.item(iii).sourceText){ // 参照を削除 targetTopics.item(i).pageReferences.item(ii).remove(); // 増やす number++; // ラベルの次の繰り返しへ continue pageReferencesLabel; } } // アンカー付きオブジェクトマーカーの場合 }else if(targetCharacter.contents == String.fromCharCode("0xFFFC")){ // 次の繰り返しへ continue; // 以外の場合 }else{ // 抜ける break; } } } // 対象のIndex targetIndex = targetTopics.item(i).pageReferences.item(ii).sourceText.index; // 最後のひとつ前では無い場合 if(targetIndex + 1 != targetTopics.item(i).pageReferences.item(ii).sourceText.parent.characters.lastItem().insertionPoints.lastItem().index){ // 後ろに調べていく while(targetIndex < targetTopics.item(i).pageReferences.item(ii).sourceText.parent.characters.lastItem().insertionPoints.lastItem().index){ // 増やす targetIndex++; // 対象の文字 targetCharacter = targetTopics.item(i).pageReferences.item(ii).sourceText.parent.characters.item(targetIndex); // 索引マーカーの場合 if(targetCharacter.contents == String.fromCharCode("0xFEFF")){ // 現在の参照のひとつ前から前に繰り返す for(var iii = ii - 1; iii >= 0; iii--){ // 対象の索引マーカーの参照点が目次の参照点と同じ場合 if(targetCharacter.insertionPoints.firstItem() == targetTopics.item(i).pageReferences.item(iii).sourceText){ // 参照を削除 targetTopics.item(i).pageReferences.item(ii).remove(); // 増やす number++; // ラベルの次の繰り返しへ continue pageReferencesLabel; } } // アンカー付きオブジェクトマーカーの場合 }else if(targetCharacter.contents == String.fromCharCode("0xFFFC")){ // 次の繰り返しへ continue; // 以外の場合 }else{ // 抜ける break; } } } } // 一つ下の階層の目次がある場合 if(targetTopics[i].topics.count() != 0){ // 循環 number = deleteOverlappingPageReferences(targetTopics[i].topics,number); } } // 数を返す return number; }
- 投稿日:2021-10-25T19:06:24+09:00
TeamViewer IoTのAPIをNode.jsから触ってみたメモ #iotlt
LTネタとして1時間くらい調べたくらいの雑観です。時間かけてないので間違いが多いかもしれません。 TeamViewer IoTってサービスがあるんですね、TeamViewerは学生時代にリモートデスクトップで使ったことあった気がする。そのソフトのIoT...?ほうほう。 IoTLTのグループでコメントをもらったので触ってみました。 ちなみに次回のIoTLTは11/16です! https://iotlt.connpass.com/event/228734/ また、こんなの作ったよー話はこちらのスライドに載ってます。 https://speakerdeck.com/n0bisuke2/teihuaruwang-rewen-ti-number-iotlt なんとなくの全体感 ラズパイなどにSDKを仕込んでデータをクラウドに上げる ダッシュボードなどの機能がある データをストアできる機能がある MQTTが使える 設定変更などをREST API経由で行える こんな感じのIoTのバックエンドサービスです。 サイト見ても料金などが見つけられなかったので無料プランと有料プランの違いみたいなのがいまいち把握できていません。 アカウント作成 せっかくなのでアカウント作成画面から、 「ほとんど終わりです!」、そっか! 独特な日本語訳だな。。。 何ができるか探す SDK色々ある模様。Node.jsを見つけてテンションが上がります。 ただラズパイなどに入れる想定っぽいのと通常のようにnpmに存在するわけではなさそうでした。 TeamViewer IoT Cloud API 1番サクッと使えそうだったTeamViewer IoT Cloud APIを触ってみました。 おそらくですがMQTTのトピックなどを作成したり、それらをグルーピングしたりが出来るAPIです。 とりあえずGET とりあえずドキュメントを上から見た時に最初にあったGet Assignment Tokenのエンドポイントを叩いてみます。 https://docs.teamviewer-iot.com/cloud-api/#11-get-assignment-token APIキーを指定してあげて、APIバージョンを指定すれば素直に動いてくれました。 APIバージョンは現時点だと2.0.0で固定文字列で問題なさそうです。 app.js 'use strict'; const axios = require(`axios`); const API_KEY = `xxxxx`; const API_VERSION = `2.0.0`; const URL = `https://api.teamviewer-iot.com/assignmentToken`; (async () => { const options = { headers: { Authorization: `Bearer ${API_KEY}`, Accept: `application/json; Version=${API_VERSION};` } } try { const res = await axios.get(URL, options); console.log(res.data); } catch (error) { console.log(error.response.data); } })(); $ node app.js { uid: 'u181744624', assignmentToken: '*', data: '14370533-ttNCDoiTeWFhitMfGCwz', status: 'OK', timestamp: 1634797986766 } 無事に結果が帰ってきました。 トピックの作成 トピックの作成が出来たので試してみました。 https://docs.teamviewer-iot.com/cloud-api/#101-create-topic 'use strict'; const axios = require(`axios`); const API_KEY = `xxxxxxxxxx`; const API_VERSION = `2.0.0`; const URL = `https://api.teamviewer-iot.com/topics`; (async () => { const body = { "action": "create", "payload": { "name": "n0bisuke-topic", "channels": [ "5d498a5a965e59417cb3cf36" ] } }; const options = { headers: { Authorization: `Bearer ${API_KEY}`, Accept: `application/json; Version=${API_VERSION};` }, } try { const res = await axios.post(URL, body, options); console.log(res.data); } catch (error) { console.log(error.response.data); } })(); リクエストBodyの部分はサンプルコードのままです。channelsのIDっぽいのは適当な数字 const body = { "action": "create", "payload": { "name": "n0bisuke-topic", //名前を変えてみた。 "channels": [ "5d498a5a965e59417cb3cf36" //サンプルのまま ] } }; 実行してみる $ node create_topic.js { uid: 'u181744624', body: { name: 'n0bisuke-topic', channels: [ '5d498a5a965e59417cb3cf36' ] }, data: { id: '61767fade27d3be61b9af5f6', info: 'InvalidChannelId(s): [5d498a5a965e59417cb3cf36]' }, topics: '*', status: 'OK', timestamp: 1635155885620 } トピックが作成されて、何かIDが取得出来ました。 トピックの情報を更新する 先ほど作成したトピックの情報を変更してみます。 https://docs.teamviewer-iot.com/cloud-api/#104-update-topic IDを指定してトピック名を変更してみます。 'use strict'; const axios = require(`axios`); const API_KEY = `xxxxxxxxxxxxxxx`; const API_VERSION = `2.0.0`; const URL = `https://api.teamviewer-iot.com/topics`; (async () => { const body = { action: `update`, payload: { id: `61710b40e27d3be61b9aeb81`, name: `hoge` } }; const options = { headers: { Authorization: `Bearer ${API_KEY}`, Accept: `application/json; Version=${API_VERSION};` }, } try { const res = await axios.post(URL, body, options); console.log(res.data); } catch (error) { console.log(error.response.data); } })(); これでトピック名がhogeになりました。 $ node update_topic.js { uid: 'u181744624', body: { name: 'hoge', id: '61710b40e27d3be61b9aeb81' }, data: { id: '61710b40e27d3be61b9aeb81', info: '' }, topics: '*', status: 'OK', timestamp: ふむふむ。トピックの名前がhogeになってますがidは引き継いでますね。 よもやま MQTTでトピックに情報をpublishしたいってのがありましたが、いまいちそこまで辿り着けてません。 感想など 中途半端ですが、トピック名を更新することができたので、ここにセンサーデータを入れ込むという間違った使い方をしてティファールの温度管理っぽいのをやってみました。 https://speakerdeck.com/n0bisuke2/teihuaruwang-rewen-ti-number-iotlt API自体は素直な感じですが、全体像としてはまだ掴み切れてません。 Firebaseみたいにデータストアと通信がいい感じにやれる何かだと嬉しいんですけど果たしてどうなのか。。 気になった人は触って教えてください笑
- 投稿日:2021-10-25T18:10:06+09:00
はじめてのJavaScript⑩ 「繰り返し処理 "do while"」
目次 1.はじめに 2."do while"の概要 3.whileとの違い 4.構文 5.例題 6.おわりに 1. はじめに 本記事では、JavaScriptの「繰り返し処理 "do while"」について記載する。 2. "do while"の概要 while文と同様に、条件を満たしているときだけ繰り返しの処理を行う構文である。 3. whileとの違い while ・条件の判断が前になる。 ・条件によっては一度もループを実行しない。 do while ・条件の判断が後ろにある。 ・条件に関わらず、最低一回はループを実行する。 4. 構文 構文は以下のようになる。 index.js do { //条件式がtrueのときに、実行したい処理 } while (条件式); 5. 例題 例題は以下のようにする。 変数kの値を1から3までコンソールに出力するプログラムをdo whileを使って記述する。 変数の設定 まずは変数kの設定を実施する。 例題に沿って最小値は1なので、kは1とする。 index.js let k = 1; do whileの作成 index.js do { console.log(k); k++; } while (k <= 3); 前回のwhileでも記載したことなので詳細には記載しないが、 whileとの決定的な違いは do whileは条件式を最後に記述しているのでループ処理は最後に実施されているということ。 なお、デベロッパーツールで表示すると以下のようになる。 6. おわりに 次項:はじめてのJavaScript⑪ 「繰り返し処理の演習」に続く。
- 投稿日:2021-10-25T17:00:23+09:00
Xampp環境で開発したReact x Laravel x mysql のアプリケーションをherokuにデプロイする。
はじめに 開発したアプリケーションをherokuにデプロイしようと思ったのですが、使用した技術的にもあまり適当な情報がなかったので備忘録としてまとめるついでに誰かの役にたてばなと思います。環境がLinuxですのでMacの方は適宜読み替えてください。様々なサイトを参考にさせてもらっており、参考文献として最後に載せています。 環境 Ubuntu20.04LTS React Laravel(php) mysql xampp 前提 linuxのコマンドが触れる gitを扱える デプロイの概念がなんとなく分かる laravelアプリケーションフォルダ直下をgitで管理している herokuの登録とClearDBの設定 こちら[1]から登録します。2段階認証みたいなやつはなんか飛ばしちゃいましたが、やっといた方が良い気がする。ログインまで行けたらCreate new appを選択してアプリケーションの作成をします。名前を入力して、リージョンはアメリカとヨーロッパしかないのでアメリカを選択。Overviewを押した後にConfigure Add-onsを押すとAdd-onsと書いてある検索欄が出てくるのでそこにClearDBと入力して出てきたものをクリック。更にIgnite-freeをクリックすると赤い警告のようなものが出てくるので、そこにある下の方のリンクをクリックします。 ここで一旦ここまでの流れを解説します。herokuではデフォルトのDBはpostgreSQLなのでアドオン(拡張機能)を用いてmySQLを使用出来るようにする必要があります。そのアドオン名がClearDBです。その後利用プランを無料のものに選択ししましたが、このアドオンの利用には条件があります。それがクレジットカードの登録です。赤い警告ではクレジットカード登録しろといっているのでリンクをクリックして登録しようとしているという流れです。 リンクをクリックするとクレジットカードの番号、名前、住所などの入力画面が出てくるのですべて英語で入力してください。こちらのサイト[2]で日本語住所を変換すれば簡単に英語になるようですが、順番とか間違えない人だったらそのまま英語で入力しても大丈夫です。完了したら先程の画面に戻ってClearDBの下にあるSubmit Order Formを押したら完了です。 環境変数の設定 Settingsを押した後、Reveal Config Varsを押すと環境変数の追加画面が出てきます。左側のKEYの部分に環境変数名を、 VALUEの部分にその値を入力すれば環境変数を設定することが出来ます。環境変数CLEARDB_DATABASE_URLのURLの部分に色々な情報が含まれているので、それを環境変数として抜き出して使いやすくします。形式は下の形です mysql://ユーザー名:パスワード@ホスト名/データベース名?reconnect=true そして以下のような対応で環境変数を作成します。 KEY:VALUE DB_CONNECTION:mysql DB_HOST:ホスト名 DB_PORT:3306 DB_DATABASE:データベース名 DB_USERNAME:ユーザー名 DB_PASSWORD:パスワード 次にアプリケーション用の環境変数APP_KEYとAPP_URLの設定を行います。laravelの環境で以下のコマンドを実行します。 php artisan key:generate --show このコマンド後に表示されたkeyがAPP_KEYになります。base64の部分も含めます。ちなみにbase64はデータの表現方法の一つで、暗号ではないのでこのkeyは盗まれないようにしましょう。ただこのコマンドはキーを生成してそれをコンソール上に表示しているようですが、そもそもlaravelの設定時にAPP_KEYを.envファイルへ書き込んだ気がします(事実書いてあった)。こっちでも良さそうな感じあるんですが、一応参考サイト通りに生成してそっちを書いておきました。 APP_URLは以下の形式です https://Herokuのアプリケーション名.herokuapp.com この2つの環境変数を追加しましょう。メール機能などがあればもっと環境変数が必要になりますが、僕のアプリケーションにはないので省略します。 ビルドやマイグレーション時の設定 先程のページの下にあるAdd Buildpackボタンを押した後、ポップアップが出てくるのでnodejsを選択した後にSave Changesを選択。もう一度phpで同じことをします。ここらへんはよくわかってないですが、ビルドに要るんだろうなあという感じです。 次にローカルのpackage.jsonの編集を行います。下記のスクリプトをpackage.jsonに追加します。スクリプトは既に存在するはずなので追記するだけでいいです。 "scripts": { "heroku-postbuild": "npm run prod" }, 次にアプリケーションを動かすためのコマンドを記述するファイルであるProcfileを作成していきます。laravelアプリフォルダ直下にProfileというファイル(拡張子なし)を作成して以下の内容を記述します。 Procfile web: vendor/bin/heroku-php-apache2 public/ とりあえずこうすればいいんでしょうが、よく分からなかったので調べてみたところProcfileの書き方は process type : command という感じらしいので、今回はwebという process typeでなんかコマンド実行してるっぽいですね。xamppもapacheで構成されてますし、なんかそこらへんのコマンドなんでしょう。もやもやしますが、深堀りしすぎると性格上脱線しやすいのでここら辺にしておきます。 次にvarchar型の文字数の制限をします。laravel,heroku,デプロイで検索すると複数ヒットしますが、どうやらvarchar型の文字数制限を191にしないとマイグレーション時にエラーが出てしまうようです。このエラー対策としてapp\Providers\AppServiceProvider.phpを下記のように書き換えましょう。 use Illuminate\Support\Facades\Schema;//追加 public function boot() { Schema::defaultStringLength(191);//追加 } Heroku CLIでの操作 Heroku CLIのインストールの前に少しだけ解説をしておくと、CLIはCommand Line Interfaceの略で、全部文字でやろうね的なやつです。これと対比されるのがGUIで、視覚的でこちらの方が一般的だと思います。とりあえずここで理解しておいてほしいのはHeroku CLIはherokuを文字(コマンド)で操作するためのものだということです。要するにターミナルでheroku操作したいからHeroku CLIインストールしようという流れです。 こちらのページ[3]から自分が使ってるOSのコマンドをコピペしてインストールしてください。僕はUbuntuなので下記コマンドになります。 sudo snap install --classic heroku ダウンロードが終わったらログインをします heroku login ここで参考サイトとは異なりメッセージが表示される コマンド 'heroku' は '/snap/bin/heroku' で利用できます '/snap/bin'がPATH環境変数に含まれていないためコマンドを特定できませんでした。 heroku: コマンドが見つかりません どうやらパスが通ってないようなので.bashrcにパスを追加して更新。 export PATH="/snap/bin:$PATH" macだとzshになってたりすると思いますが、適宜置き換えてください。 もう一度ログインするとコマンド自体は成功 › Warning: Our terms of service have changed: https://dashboard.heroku.com/terms-of-service heroku: Press any key to open up the browser to login なんか警告出てるので見てみると、利用規約の変更があったっぽい。読んだが特に問題なさそうだったのでスルーしてもう一度実行 › Warning: heroku update available from 7.59.0 to 7.59.1. heroku: Press any key to open up the browser to login or q to exit: 今度はアップデート出来るよと出てるので調べてみると、単純にアップデートすればいいらしい。 heroku update › Warning: update with: snap refresh heroku heroku: Updating CLI... not updatable としたらまたエラー。調べてみると似たような内容の記事が出てきたのでコマンドを実行 sudo snap refresh All snaps up to date. うまくいったかと思いもう一度ログインを試みるも、さっきと同じエラーが出た。もう一度検索してみると違うコマンドが載っていたのでそちらを実行 source <(curl -sL https://cdn.learnenough.com/heroku_install) curlで取ってきて更新してるっぽい。これで警告はでなくなった。警告放置しておくと後々面倒になることが多いので良かった。最終的には一番下のコマンドを実行すれば良かったっぽい。 警告もなくなったので続きにもどる。最初のコマンドの入力したあとEnterキーなどを押すとサイトが開いた。自分の場合はメールアドレスやパスワードなどは入力しなくてもログインボタンを押すだけでログインできた。恐らくクッキーで保存してるからだと思うので、してない人は入力すればいけるはず。ログインボタンを押すとコンソールの方にログインしたと出力が出来たので、とりあえずCLIでログインすることには成功。 デプロイとマイグレーション herokuではデプロイする場合、herokuリモートリポジトリのmaterブランチにローカルリポジトリのmasterブランチからプッシュすることでデプロイすることが出来ます。一応masterブランチ以外からでもデプロイ出来るようですが、基本的にはデプロイ前にはマージしてmasterブランチを更新しておいた方が良さそうです。 設定も色々変えているので、masterへのマージとプッシュしてあることを確認したら、下のコマンドを実行します。 heroku git:remote -a herokuのアプリケーション名 set git remote heroku to https://git.heroku.com/herokuのアプリケーション名.git git push heroku master これで一応アプリケーションのデプロイは完了です。最後のコマンドの後に出てくるURLをクリックすることでも、自分のアプリケーションのサイトに飛べますし、下記コマンドでも開けます(herokuにアプリケーションが複数ある場合は違うコマンドかも) heroku open しかしアプリケーションをwebで開いてみると、真っ白になっている。デベロッパーツールのコンソールを見てみると下記のエラーが Mixed Content: The page at 'https://frote.herokuapp.com/' was loaded over HTTPS, but requested an insecure script 'http://frote.herokuapp.com/js/App.js'. This request has been blocked; the content must be served over HTTPS. どうやらHttp接続をしているようだが、どこかよく分からない。言われているのがビルド後のReactファイルなのでコードの中身ではなさそうと判断し、色々と情報を調べているとこのサイト[6]の一番最後に同様のエラーに対する対処が書いてあった。この手のサイトあまり信用していないのですが、あまりに情報がないのでとりあえず実行。下記のような環境変数を追加 Key : Value ASSET_URL : https://herokuのアプリケーション名.herokuapp.com これを追加したところ表示された。正直なんでかよく分かっていないので調べたが全然情報が出てこない。恐らくhttps通信にすることが出来たのだろうが、そもそも何が原因だったのか。とりあえず動いたので一旦このままにしておくが、後々原因を究明したほうが良さそう。何か分かる人いたら教えてください。 とりあえず表示までは出来たのでDBを使用するためにマイグレーションしていく。下記コマンドでマイグレーション heroku run php artisan migrate コマンド入力後に** Do you really wish to run this command? (yes/no)**と聞かれるのでyesと入力。しかしエラーが出て、試行錯誤しているうちに既にテーブルが作成されているためマイグレーションがうまく行かなくなってしまった(エラーを保存し忘れていました。すみません)。コマンドを見る感じheroku runのあとにartisanコマンドが出来そうなので、下記コマンドでデータベースの再構築を試みる。 heroku run php artisan migrate:refresh しかし、同様にテーブルが存在するので無理だとエラーが出る。色々と調べてみるとこちら[7]でrefreshでなくfleshならうまくいったと書いてあったので実行してみる。 heroku run php artisan migrate:fresh このコマンド後にマイグレーションに成功。2つのコマンドの違いを調べたところrefleshはロールバックしてから実行するのに対し、freshは削除してから実行しているよう[8]。何回も実行していたのでロールバックしても同じような状態だったのかもしれない。この後に動作確認をしたが、プログラム通りではない動作は存在しなかった(本番環境になったことで見えてきた修正点はあったが)。とりあえずデプロイ自体は完成したよう。 その他調整 アプリケーション名を開発環境だと適当にしていたので、本番環境で名前を変えたくなった。環境変数に以下を追加で解決。 key:value APP_NAME : アプリケーション名 デプロイ後のアプリケーションの感想 遅い!!とにかく遅い!!herokuは遅いから辞めておけという記事を見て、まあ言うほどでもないやろと思っていたがこれはひどい。おかげでローディング時のUIの大切さの理解や遅すぎて動作がはっきり見えるためデバッグしやすいなどの利点もあったが、これは不便さを感じるレベルで無理ですね。まあ無料でこんな簡単に利用してる側が文句言うのはおかしな話なので感謝しなければならないのですが。リージョンに東京がないためアメリカにしているのが結構効いてそうですね。やはりちゃんとしたサービスを展開したいならAWSなどの方が良さそう(やったことないのでどうなのか分かりませんが)。とはいえ完全無料でここまで出来るので、勉強がてらデプロイの理解などをするにはちょうど良さそう。 一応デプロイしたアプリケーションtoDoリストアプリケーションFroteはこちら[9]になります。 おわりに 超簡単という感じでは無かったですが、ちゃんと調べればデプロイは手軽に出来ると思います。ただ自分の真剣に作ったアプリケーションをherokuにデプロイしようとはちょっと思えませんでしたね。アプリケーションの修正とCI/CDだけしたらAWSに移行しようと思います。 参考文献 [1]:heroku [2]:君に届け [3]:The Heroku CLI [4]:Herokuコマンド アップデート [5]:Heroku コマンド を最新バージョンにアップデートする方法 [6]:HerokuにReactjsアセットを使用してLaravel8Webサイトをデプロイする [7]:マイグレーション実行によってHeroku上に作成されたテーブルを消して、再度マイグレーションする方法 [8]:(Laravel) migrationやり直しコマンドあれこれ [9]:Frote [10]:Laravelをherokuにデプロイする方法・手順の解説(MySQL使用)〜コマンドを使わず、Herokuのサイト上で設定するよ!〜
- 投稿日:2021-10-25T15:32:14+09:00
はじめてのJavaScript⑨ 「繰り返し処理 "while"」
目次 1.はじめに 2.whileの使い道 3.構文 4.例題 5.おわりに 1. はじめに 本記事では、JavaScriptの「繰り返し処理 "while"」について記載する。 2. whileの使い道 ・回数が決まっていない場合の繰り返し処理に使用される。 ・条件がtrueの間に繰り返し処理が実行されることが特徴である。 ・while文で書いた繰り返し処理はfor文に書きかえることが可能である。 3. 構文 構文は以下のようになる。 index.js while (条件式) { //条件式がtrueのときに実行したい処理 } 4. 例題 例題は以下のようにする。 変数jの値を1から3まで出力するプログラムをwhileを使って記述 変数の設定 まずは変数jを設定する。 例題の条件として、値の最小値は1なので、変数は1としている。 index.js let j = 1; while文の記述 index.js while (j <= 3) { console.log(j); j++; } ・条件式の()内は、変数jが3までになるまで実行されるという意味。 (変数jの初期値は1なので、例題に沿っている) ・console.logの()にjを入れて変数jを出力している。また、jに1ずつプラスすることによって条件を満たすようにプログラムさせている。 なお、デベロッパーツールで表示すると以下のようになる。 5. おわりに 次項:鋭意作成中
- 投稿日:2021-10-25T15:22:29+09:00
オンラインプログラミングを学ぶための10のウェブサイト
プログラミング言語の基礎を学ぶとき あなたは長い間理解し、覚えることができるように練習したいと思うでしょう。 したがって、以下のウェブサイトは、子供たちがより長く覚えるための演習を解決する際にその知識を適用するために生まれました。 これらのサイトの目的は、プログラミングスキルのテストを支援することです。 低いものから高いものへと問題を解決することによって。 それだけでなく、あなたはしなければなりません より高いランキングを達成するために他の開発者と競争してください。 それはゲームをするようなものです。 あなたはブロンズ、シルバーからゴールド、プラチナのような低ランクから行かなければなりません...これらのページが場所になることを願っています プログラミングへの関心を高め、新しい知識を学ぶのに役立ちます。 Exercism Exercism Webサイトには、50を超えるサポートされているプログラミング言語で練習できる何千もの演習があります。 その演習も非常に多様で、初心者やプログラミング業界で多くの経験を持つ人に提供されています。 オープンソースであり、世界中のボランティアプログラマーの貢献に依存しているため、費用は一切かかりませんのでご安心ください。 さらに、自分に能力があると感じた場合は、コミュニティに貢献して、他の学生が役立つ演習を行ったり、自分で知識を追加したりできるようにすることもできます。 Exercism CodeWars CodeWarsのWebサイトは、その名前が示すように、各問題を解決する場所であり、簡単なものから難しいものまで上位にランクされています。 サイトはキュウと呼ばれるポイントに基づいて評価を計算し、このスコアはあなたが解決している問題の程度に応じて上下します。 問題の解決策を見つけたら、他の人の答えを見て、それらを自分のコードと比較して、スキルをさらに向上させることができます。 CodeWars CodeChef CodeChefのWebサイトは、インドのソフトウェア会社Directによって設立された非営利の教育機関として知られています。 オンラインエディタを使用して、問題をすばやく解決できます。 さらに、問題はレベルに応じてサイト上で分類されます。 このサイトのハイライトは、他のプログラマーとコミュニケーションを取り、疑問に思っている質問をしたり、他のプログラマーの質問に答えたりできることです。 CodeChef CodinGame CodinGameのWebサイトでコードを学習しながら、実際にゲームをプレイできます。 それは私たちがよりリラックスして幸せな方法で問題を解決することを可能にするだけでなく、問題解決への私たちの興味を高めます。 CodinGameは25以上の言語をサポートしており、世界最高のプログラマーから多くのアルゴリズムやトリックを学ぶことができます。 数学の問題は、簡単なものから難しいものまで、すべての人の学習ニーズを満たすために多くの異なるレベルに分けられます CodinGame HackerRank HackerRank Webサイトは、コーディングスキルの向上を目指すすべての人に人気のあるサイトです。 世界クラスの大会では、初心者だけでなく才能のあるプログラマーも参加できます。 また、採用会社が主催するコンテストを通じて、適切な仕事を見つける機会もあります。 それは主にアルゴリズム、AI、データベース、数学に焦点を当てています... HackerRank CoderByte CoderByte Webサイトは、コーディングスキルの練習と向上を可能にするWebアプリケーションです。 難しいものから簡単なものまで、プログラミングのすべてのレベルに分類されている多くの問題があります。 さらに、マイクロソフト、グーグル、フェイスブックなどの大企業への面接の質問は、これらの企業に応募する際に適用するテクニックを理解するのに役立ちます。 欠点は、高度なタスクにアクセスしてここでコースを受講するのに月額約35ドルかかることです。 CoderByte FreeCodeCamp FreeCodeCampのWebサイトはおそらく私のお気に入りです。 それは私たちが新しい知識を学び、私たちが学んだことを実際の問題解決に素早く適用するのに役立ちます。 さらに、学習したばかりのスキルに関するプロジェクトを完了すると、無料の証明書を受け取ります。 このコースのプログラミング言語は、HTMLやCSSの学習など、プログラミングに不慣れな人のために特別に設計されています。 コーディングプロセス中に質問に答えるのに役立つ専用のフォーラムもあります。 FreeCodeCamp Edabit Edabit Webサイトでは、このサイトでのライブの問題解決を通じて、言語の基本と微妙な点を学ぶことができます。 問題を解決するたびに、ポイントはxpで自動的に計算されます。 ポイントが多ければ多いほど、レベルは高くなります。 あなたや他のプログラマーがリーダーボードのトップに立つためにポイントを競うのは楽しいですね。 また、コーディングの面白さを増し、実際のプログラマーのように問題を考えて解決するのに役立ちます。 Edabit TopCoder TopCoderサイトは、問題解決レベルがかなり難しい挑戦的なサイトであり、通常、プログラミングの経験が豊富な人を対象としています。 特定の問題を解決する必要のある企業と、世界中の開発者によるソリューションをつなぐ場所です。 したがって、ソリューションが他のプログラマーよりも優れている場合は、賞品を獲得できます。 さらに、解決された問題を確認して修正し、Webサイトに適用することができます。 TopCoder Codepen Codepen Webサイトは、さまざまなプログラミング言語で事前に記述されたコードを使用するプログラマーのためのソーシャルネットワークと見なされているため、他のプログラマーと学習して対話することができます。 もう1つの興味深い点は、毎週Webサイトに解決する必要のあるトピックがあり、あなたと他の人が問題を迅速かつ最適に解決するために競争していることです。 Codepen 概要: これを通じて、この記事がコーディングへの関心を見つけ、問題を解決するのに役立つことを願っています。 より良い方法でトピックを作成します。ご不明な点がございましたら、メールをお送りください。できるだけ早く返信いたします。 楽しみにしている もっと良い記事を書けるように、引き続きサイトを応援してください。 良い1日を! 参照リンクen.niemvuilaptrinh.com
- 投稿日:2021-10-25T14:06:37+09:00
「こんなはずじゃなかった」どこで間違えた俺の個人開発
この記事では賢い知識は得られません。息抜き程度に見ましょう。 こんなはずではなかった 「なぜだ、なぜこんなに伸びないんだ」 そう思いました。アプリを始めて開発した時には。 自分で作った初めての個人開発アプリ半年ほどかけて開発し、 公開しましたが引くほど使われていません。 「半年もかけたのに」 「もぅマヂ無理、ちょぉ頑張ったのに」 「ゥチのことゎもぅどぉでもぃぃんだって」 「docker-compose downしょ」 なぜこうなりましたか? 一緒に見ていきましょう。 開発とマーケティングはどっちも大事 自分には個人開発に取り組む動機に少し問題があったような気がします。 ここで少し、自分の経歴についてお付き合いください 私の経歴 (頑張って読んで) 私は元々、新卒で某ITベンチャーでバックエンドエンジニアとして働いていました。 そこではプロジェクトのサブリーダーとして働いていましたが、 打ち合わせやMTGが多くコードを存分に書ける時間はそれほど多くなかったのかなぁと思います。 幸いなことに、なぜかサブリーダーの私よりも そのメンバーとして働いている方々の方が優秀だったので、 楽させてもらっている方だったのですが ワガママ言うともっとコードを書きたいと思っていました。 詳しくはこちらの記事でも書いているので、よかったら見て下さい。 そんな折、転職をきっかけにポートフォリを作るようになりました。 楽しかったです。自分で全部決めて書けることが嬉しく思えました。 元々は技術力を増やすための転職を考えていたのですが ポートフォリを作る内次第にこう思うようになりました。 「あれ、既にもうなんでも作れるんじゃね?」 「実は、技術力はそれなりにあって、実はもう独立できるんじゃね?」 皆さんもお気づきかもしれませんが、これは甚だしい勘違いです。 「殴ってやりたい」と思いましたか? それは正しい感情です! ですが筆者は豆腐メンタルなので鋭い批判は避け、意見するときは赤ちゃん言葉でコメントしましょう。(ex. 勘違いがすぎるでちゅ) そんな経緯で私は個人開発を始め、これで生計を立てることを目標にしました。 開発10割 マーケティング0割 僕の「個人開発」という列車は 使用技術を自分で決めれる 設計を全部自分でできる デザインも自分で全部決めれる という欲求を満たすことを動機に発進してしまいました。 ユーザーのニーズ調査などもせずにドンドン進んでいきます。 「楽しい!楽しい!(脳死)」 そしてアプリの開発が進んできてふと思います。 「これってちゃんと使ってもらえるのかな?」 「どうやって宣伝しよう」 「...まぁモノさえ良ければ使ってもらえるだろう!」 そして私は考えるのをやめました。 この時点で半年が経過していました。 ここまで見てきて皆さんは何が問題だと思いますか? たくさんありますよね。ほんと、どうしましょう。 自分が思うに問題点は以下かなと思っております。 ニーズ調査・フィードバック集めを行わない 作り込みすぎる 自分のアプリを客観視できていない 要するに独りよがりで、マーケティングの部分を疎かにし過ぎたのだと思います。 ではどうすべきだったのでしょう? これから上記の問題への対応を考えます。 ニーズ調査・フィードバック集めを行わない どれだけ手を込めて作っても使われなければ意味がありません。 自分の作ろうとしているアプリを必要としている人がいるのか? 自分自身本当に必要なアプリだと思えるのか? これを考える必要が大切でした。そのためにもQiitaやTwiiterなどのSNSを日頃から頑張っておいて、フィードバックを返してくれる人々を集める必要があったのかなと思います。 私は高校生時代からTwitterなどといったSNSに乗り遅れて以来、 謎のプライドでSNSをやらないという姿勢を貫いてきました。 大間違いでした。最近始めました。フォローお願いします。。。 筆者のTwitterのプロフィール また、個人開発向けのアプリなども存在しているので、そのアプリが活発になればいいなぁと思っています。 一応、皆に知ってもらうためにGoogle広告などを打ってみましたが、全然うまくいきませんでした。 付け焼き刃でやっても、ユーザーが必要としていないならあまり意味がない気がします。 作り込みすぎる 作り込み、ダメ、ぜったい 私も元々はアプリを開発するために、「なるべく作り込みすぎないである程度作ったら公開するという姿勢を取ろう。その姿勢でいくつかアプリを作ってから反応の大きかったものだけ伸ばそう」と考えていました。 ですがいつの間にか沼に入って作り込みすぎちゃいました。 「中途半端な作り込みで公開しても誰も触ってくれないだろう」 そういった考えで作り込みすぎちゃいました。 また、これは誰が使うの...?というちょっとでも批判的な意見を受けるのを恐れていた部分があります。 しかし、そうは言っても使われないアプリを自分で作り続けても報われません。 勉強目的で個人開発をされている方はそれで全く問題ないのですが、 これで食ってきたいと考えている自分として全くの無意味です。 過去の自分を説き伏せたい気持ちでいっぱいです。 今の自分「やめて!それ以上作り込みすぎないで!」 過去の自分「うるせぇ、作りこめば注目されて使ってもらえるんだ!」 過去の自分「iPhoneだって売り出すまでは駄作と言われていたけど」 過去の自分「そのクオリティに惹かれて今では当たり前になっているんだ」 今の自分「落ち着いて、あなはビルゲイツではないわ!」 自分のアプリを客観視できていない 自分だけかもしれませんが、ついつい自分で作ったアプリが最高だと思っちゃいます。 なぜでしょうか。教えてください。 ゆえに引き時が分かりません。 このためにもフィードバックを受けておく必要があります。 自分だけで考え、作っていると追加機能がどんどん思いついて、 創作意欲が刺激され、いずれ、とんでもないキメラが生まれてしまうということを 忘れてはいけないということを学びました。 僕が冷酷無比な錬金術師であれば、それでいいのかもしれませんが、 残念ながら僕は心優しいエンジニアもどきなので、キメラは作らない方がいいでしょう。 (伝われ) 自分が正しいことばかりをしているのではないということを学び、 開発を続けていくことで、適切に戦略的撤退を行うべきだと思います。 まとめ 個人開発には冷静さが必要で、以下を意識して開発する必要があることを書きました。 ニーズ調査・フィードバック集めを行う 作り込みすぎない 自分のアプリを客観視する 失敗して学ぶことも重要と言われますが、 失敗しなくても分かることは経験しない方がいいと思います。 この記事が参考になるかは謎ですが、もうそうなれば幸いです。 ところで私は新しく個人開発するアプリに 「Progateみたいなゲーム感覚でSQLを学べる学習サイト」 を作ろうとしています。これはどうですか? ProgateでもSQLも学べますが、 よりSQLに特化したアプリを作ろうと思います。 いけると思いますか?無理そうですか? 皆さんの意見を聞かせて下さい。 必要となれば僕がキメラを作る前に止めて下さい(厨二) 最後に 僕はやめろって言ったんですけど、この記事で失敗例として取り上げている アプリのリンクを貼っておけって、僕の中の「まだ耐えられる勢」が言ってくるので リンクを貼っておきます。僕はやめろって言ったんですけどね。 僕はやめろって言ったんですけど、 僕の中の「自己顕示欲」もTwitterのフォローを もう一回お願いしておけって言ってくるので貼っておきます。 僕はしつこくなるからやめたほうがいいって止めたんですけどね。すいませんね。 筆者のTwitterのプロフィール
- 投稿日:2021-10-25T13:13:07+09:00
webpack.config.js側で`--mode`の値によって処理を分岐する
よくあるパターン package.json "scripts": { "build": "NODE_ENV=production webpack --mode production", //← NODE_ENVという環境変数を作ってる "dev": "webpack --mode development", ... } なんだか冗長に感じませんか? 正直、開発までのレベルだとそんなに困ることはないかと思いますが、それでも依存するファイルやコマンドは分散したくないです。 なので、こんな感じのシンプルなままキープしたい。 package.json "scripts": { "build": "webpack --mode production", "dev": "webpack --mode development", ... } 公式にありました。 If you want to change the behavior according to the mode variable inside the webpack.config.js, you have to export a function instead of an object: Mode | webpack 上記ページの公式的な手法としてはこの様になっています。 webpack.config.js var config = { entry: './app.js', //... }; module.exports = (env, argv) => { if (argv.mode === 'development') { config.devtool = 'source-map'; } if (argv.mode === 'production') { //... } return config; }; 予め設定用のオブジェクトを定義しておき、--modeの内容(など)を受け取れる関数の形にして、その値からオブジェクトに追加・変更したものをreturnすることで、処理を分岐できるようです。 しかしこの書き方だと、値を更新するのが困難な場合がありますし、個人的にもう少し直感的に読みやすい方がが好きなので下記のようにしてみました。 webpack.config.js //modeを利用する部分のみ抜粋 module.exports = (env, options) => { isCssSourceMapEnabled = options.mode !== 'production' ? true : false return { ... sourceMap: isCssSourceMapEnabled, } } これは--modeがproduction以外の場合のみsourceMapをtrueにする例です。開発中はSCSSのソースマップを出力してほしいけど、本番環境にはソースマップは不要というよくあるお悩みです。 (以前は第一引数からmodeを参照できたようですが、仕様が変更された模様) もう少し略さず書くとこの様になり、公式の書き方だと少し困難であることがおわかりになると思います。 webpack.config.js module.exports = (env, options) => { isCssSourceMapEnabled = options.mode !== 'production' ? true : false return { module: { rules: [ { test: /\.scss$/, use: [ ... { loader: "sass-loader", options: { ... sourceMap: isCssSourceMapEnabled, こちらも万能ではなく、変更する部分ごとに変数を定義することになり、変更箇所が多い場合など、向いていない場合もあるかと思います。また予め別のファイルなどで環境変数を定義している場合もあると思います。 少し特殊なWebpack.configの書き方の例として、こんなのもあるということを覚えておくと役立つことがあるかもしれません。
- 投稿日:2021-10-25T12:41:29+09:00
S3 + CloudFront の環境下で、JavaScriptファイルが読み込まれない時の解決方法
どんな時に読み込まれないのか Chromeの開発者ツール(F12)でページを見たときに、 下記のエラーメッセージが出ていれば、後述する対処法に従うことで解決する可能性が高いです。 Refused to execute script from '<スクリプトのURL>' because its MIME type ('binary/octet-stream') is not executable, and strict MIME type checking is enabled. 原因 読み込もうとしたファイルのMIMEタイプが実行不可能なタイプ( binary/octet-stream )であるため 現在閲覧中のWEBサーバーで厳格なMIMEタイプ判別を行っているため エラー説明文のままですが… 説明 binary/octet-stream は、正体不明のバイナリーファイルに用いられます。 厳格なMIME判別を行うWEBサーバーにおいて、このタイプはダウンロードされるのみで、 ブラウザ上で実行されないために、当該のJavaScriptファイルは読み込まれません。 S3アップロード時(PUT)のContent-Typeの既定値が binary/octet-stream らしく、 一部のファイルはこれが原因で表示されないみたいです。 何がトリガーで binary/octet-stream が設定されるのかはよくわかってません。すいません 解決方法 Step1. S3でファイルのContent-Typeを application/x-javascript に変更する ※HTMLファイル上の記述に以下のように type="text/javascript"を追加しても意味はないです。S3でメタデータを編集する必要があります <script src="common/js/common.js"></script> ↓ <script type="text/javascript" charset="utf-8" src="common/js/common.js"></script> S3コンソールで以下の操作をします Amazon S3 > {S3バケット名} > common > js を開く common.js にチェックを入れて、 アクション > メタデータの編集 をクリック ※バケット名やディレクトリ名は皆さまのお使いの環境で読み替えてください メタデータ タイプ: システム定義 キー: Content-Type 値: binary/octet-stream -> application/x-javascript に変更 メタデータの編集ボタンを押す ※ディレクトリ内のファイルを一括で変更するには以下のようにします Amazon S3 > {S3バケット名} > common > js を開く ディレクトリ左上にあるチェックボックスをクリックして一括チェック後、 アクション > メタデータの編集 をクリック ※バケット名やディレクトリ名は皆さまのお使いの環境で読み替えてください メタデータの追加 タイプ: システム定義 キー: Content-Type 値: application/x-javascript メタデータの編集ボタンを押す これにより、もともと定義されていた Content-Type の値が上記で設定した値に上書きされます。 Step2. CloudFrontのキャッシュを削除する メタデータを編集しても、キャッシュが削除されなければ反映されません。 ブラウザのキャッシュを削除しても、CDNサーバー上にキャッシュが残っているので無意味です。 CDNサーバー上のキャッシュを削除する必要があります。 以下の操作でCDNサーバー上のキャッシュを削除します。 CloudFront > ディストリビューション > {ディストリビューションID} キャッシュ削除 > キャッシュ削除を作成 をクリック オブジェクトパスを追加 /common/js/common.js ※ディレクトリ名やファイル名はお使いの環境で読み替えてください キャッシュ削除を作成 をクリック ステータス: 進行中 が 完了済み になるまで待ちます ※一括でキャッシュ削除するなら以下のようにします オブジェクトパスを追加 /common/js/* キャッシュ削除を作成 をクリック ステータス: 進行中 が 完了済み になるまで待ちます Step3. 動作確認 ステータスが完了になった後、改めてChromeで当該ページを開くと、 ちゃんと読み込まれるようになると思います。 開発者ツール(F12)を開いた状態でページをリロードした後、 開発者ツールの ネットワーク タブを開き、ちゃんと読み込まれていなかったJavaScriptファイルをクリック、 ヘッダー > レスポンスヘッダー > content-type の値が application/x-javascript に変わっていればOKです。 参考URL https://qiita.com/hihats/items/33872f88cf21b5aca952 https://kenjiszk.hatenablog.com/entry/2014/05/08/090733 http://norm-nois.com/blog/archives/2968 https://qiita.com/baby-0105/items/0356b0af4ab4585d86c4
- 投稿日:2021-10-25T12:38:14+09:00
複数のモジュールをfor文によって一括で動的にimportする(JavaScript)
script.js const moduleNames = ['module1', 'module2', 'module3']; const modules = {}; for(let i=0;i<moduleNames.length;i++){ import('./modules/' + moduleNames[i] + '.js') .then((module)=>{ modules[moduleNames[i]] = module.default; }); } ただしフォルダ構成が次のようになっているとする。 module1.js, module2.js, module3.jsたちはexport defaultしている。 root/ ┣ script.js ┗ modules/ ┠ module1.js ┠ module2.js ┗ module3.js 動的インポートと呼ばれるものを使う。 静的インポートとの違いは、Promiseで返って来るところ。 静的インポートはトップレベルのスコープでしか使えない。 for文の中で静的インポートをしようとするとUncaught SyntaxError: Unexpected identifierのエラーが出る。 なんとかforで回せないかとこねくり回したらできた。 ただし参考ページ import - JavaScript - MDN Web Docs https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import には、 動的インポートは必要な場合にのみ使用してください。 とある。こういう使い方は非推奨なのかな。
- 投稿日:2021-10-25T11:58:30+09:00
bignumber.jsの使い方 ~Javascriptの小数での計算の誤差をなくすライブラリ~
はじめに これは、bignumber.jsのメソッドなどの備忘録です。 小数の計算に悩んでいた方の役に立てると幸いです。 実行環境 BigNumber.jsのバージョン: v9.0.1 テストしたブラウザ: Google Chrome 95.0.4638.54(Official Build) (x86_64) bignumber.jsとは Javascriptで小数の計算をすると、丸め誤差というものが発生してしまう場合があります。 console.log(0.1 + 0.2); //0.30000000000000004 0.3になるはずなのですが、誤差が出てきてしまいます。 Javascriptでこのような計算をせず、サーバーサイドに任せるのが最適かもしれませんが、どうしてもの場合に、bignumber.jsを使います。 なぜ誤差がでてくるのか コンピュータ内部で小数を2進数に変換したときに、循環小数などになってしまい近似値を使わなければならなくなるためです。詳しくはこのサイトを参照してください。 インストール npm(Node.js)でインストールできます。 npm install bignumber.js または、Githubからダウンロードできます。 git clone https://github.com/MikeMcl/bignumber.js.git bignumber.jsを使う 1. bignumber.jsを読み込む ブラウザで読み込む: <script src="./path/bignumber.js" type="text/javascript"> または、 <script type="module"> import BigNumber from './path/bignumber.mjs'; ... </script> Node.jsを使う: const BigNumber = require('bignumber.js'); 2. 数を定義する Object型なので、浮動小数点型よりは面倒くさいです。 いくつか宣言方法があります。 let num = new BigNumber(0.1); console.log(num.toString()); //'0.1' //その他の宣言方法 BigNumber(0.1); //'0.1' newは省略可能 new BigNumber('32e-5'); //'0.00032' 指数表記も可能 new BigNumber(1101, 2); //'13' 基数2を指定 new BigNumber('123.456'); //'123.456' 文字列値から生成 newは省略することができて、指数表記を使うことができます。 基数(n進法のnの数字のこと)を第2引数に指定することもできます。 3. 基本の演算 警告 普通の演算子を使用することはできません。 //足し算 BigNumber(0.1).plus(0.2); //'0.3' //引き算 BigNumber(0.1).minus(0.2); //'-0.1' //掛け算 BigNumber(0.1).times(0.2); //'0.02' //割り算 BigNumber(0.1).div(0.2); //'0.5' //商の整数部 BigNumber(0.1).idiv(0.03); //'3' //割ったときのあまり BigNumber(0.1).mod(0.03); //'0.01' //累乗 BigNumber(0.1).pow(2); //'0.01' 基本的な算術演算です。 関数名が長いものには短縮形の関数もあります。 上は全て短縮形です。 くわしくはこちらを参照してください(英語です)。 また、 BigNumber(0.3) - BigNumber(0.2); //'0.09999999999999998' とやってもうまく動かないのでやらないようにしましょう。 4. その他のメソッド 1. 絶対値 abs()を使う。戻り値はBigNumber。 BigNumber(-0.1).abs(); //'0.1' 2. シフト(小数点を動かすこと) shiftedBy(n)を使う。戻り値は、BigNumber。 BigNumber(0.123).shiftedBy(3); //'123' BigNumber(12.3).shiftedBy(-2); //'0.123' 3. 平方根 sqrt()を使う。戻り値は、BigNumber。丸められる。 丸める設定をするのは後述します。 BigNumber(0.01).sqrt(2); //'0.1' BigNumber(0.1).sqrt(2); //'0.3162277660168379332' 4. 正負の逆転 negated()を使う。戻り値は、BigNumber。 BigNumber(0.4).negated(); //'-0.4' BigNumber(-4.6).negated(); //'4.6' 5. BigNumberを比べるメソッド 1.大小どちらも comparedTo(n [, 基数])を使う。([]内は書いても書かなくても良い引数) 戻り値は、Number。 戻り値について: 戻り値 大小 1 BigNumberがnよりも大きい -1 BigNumberがnよりも小さい 0 BigNumberとnがおなじ null BigNumberかnがNaN BigNumber(3.5).comparedTo(2.6); //1 BigNumber(2.8).comparedTo(BigNumber(6.5)); //-1 BigNumber(-1.3).comparedTo(-1.3); //0 BigNumber(4.6).comparedTo(NaN); //null 2.等しいかどうか .eq(n [,基数])を使う。戻り値はBoolean。 NaN同士を比べても、falseになります。 BigNumber(0.1).eq(0.2); //false BigNumber(255).eq('ff', 16); //true BigNumber(NaN).eq(BigNumber(NaN)); //false 3.不等号たち gt(n [,基数])などを使う。戻り値はBoolean。 他の関数もおなじ引数でおなじ戻り値です。 BigNumber(0.1).gt(0.3); // > BigNumberのほうがnより大きい false BigNumber(0.1).gte(0.1); // >= BigNumberはn以上 true BigNumber(0.1).lt(0.2); // < BigNumberのほうがnより小さい true BigNumber(0.3).lte(0.1); // >= BigNumberはn以下 false 6. 〜をであるかを調べるメソッド 1. BigNumberかを調べる isBigNumber(value)を使う。戻り値はBoolean。 BigNumber.isBigNumber(3); //false BigNumber.isBigNumber(BigNumber(3.5)); //true BigNumber.isBigNumber(BigNumber(NaN)); //true 2. 整数かを調べる isInteger()を使う。戻り値はBoolean。 BigNumber(123.456).isInteger(); //false BigNumber(123).isInteger(); //true 3. NaNかを調べる isNaNを使う。戻り値はBoolean。 BigNumber(Math.sqrt(-1)).isNaN(); //true BigNumber(3).isNaN(); //false 4. 正負を調べる BigNumber(3.5).isPositive(); //true 正かどうかを調べる BigNumber(-0).isNegative(); //true 負かどうかを調べる BigNumber(13).isZero(); //false 0かどうかを調べる 7. BigNumberを他の型に変換 Numberへ toNumber()を使う。戻り値はNumber。 BigNumber(4.2).toNumber(); //4.2 Stringへ toString([基数])またはtoFixed([小数点以下の桁数 [, 丸めモードの指定]])を使う。戻り値はString。 丸めモードは、後述します。 BigNumber(3.4).toString(); //'3.4' BigNumber(2).toString(2); //'10' BigNumber(6.7).toFixed(); //'6.7' BigNumber(34.6157984).toFixed(3); //34.616 分数へ(BigNumberの配列) toFraction([n])を使う。戻り値は[BigNumber, BigNumber]。 引数のnには、最大の分母を指定することができます。 その場合は、最大の分母を超えないように近づけたものが返されます。 BigNumber(1.45).toFraction(); //['29', '20'] const x = new BigNumber(3.14159265358979); x.toFraction();//['314159265358979','100000000000000'] x.toFraction(100); //['311', '99'] 丸めモードの設定 BigNumber.set()またはBigNumber.config()を使う。 どちらも全く同じことをやっています。 詳しくはサンプルコードを参照してください。 BigNumber(1).div(3); //'0.33333333333333333333' BigNumber.set({ DECIMAL_PLACES : 5, //小数点以下の最大桁数 ROUNDING_MODE : BigNumber.ROUND_UP //丸める方法(切り上げ) }); BigNumber(1).div(3); //'0.33334' 設定項目はもっといっぱいありますが、ここで取り上げるもの (丸めモードに関係があるもの)以外は公式ドキュメントをご覧ください。 ROUNDING_MODEに入る値 (数直線でイメージしてください。マイナスの場合の対応です。) キーワード 値 説明 ROUND_UP 0 切り上げ(0から離れていく) ROUND_DOWN 1 切り下げ(0へと近づいていく) ROUND_CEIL 2 切り上げ(正の方向へと行く) ROUND_FLOOR 3 切り下げ(負の方向へと行く) ROUND_HALF_UP 4 四捨五入(0から離れていく) ROUND_HALF_DOWN 5 四捨五入(0へと近づいていく) ROUND_HALF_EVEN 6 基本は四捨五入だが、5の場合には、偶数になる方に丸められる。 ROUND_HALF_CELL 7 四捨五入(正の方向へと行く) ROUND_HALF_FLOOR 8 四捨五入(負の方向へと行く) ちなみに、デフォルトではDECIMAL_PLACES: 20で ROUNDING_MODE: ROUND_HALF_UPです。 BigNumberを丸める dp([n[, 基数]])を使う。戻り値は、nを指定した場合はBigNumber, 指定しなかった場合はNumber。 nを指定した場合は、小数点以下のn桁目までを残して、 ROUNDING_MODEに従い丸められます。 nを指定しなかった場合には、小数点以下が何桁あるかを返します。 BigNumber(3.515145).dp(3); //'3.515' BigNumber.config({ROUNDING_MODE: BigNumber.ROUND_CEIL}); BigNumber(3.515145).dp(3); //'3.516' BigNumber(3.515145).dp(); // 6 BigNumberを整数に丸める integerValue([基数])を使う。戻り値はBigNumber。 丸めモードに従って丸められる。 BigNumber(2.51798).integerValue(); //'3'(ROUND_HALF_UP) BigNumberそのものを複製する BigNumber.clone()を使う。戻り値は、BigNumberコンストラクタ。 BigNumber.config() (BigNumber.set()) が複雑になるときにこうすると良いです。 BigNumber.set({ROUNDING_MODE: BigNumber.ROUND_FLOOR}); const copy = BigNumber.clone(); copy.config({ROUNDING_MODE: BigNumber.ROUND_CEIL}); //このように省略できます。 //BigNumber.clone({ROUNDING_MODE: BigNumber.ROUND_CEIL}); BigNumber(1.564635).dp(5); //'1.56463' copy(1.564635).dp(5); //'1.56464' 参考リンク BigNumber公式API BigNumberのGitHubリポジトリ Qiitaの記事 誤差について おわりに この記事でほとんどのメソッドなどを紹介しましたが、一部紹介していないものもあります。 使いみちがあまりなさそうだ、と勝手に判断したものですが、公式ドキュメントなどを参考にして読んでみてください。また、誤りがありましたら、コメント欄にてお知らせしていただけると嬉しいです。
- 投稿日:2021-10-25T11:24:53+09:00
モダンJavaScript基本構文をまとめ(前半)
モダンJavaScript基本構文をまとめ(前半) 講座で出た構文をcodesandboxで試したので、その内容のまとめ。 変数宣言 constとletでの変数宣言 今まで使われていたvarを使った変数宣言は、再宣言や再代入が可能だったため、意図せず変数を書き換えてしまいバグの温床となることがあった。 そこで新たにconstとletという変数宣言のコードが追加された。 letを用いた場合・・・(1) 再宣言は不可だが、再代入は可能 書き換える必要がある処理の際にはletを利用する constを用いた場合・・・(2) 再宣言、再代入ともに不可 そのため現在はほとんどconstで宣言をする 配列やオブジェクトのプロパティを変更することは可能・・・(3) 確かにconstだけで大体良さそう。。。 /** * 変数宣言 varの場合、再宣言可能、再代入可能 */ var food = "soba"; console.log(food); //soba var food = "udon"; //再宣言可能 console.log(food); //udon food = "somen"; //再代入可能 console.log(food); //somen /** * 変数宣言 letの場合、再宣言不可、再代入可能・・・(1) */ let age = 34; console.log(age); //34 //let age = "34歳"; //エラー 再宣言不可 age = 35; //再代入可能 console.log(age); //35 /** * 変数宣言 constの場合、再宣言不可、再代入不可・・・(2) */ const address = "山梨"; console.log(address); //const address = "静岡"; //エラー 再宣言不可 ////address = "東京"; //エラー 再代入不可 /** * 配列やオブジェクトの場合constでも中身の変更は可能・・・(3) */ const introduction = [name, age, address]; console.log(introduction); introduction[0] = "dende"; //[dende,"35","山梨"] //配列の中身は変更可能 const myHobby = { sports: "capoeira", book: "少年ジャンプ", anime: "氷菓" }; console.log(myHobby); myHobby.anime = "新世界より"; //{sports: "capoeira", book: "少年ジャンプ", anime: "新世界より"} //オブジェクトの中身も変更可能 //以上の動きから、メインで使う変数の宣言はconstとなる //変更の必要があるものだけletで宣言となり、varはほぼ使わない 関数宣言、アロー関数、テンプレート文字列 =>が出てきたらアロー関数 従来の関数宣言ではfunctionが使われていた・・・(4) アロー関数は「=>」を使って関数を宣言する。記号が矢印のように見えるためアロー関数と名付けられた。 (引数) = > { 処理内容 }の形で記述する・・・(5) 処理内容が一行で完結する場合、{ }とreturnを省略することができる・・・(6) 引数が一つの場合引数を囲む()も省略できる・・・(7) コードの冗長化を防ぎ短く記述できるが、上記の法則を知らないと読むのが難しい。 テンプレート文字列・・・(8) 従来文字列の結合では"A"+"B"という形で記述していた。 テンプレート文字列は文字列の中に${ ”ここにJavaScriptで記述する”}とすることで、+記号を使わずに文字列の結合をすることができる。 /** * 関数の宣言、実行(アロー関数の記法) * テンプレート文字列 */ //以前の関数宣言の記述・・・(4) const selfIntroduction = function greeting() { return `オス、おらの名めぇは${introduction[0]}ってんだ。得意技は${myHobby.sports}だ!`; }; console.log(selfIntroduction()); //オス、おらの名めぇはdendeってんだ。得意技はcapoeiraだ! ****************************************************** //アロー関数の記述・・・(5) const selfIntroduction1 = () => { //「=>」が矢印のように見えるのでアロー関数 return `歳は今年で${introduction[1]}せぇだ。 おすすめのアニメは${myHobby.anime}っちゅうぞ。おもしれぇからぜってぇ観てくれよな`; }; console.log(selfIntroduction1()); //歳は今年で35せぇだ。おすすめのアニメは新世界よりっちゅうぞ。おもしれぇからぜってぇ観てくれよな ****************************************************** //{}内の処理が1行(1塊)で終わる場合は{ }とreturnを省略できる・・・(6) const selfIntroduction2 = () => `${introduction[2]}って山奥に住んでっぞ。${myHobby.book}は6年めぇから購読してっぞ!`; console.log(selfIntroduction2()); //山梨って山奥に住んでっぞ。少年ジャンプは6年めぇから購読してっぞ! ****************************************************** //引数が一つの場合()も省略できる・・・(7) const comics = comic => `今のお勧めは${comic}だ!`; console.log(comics("ドクターストーン")); //今のお勧めはドクターストーンだ! ****************************************************** /** *テンプレート文字列について・・・(8) * *${}の中にはjavaScriptとして認識される。配列やオブジェクトにアクセスすることができる。 *上記例では配列の中身や、オブジェクトのプロパティを取り出して、文字列と結合している。 *従来の"A"+"B"で結合するより可読性が高い。 */ 四則演算 「++」「—」を使う際には、先に記述する場合と後に記述する場合で評価順が違うので、使用の際は注意。・・・(9) 前後の記述で出力内容が変わるのが普通に間違えそうで使いにくそう。 /** * 四則演算 */ let add = 1 + 1; //足し算 let sub = 4 - 1; //引き算 const mul = 7 * 1; //かけ算 const div = 11 / 1; //割り算 const sur = 13 % 14; //除算の余り const prime = [add, sub, mul, div, sur]; console.log(prime); //1を足す・・・(9) console.log(add++); //2 ++はまだ評価されていない console.log(add); //3 前述の++が反映されている console.log(++add); //4 ++が評価された結果が反映される //前に「++」をつけるか、後につけるかで出力結果が変わる。 //後につけた場合は評価されてからプラスされるので評価順に注意 //後述の減算についても同様 //1を引く console.log(sub--); //3 console.log(sub); //2 console.log(--sub); //1 比較演算 if文などの分岐の際などに、trueとfalseの判定などで使用する。 「A==B」・・・文字列と数値であっても同じと見なされる・・・(10) 「A===B」・・・文字列と数値を厳密に区別して評価される・・・(11) 「! =」「! ==」の場合も同様・・・(12)(13) 基本は厳密評価を使う。Javaでは見たことないので間違えないよう注意。 /** * 比較演算(==と===、!=と!==の違い) */ const str2 = "2"; //「==」だと数値を文字列であっても同じだと評価される・・・(10) if (add == str2) { console.log("trueです"); } //「===」だと数値と文字列は違うものと区別される・・・(11) if (add === str2) { console.log("trueです"); } else { console.log("trueではありません"); } //「!=」だと数値を文字列であっても同じだと評価される・・・(12) if (add != str2) { console.log("trueです"); } else { console.log("trueではありません"); } //「!==」だと数値と文字列は違うものと区別される・・・(13) if (add !== str2) { console.log("falseです"); } else { console.log("falseではありません"); } //「!」は論理演算子として評価の結果を反転する意味を持つ //大小比較は「>」「>=」「<」「<=」を使い評価する 論理演算 「&&」複数の理論式を使う際、「AかつB」の意味で使われる 「||」こちらは「AまたはB」の意味で使われる 実際の処理は && 左側がtrueの場合右側を返し、falseの場合左側を返す・・・(14) || 右側がfalseの場合左側を返し、trueの場合右側を返す・・・(15) 単純に「かつ」「または」の意味で使われると思っていたので、上記のような使い方ができるのは知らなかった。○○の時は表示。○○の時は非表示のような処理ができそう。 参考演算子 A ? B : C Aがtrueの場合Bを評価、falseの場合Cを評価する・・・(16) 関数を入れても動作をしたので、2択の分岐ならif文やswitch文より簡単かも。 /** * 論理演算(&& || ! ? :) */ const trueFlag = true; const falseFlag = false; if (trueFlag && falseFlag) { console.log("じっこうされない"); } else { console.log("実行される"); //こちらが出力される } //「&&」は左側がtrueの場合右側を評価する・・・(14) trueFlag && console.log("実行される"); if (trueFlag || falseFlag) { console.log("じっこうされない?"); //こちらが出力される } else { console.log("実行される?"); } //「||」は左側がfalseの場合右側を評価する・・・(15) falseFlag || console.log("実行された"); /** *三項演算子 */ //A?B:C //Aがtrue判定ならBを、false判定ならCを返す・・・(16) const case1 = trueFlag ? "truedeth" : "falsedeth"; console.log(case1); //truedeth const case2 = falseFlag ? "trueデス" : "falseデス"; console.log(case2); //falseデス truthyとfalsy JavaScriptにおいて真偽値以外でも、暗黙的に真偽値に変化して評価される。 trueとして評価されるものをtruthy、falseとして評価されるものをfalsyと呼ぶ。 どちらにも属さないものは存在せず、falsy以外はすべてtruthyとなる。 falsyとして評価される値 /** *truthyとfalsy */ //真偽値以外のtrue/false判定 //暗黙的に真偽値に変化して評価される const val = "ABC";・・・() if (val) { console.log("truthy"); //truthyと出力 } //暗黙的にfalseと扱われるものをfalsyといい、それ以外をtruthyという //どちらにも属さないというものは存在しない。 ``` switch文 Reactではif文より使うそう。caseの後の「:」が「;」になっていてエラーになった。間違えやすいので注意。breakも忘れないように。それ以外は分かりやすい。 /** * switch文 */ const calc = age + age; switch (calc) { case 69: console.log("間違いです"); //実行されない break; case 70: console.log("正解です"); //ここが実行される break; default: console.log("ここは実行されません"); break; } //caseごとに「:」で区切る //breakを書き忘れると全てのコードが実行されてしまう //fefaultは一致しない場合の処理をかく
- 投稿日:2021-10-25T11:24:02+09:00
[Javascript] Array.reduceを使って一次元配列をn個ずつに分割して新しい二次元配列を返す
はじめに 一次元配列を指定の要素数ごとに分割し、二次元配列にする必要があったため、そのロジックに関する小ネタです。即時関数、アロー関数式、通常関数(関数宣言)の3タイプのご紹介です。 ロジック sample.js const originalArr = [1,2,3,4,5,6,7,8,9,10,11,12,13]; const num = 3; // 分割単位 // 即時関数 const chunkedArr = ((arr, size) => arr.reduce((previous, _, i) => i % size ? previous : [...previous, arr.slice(i, i + size)], []))(originalArr, num); // -> [[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13]] // アロー関数式 const hoge = (arr, size) => arr.reduce((previous, _, i) => i % size ? previous : [...previous, arr.slice(i, i + size)], []); // console.log(hoge(originalArr, num)); -> [[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13]] // 通常関数(関数宣言) function fuga(arr, size){ return arr.reduce((previous, _, i) => i % size ? previous : [...previous, arr.slice(i, i + size)], []); } // console.log(fuga(originalArr, num)); -> [[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13]] 必要な場面に応じて使い分ければいいと思いますが、ポイントはArray.reduece()の使い方になります。特に第一引数であるpreviousを返す際、スプレッド構文を利用することでワンライナーにすることができます。 最後に 誰かの役に立てば幸いです。 [参考] Array.prototype.reduce() - JavaScript | MDN
- 投稿日:2021-10-25T10:40:42+09:00
【JavaScript】関数とオブジェクト⑩ prototype
はじめに Udemyの【JS】ガチで学びたい人のためのJavaScriptメカニズムの講座の振り返りです。 前回の記事 目的 関数とオブジェクトについての理解を深める 本題 1.プロトタイプとは コンストラクター関数と一緒に使用する オブジェクトに存在する特別なプロパティのこと インスタンス化した際にprototypeの参照がprotoにコピーされる 例1 function Person(name, age) { this.name = name; this.age = age; } // 以下にコンストラクター関数のメソッドを追加し無名関数を代入 Person.prototype.hello = function(){ // Personのthis.nameを入れる console.log("Hello" + this.name); } const bob = new Person('Bob', 18); const tom = new Person('Tom', 33); const sun = new Person('Sun', 20); // 実行するとHelloBobと出力 bob.hello(); メソッドとして使いたいプロパティをプロトタイプに格納するとインスタンスごとに関数を実行できる → ないとエラーになった! 例2 thisにhelloメソッドを登録した場合 function Person(name, age) { this.name = name; this.age = age; this.hello = function(){ console.log("Hello" + this.name); } } const bob = new Person('Bob', 18); const tom = new Person('Tom', 33); const sun = new Person('Sun', 20); // 実行するとHelloBobと出力 bob.hello(); tom.hello(); sun.hello(); Prototypeを使わなくても実行できる メモリの消費を抑えるためにprotoypeを使用する 今日はここまで! 参考にさせて頂いた記事 【JS】ガチで学びたい人のためのJavaScriptメカニズム
- 投稿日:2021-10-25T10:32:13+09:00
JavaScriptの命名規則早見表(と記法)
JavaScriptを書く時に、命名規則について「あれどうだったっけ」となった時に参考になれば幸いです。 ※ 業務であれば、チームなどによってルールが決まっていることもありますので、この限りではありません。 Javascriptの命名規則 記法 記法名 例 コンポーネント名 アッパーキャメルケース UserForm 変数名 ローワーキャメルケース sampleFunction 定数名 スネークケース API_URL メソッド名 ローワーキャメルケース addNumber プロパティ名 ローワーキャメルケース userName クラス名 アッパーキャメルケース MyCar おまけ: 記法について 記法 説明 例 アッパーキャメルケース 先頭と言葉の区切りは大文字、それ以外は小文字 UpperCamelCase ローワーキャメルケース 先頭は小文字、言葉の区切りは大文字、それ以外は小文字 lowerCamelCase スネークケース すべて大文字で、単語ごとにアンダースコアでつなぐ THIS_IS_SNAKE
- 投稿日:2021-10-25T08:55:23+09:00
FullCalender.js Timeline Viewで日付境界線を書き換える
はじめに 最近、なんちゃってグループウェアみたいなものを作ることがあり、 そんな中初めて、FullCalenderを使ってみようといろいろ試しています。 今回は、Timeline Viewで複数の日付を表示した際に、日付のが変わる位置の線を変更したという内容です。 もっといい方法があるよという人はぜひ教えてください。 本記事は、以下の記事がほぼそのままの内容ですが、こちらの記事ではCSS時間をべた書きしてしまっています。 ユーザーで設定を変更したり変数化した際、CSSを書き換えるのが難しいためJS側で設定できるようにしたものです。 How can I show separation between days in a FullCalendar ResourceTimeline? 注意 商用利用の場合Timeline Viewは有料機能です。 サンプルのソースコードと、完成イメージはライセンスが入っていないテスト状態です。 bootstrap 5.xでは動作しないようです。 環境 Chrome バージョン: 94.0.4606.81(Official Build) (64 ビット) jquery 3.6.0 bootstrap 4.6.0 FullCalender 5.9.0 実装 完成イメージ 10/20と10/21の間に黒の線が入っています。 コード calender.js var calendarEl = document.getElementById('calendar'); // カレンダーを表示するDIV let calender_setting = { initialView: 'resourceTimelineWeek', // 表示モード slotMinTime: '07:00', slotMaxTime: '20:00', datesSet(info){ // calendarの日付情報が変更される度に呼び出される // 日付境界に当たるセルにクラスを追加する day_firest_time = calendar.getOption('slotMinTime')+ ":00"; // 07:00:00 // data-dateの値が07:00:00で終わるNodeを集める allEl = document.querySelectorAll('th[data-date$="'+day_firest_time+'"], td[data-date$="'+day_firest_time+'"]'); for (var i = 0; i < allEl.length; i++) { // 全部のElementにクラスを追加 allEl[i].classList.add('day_firest_time_cell'); } }, resources: [ // リソース ] } // カレンダーの初期化 document.addEventListener('DOMContentLoaded', init); function init() { calendar = new FullCalendar.Calendar(calendarEl,calender_setting); calendar.render(); } clender_custom.css .day_firest_time_cell { border-left: 3px double !important; border-left-color: #333 !important; } clender.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2-bootstrap-theme@0.1.0-beta.10/dist/select2-bootstrap.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar@5.9.0/main.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fullcalendar-scheduler@5.9.0/main.min.css"> <link rel="stylesheet" href="clender_custom.css"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-Piv4xVNRyMGpqkS2by6br4gNJ7DXjqk09RmUpJ8jgGtD7zP9yug3goQfGII0yAns" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.full.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.9.0/main.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/fullcalendar@5.9.0/locales/ja.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/fullcalendar-scheduler@5.9.0/main.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/fullcalendar-scheduler@5.9.0/locales-all.min.js"></script> <script src="./js/calendar.js"></script> <title></title> </head> <body> <div id='calendar'></div> </body> </html> おわり 今回は3px 2重線 #333にしていますが、お好みで。 calender.jsのslotMinTimeを変更することで開始時間を調整できます。 実装のコードは実用しているものから公開できるように簡易化したものです。 主題の内容以外は取り除かれているので、実装コードだけでは完成イメージにはなりません。 ローカライゼーションなどは公式サイトなど見てください。 再利用などはご自由に。本家のライセンスは守ってくださいね。 大変使いやすくてありがたいのですが、ちょっとカスタマイズしようとすると、検索がなかなか大変です。 特に商用では有料の機能については、日本語の情報はなかなか見つからないので試行錯誤の毎日です。 意見やもっとこうすればいいよがればコメントいただければ。 参考文献 How can I show separation between days in a FullCalendar ResourceTimeline? / stack overflow datesSet / FullCalender Docs
- 投稿日:2021-10-25T02:07:11+09:00
Bootstrap4.0.0-alpha.6 のModal is transitioningを解消したい
問題 Bootstrap4.0.0-alpha.6において、公式リファレンスのモーダルを実装し、モーダルの開閉を繰り返すと、Uncaught Error: Modal is transitioningというエラーが発生する。 該当のコード 公式リファレンスのモーダル <!-- Button trigger modal --> <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal"> Launch demo modal </button> <!-- Modal --> <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="exampleModalLabel">Modal title</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> ... </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-primary">Save changes</button> </div> </div> </div> </div> 解消法1 バージョンを上げる。 どうやらbeta1では解決されているらしい。 https://github.com/twbs/bootstrap/issues/21607#issuecomment-330156180 解消法2 問題の根幹はfadeクラスにあるようなので、これを削除するとエラーは発生しなくなる。 ただしfadeinっぽいアニメーションは消える。 <div class="modal fade ←これ" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> 引用
- 投稿日:2021-10-25T01:15:32+09:00
p5.js の 3D描画におけるカメラの位置変更を軽く試してみる(3つの異なるやり方にて)【その1】
p5.js の createCanvas() や createGraphics() で、以下の WEBGL を選択した場合の描画に関し、カメラの位置変更を行った場合の話に関する記事です。 ●reference | WEBGL https://p5js.org/reference/#/p5/WEBGL 試した内容は以下のツイートにある通りで、詳細はこの後に説明をしていきます。 #p5js の 3D空間内のカメラ操作について、自分が理解していく第一歩として、簡単なサンプルを 3つほど作っての挙動の違いを比較してみました。ちなみに、以下の 3パターン(細かな部分の調査・理解はこれから)。■「camera()」のパラメータを変更■「pan()・tilt()」を利用■「move()」を利用 pic.twitter.com/unLD75IIGN— you (@youtoy) October 24, 2021 今回試したものに関する公式情報 p5.js の referenceでの「Lights, Camera」という項目の下に、以下のようなカメラ関連の内容が並んでいます。 この中の「camera() の項目」と「p5.Camera の項目」のページには、カメラ位置の変更に関わる処理がいくつか書かれています。 今回、それらの中の全てではないですが、気になるものいくつかを試してみました。 試した処理の概要 今回試した具体的な処理は、冒頭に掲載したツイート本文にも書いた以下になります。 「camera()」のパラメータ変更 「pan()・tilt()」の利用 「move()」の利用 上記の 1つ目は、「camera() の項目」で、以下のように書かれているものになります。 上記 2つ目と 3つ目は、「p5.Camera の項目」の Methods に含まれているものです。 お試し時に描画した内容 今回のカメラの位置変更を試す際、3D空間で描画してみたものは、以下の見た目のものになります。 描画処理部分のソースコードを抜粋して書いておきます。 push(); translate(-width / 4, -height / 4, 0); fill(0, 0, 255); box(80); pop(); push(); box(80); pop(); push(); translate(width / 4, height / 4, -100); fill(200, 100, 150); box(80); pop(); 上記の2つ目の box() は色を指定していないですが、setup() の部分で ambientLight(255, 255, 255) を指定しているため、それに影響を受けた白い色になっています。 カメラの位置変更の処理について(概要) カメラに関して試した処理: camera() のパラメータ変更 具体的なカメラの位置変更の処理内容は、以下となります。 以下の cameraX と cameraZ は、初期値がゼロで、キーの押下時に値が増減するようなものにしています。 camera(cameraX + 0, 0, cameraZ + height / 2 / tan(PI / 6), 0, 0, 0, 0, 1, 0); 上記でゼロでない部分があるのは、公式リファレンス内でデフォルト値が以下となると説明されていたためです。 camera(0, 0, height / 2 / tan(PI / 6), 0, 0, 0, 0, 1, 0); パラメータ変更時は、このデフォルト値をベースに値の変更を行っています。 カメラに関して試した処理: pan()・tilt() pan()・tilt() は、公式リファレンス内に以下の説明ページがあります。 ●reference | pan() https://p5js.org/reference/#/p5.Camera/pan ●reference | tilt() https://p5js.org/reference/#/p5.Camera/tilt これらを使った処理は、キーが押された時に実行するようにしています。 左右の矢印キーが押された時は「pan(0.01)」・「pan(-0.01)」といった処理を行い、上下の矢印キーが押された時は@tilt(-0.01)」・「cam.tilt(0.01)」といった処理を行っています。 カメラに関して試した処理: move() move() は、公式リファレンス内に以下の説明ページがあります。 ●reference | move() https://p5js.org/reference/#/p5.Camera/move これを使った処理は move(cameraX, 0, cameraZ) という内容です。 cameraX と cameraZ は、何もキーが押されていなければゼロの値となり、特定のキーが押下された際に値が 10 や -10 になるようにしています。 今回使ったソースコード 以下に、今回の 3通りのお試しに用いたソースコードを載せておきます。 camera() のパラメータ変更を行ったもの let cameraX = 0, cameraZ = 0; const stepX = 10, stepZ = 10; function setup() { const canvas = createCanvas(500, 400, WEBGL); canvas.position(0,65); createElement('h2', '「camera()」のパラメータを変更'); ambientLight(255, 255, 255); } function draw() { background(180); if (keyIsDown(LEFT_ARROW)) { cameraX += stepX; } else if (keyIsDown(RIGHT_ARROW)) { cameraX -= stepX; } if (keyIsDown(UP_ARROW)) { cameraZ -= stepZ; } else if (keyIsDown(DOWN_ARROW)) { cameraZ += stepZ; } camera(cameraX + 0, 0, cameraZ + height / 2 / tan(PI / 6), 0, 0, 0, 0, 1, 0); drawObjects(); } function drawObjects() { push(); translate(-width / 4, -height / 4, 0); fill(0, 0, 255); box(80); pop(); push(); box(80); // 色指定なしだと「ambientLight」の色に pop(); push(); translate(width / 4, height / 4, -100); fill(200, 100, 150); box(80); pop(); } function keyPressed() { if (key == " ") { cameraX = 0; cameraZ = 0; } } 基本的には上で説明した処理を実装した形ですが、一番下の部分の keyPressed() で初期位置に戻す処理も加えています。 pan()・tilt() を使ったもの let cam; function setup() { const canvas = createCanvas(500, 400, WEBGL); canvas.position(0,65); createElement('h2', '「pan()・tilt()」を利用'); cam = createCamera(); ambientLight(255, 255, 255); } function draw() { background(205); if (keyIsDown(LEFT_ARROW)) { cam.pan(0.01); } else if (keyIsDown(RIGHT_ARROW)) { cam.pan(-0.01); } if (keyIsDown(UP_ARROW)) { cam.tilt(-0.01); } else if (keyIsDown(DOWN_ARROW)) { cam.tilt(0.01); } drawObjects(); } function drawObjects() { push(); translate(-width / 4, -height / 4, 0); fill(0, 0, 255); box(80); pop(); push(); box(80); pop(); push(); translate(width / 4, height / 4, -100); fill(200, 100, 150); box(80); pop(); } function keyPressed() { if (key == " ") { camera(0, 0, height / 2 / tan(PI / 6), 0, 0, 0, 0, 1, 0); } } move() を使ったもの let cam, cameraX = 0, cameraZ = 0, moveParamX = 10, moveParamZ = 10; function setup() { const canvas = createCanvas(500, 400, WEBGL); canvas.position(0,65); createElement('h2', '「move()」を利用'); cam = createCamera(); ambientLight(255, 255, 255); } function draw() { background(220); if (keyIsDown(LEFT_ARROW)) { cameraX = -moveParamX; } else if (keyIsDown(RIGHT_ARROW)) { cameraX = moveParamX; } else { cameraX = 0; } if (keyIsDown(UP_ARROW)) { cameraZ = -moveParamZ; } else if (keyIsDown(DOWN_ARROW)) { cameraZ = moveParamZ; } else { cameraZ = 0; } cam.move(cameraX, 0, cameraZ); drawObjects(); } function drawObjects() { push(); translate(-width / 4, -height / 4, 0); fill(0, 0, 255); box(80); pop(); push(); box(80); pop(); push(); translate(width / 4, height / 4, -100); fill(200, 100, 150); box(80); pop(); } function keyPressed() { if (key == " ") { camera(0, 0, height / 2 / tan(PI / 6), 0, 0, 0, 0, 1, 0); } } 実行結果 冒頭で掲載したツイートにもありましたが、実行結果は以下のとおりです。 おわりに 今回の記事の中で「3通りの処理結果の比較や、違いが生じる理由(各処理の仕様の違い)」も扱おうと思っていたのですが、ここまででけっこうな分量になった感じもあるので、続きは別の記事に分けて書こうと思います。
- 投稿日:2021-10-25T00:03:22+09:00
ml5.js の Handpose でカメラ画像のサイズを変えたら描画がズレたので検証・対処してみる(PoseNet、Facemesh との比較も)
タイトルの通りの内容です。 (仕様を勘違いしている、とかあるかな...) ml5.js の Handpose の公式サンプルの中で、「p5.js Web Editor + Webカメラ」のサンプルの組み合わせを試した際、カメラ画像のサイズ変更を行ったら描画がズレるということが起こったので検証などをしてみました。 以下の画像は、サンプルをそのまま動かした時のものです。 カメラ画像の横x縦の大きさは 640 x 480 です。 以下の画像は、ビデオの横x縦の大きさを 480 x 360 にして、キャンバス上への描画時のサイズもそれを使った時のものです。 キーポイントの描画にズレが生じています。 Handpose のサンプル 元のコード ml5.js の Handpose の公式サンプルの、「p5.js Web Editor + Webカメラ」のサンプルは、コメントを除くと以下となります。 let handpose; let video; let predictions = []; function setup() { createCanvas(640, 480); video = createCapture(VIDEO); video.size(width, height); handpose = ml5.handpose(video, modelReady); handpose.on("predict", results => { predictions = results; }); video.hide(); } function modelReady() { console.log("Model ready!"); } function draw() { image(video, 0, 0, width, height); drawKeypoints(); } function drawKeypoints() { for (let i = 0; i < predictions.length; i += 1) { const prediction = predictions[i]; for (let j = 0; j < prediction.landmarks.length; j += 1) { const keypoint = prediction.landmarks[j]; fill(0, 255, 0); noStroke(); ellipse(keypoint[0], keypoint[1], 10, 10); } } } カメラ画像のサイズは video.size(width, height) となっているので、キャンバスのサイズに合うように設定されています。 また、キャンバスに描画する処理が image(video, 0, 0, width, height) となっているので、キャンバスいっぱいにカメラ画像が表示されるようになっています。 カメラ画像のサイズを変更したもの このカメラ画像のサイズ・カメラ画像をキャンバスへ描画するサイズを以下のようにしたところ、冒頭の画像のようなズレが生じました。 カメラ画像のサイズ設定: video.size(480, 360) キャンバスへの描画処理: image(video, 0, 0, 480, 360) これを解決した対処法は、 ellipse(keypoint[0], keypoint[1], 10, 10) というキーポイントを描画している部分の、最初の 2つの変数(描画する x座標と y座標を指定する部分)の値がとる範囲を変更するというものでした。 ellipse(map(keypoint[0], 0, 640, 0, 480), map(keypoint[1], 0, 480, 0, 360), 10, 10) キーポイントの座標の値を map() を用いて、以下のようにしました。 x座標: 0-640 の範囲を 0-480 の範囲にマッピング x座標: 0-480 の範囲を 0-360 の範囲にマッピング その表示結果は以下のとおりです。 ソースコードの中まではチェックできてないですが、カメラ画像が 640 x 480 のサイズとなるのが前提という感じでしょうか。 PoseNet・Facemesh の場合も見てみる 他に、カメラ画像から人の姿勢・顔のキーポイントを検出するもの(「PoseNet」と「Facemesh」)もあるので、それらでもカメラ画像のサイズ変更の影響を見てみます。 「p5.js Web Editor + Webカメラ」のサンプルは、それぞれ以下になります。 PoseNet_webcam Facemesh_Webcam 試してみた結果、PoseNet のほうはキーポイントの座標に何も処理を加えなくても、表示位置のズレは出ませんでした。 Facemesh のほうはキーポイントの座標に Handpose で行ったような処理を加えないと、表示位置がズレました(以下は、調整用の処理を加えた後の表示結果)。 このあたり、仕様詳細を見て、違いが出る理由を探してみたいところです。
