20210109のJavaScriptに関する記事は25件です。

TypeScriptを学んだ(2) ~モジュール編~

前編は、TypeScriptを学んだ(1) ~型、クラス編~

内部モジュール

コードを部品化してわかりやすく整理したりだとか、変数名とかクラス名の衝突を避けたりするために使用する。

module UserModule {                 // モジュール定義
  export var name = "John";         // モジュールの外部からアクセスするにはexportを使う
  export module AddressModule {
    export var zip = "111-111";
  }
}

// dotアクセス
console.log(UserModule.AddressModule.zip);

// もしくはimportを使い、、
import addr = UserModule.AddressModule
console.log(addr.zip);

また、複数のファイルに別れており、別ファイルを読み込む必要がある場合には、ファイルの最初に

main.ts
/// <reference path = "./user.ts" />
...

これらをコンパイルして一つのファイル(all.js)に書き出したい場合には、コンソールで$ tsx main.ts --out all.jsを実行すれば良い。

外部モジュール

user.ts
module UserModule {
  export var name = "John";
}
main.ts
import User = require("./user") // 拡張子はいらない
console.log(User.name);         // モジュールにはimport時の変数からアクセス

// Node JS でよく使われる CommonJS形式でのコンパイル方式
$ tsx main.js -m commonjs


// RequireJSなどで使われる AMD と呼ばれるコンパイル方式
$ tsx main.js -m amd

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】現在時刻をリアルタイムで表示し続ける方法

プログラミング勉強日記

2021年1月9日
以前、こちらの記事でJavaScriptでの日付と時刻を取得する方法をまとめた。今回は、現在時刻をリアルタイムで表示し続ける方法を紹介する。

方法

  1. 現在時刻を格納するDateオブジェクトを作成する
  2. 作成したDateオブジェクトから時間・分数・秒数を取り出す
  3. 時計として表示する文字列を作成する
  4. 文字列を時間に書きかえる
  5. 1秒ごとに特定の処理を実行する

1. 現在時刻を格納するDateオブジェクトを作成する方法

 以下のようにnew Date()で現在の日付・時刻する。

var nowDate = new Date();
console.log(nowDate);
コンソール結果
Sun Sep 27 2020 09:22:20 GMT+0900 (日本標準時)

2. 作成したDateオブジェクトから時間・分数・秒数を取り出す方法

let nowTime = new Date(); //  現在日時を得る
let nowHour = nowTime.getHours(); // 時間を抜き出す
let nowMin  = nowTime.getMinutes(); // 分数を抜き出す
let nowSec  = nowTime.getSeconds(); // 秒数を抜き出す

3. 時計として表示する文字列を作成する方法

let msg = "現在時刻:" + nowHour + ":" + nowMin + ":" + nowSec;

4. 文字列を時間に書きかえる方法

document.getElementById("id名").innerHTML = msg;

5. 1秒ごとに特定の処理を実行する方法

 etIntervalメソッドを使う。(詳しくは後日Qiitaに乗せようと思う)

// 第1引数は指定時間後に自動実行される関数
// 第2引数はミリ秒で指定時間を設定する(1000=1秒)
setInterval('関数名',1000);

サンプルコード

 以上を踏まえてできたコードがこちら。

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>sample</title>
  <style>

  </style>
  <script>
    function showClock() {
      let nowTime = new Date();
      let nowHour = nowTime.getHours();
      let nowMin  = nowTime.getMinutes();
      let nowSec  = nowTime.getSeconds();
      let msg = "現在時刻:" + nowHour + ":" + nowMin + ":" + nowSec;
      document.getElementById("realtime").innerHTML = msg;
    }
    setInterval('showClock()',1000);
  </script>
</head>

<body>

  <p id="realtime"></p>

</body>

</html>

実行結果
image.png

 23:7:35よりは23:07:35の方が見やすく、デジタル時計としては常に2桁で表示させたい。そのコードを紹介する。

常に2桁で表示するコード
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>sample</title>
  <style>

  </style>
  <script>
    function twoDigit(num) {
      let ret;
      if( num < 10 ) 
        ret = "0" + num; 
      else 
        ret = num; 
      return ret;
    }
    function showClock() {
      let nowTime = new Date();
      let nowHour = twoDigit( nowTime.getHours() );
      let nowMin  = twoDigit( nowTime.getMinutes() );
      let nowSec  = twoDigit( nowTime.getSeconds() );
      let msg = "現在時刻:" + nowHour + ":" + nowMin + ":" + nowSec;
      document.getElementById("realtime").innerHTML = msg;
    }
    setInterval('showClock()',1000);
  </script>
</head>

<body>

  <p id="realtime"></p>

</body>

</html>

実行結果
image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScriptを学んだ(1) ~型、クラス編~

TypeScriptで用意されている型

  • number
  • string
  • boolean
  • any
var i: number;
var i: number = 10;
var i = 10 // 暗黙の型推論

var x; // any型になる

var nums: number[]; // number型の値の配列
  • 列挙型
enum Signal {
  Red,      // 0
  Blue = 3,
  Yellow,   // 4
}

Signal.Blue    // 3
Signal['Blue'] // 3
Signal[3]      // 'Blue'

// なお、複数の同名の列挙型を宣言した場合にはそれらがマージされる。
// ただし、最初のメンバを省略できるのはどちらかだけである。

関数

function add(a: number, b?: number): number {  // bはオプション
  if (b){
    return a + b;
  } else {
    return a;
  }
}

// bにデフォルトで10を与えたいなら、
function add(a: number, b: number = 10): number {...}

// 式関数で書くなら、
var add = (a: number, b: number): number => a + b;

関数のオーバーロード

TypeScriptでは、関数のオーバーロードは、インターフェイス(signature)と実装部分を分けて書く。

function add(a: number, b: number): number;
function add(a: string, b: string): number;

function add(a: any, b: any): any {   // この(any, any)は上書きされるので、(1. 'string')とかだとエラーが出る。
  if (typeof a === "string" && typeof b === "string"){
    return a + ' ' + b
  }
  return a + b
}

クラス

class User {
  public name: string;         // (アクセス修飾子を書かなくても)デフォルトでpublicになる
  constructor(name: string) {  // functionはいらない
    this.name = name;
  }
}

// これを、constuctorの引数の箇所でインスタンス変数の初期化ができる
class User {
  constructor(public name: string) {
    this.name = name;
  }
}

フィールドやメソッドをまとめて、そのクラスのメンバーと呼ぶ。

アクセス修飾子

アクセス修飾子は、public, protected, privateの3種類。

以下のように、constructorの引数の変数名の前にアクセス修飾子をつけることによっても、フィールドを設定することができる。(他のメソッドの引数につけることはできない)

class User {
  // public name: string;         // 不要になった
  constructor(public name: string) {  // アクセス修飾子のpublicをつけた
    this.name = name;
  }
}

getterとsetter

getter/setterを定義すると、(privateのfieldであっても)、ClassName.fieldでアクセス/ClassName.field=xで代入することができるようになる。

getter/setterを使う場合のコンパイルは、ECMAScript 5を使い、tsx main.tsx -t ES5とする。

class User {
  constructor(public name: string) {  // アクセス修飾子のpublicをつけた
    this.name = name;
  }

  get name(){
    return this._name;
  }

  set name(newValue: string) {
    this._name = newValue
  }
}

継承とprotected

class AdminUser extends User {
  private _age: number;
  constructor (_name:string, _age:number) {
    super(_name);
    this._age = _age;
  }

  public sayHi(): void {
    console.log("my age: ", + this.age);
    super.sayHi();
  }
}

先述のUserクラスを継承するAdminUserクラスを書いた。これだと、子クラスにから親クラスのprivateフィールドである_nameにアクセスすることができない。

これを可能にするためには、nameフィールドをprotected(自分のクラス及び、それを継承するクラス内でのみ使えるアクセス修飾子)にするとよい。変数名も、nameではなく、nameにしよう。

static member (= static field + static method)

class User {
 static count = 0;     // static fieldの定義
  name: string;
  constructor(name: string) {
    this.name = name;
    User.count++       // static fieldへのアクセス
  }
  static showDescription(): void {  // static methodの定義
    console.log("This class is constructed" + User.count + "times");
  }
}


User.showDescription(); // static methodの実行

インターフェイス

型の組み合わせを変数として保持するもの。(Javaだと、メソッドの引数の型と返り値の型の組み合わせを定義するものだったな)

interface Result {
  a: number;
  b: number;
}

インターフェイスは、継承することもできるし、変数自体をオプション指定にすることもできる。

interface SpringResult {
  spring: number;
}

interface FallResult {
  fall: number;
}

interface FinalResult implements SpringResult, FallResult {
  final?: number;  // spring, fallに加え、c(optional)を持つ
}

function getTotal(result: FinalResult) {
  if (result.final) {
    return result.spring + result.fall + result.final;
  } else {
    return result.spring + result.fall;
  }
}

[1]インターフェイスを関数の引数として使用する場合

先述の通りである。

[2]インターフェイスをクラスで実装する場合

そのクラスは、interfaceで定義されたフィールド/メソッドを持っている必要がある。

interface GameUser {
  score: number;
  showScoreWithMemo(memo:string): void;  // methodのsignature
}

class User implements GameUser {
  score:number = 0;   // ちゃんと実装

  constructor(name:string) {
    this.name = name;
  }

  showScoreWithMemo(memo: string): void {  // ちゃんと実装した
    console.log("score: " + this.score);
  }
}

Generics型

[1]関数で使用する場合

function getArray<T>(value: T) : T[] {
  return [value, value, value]
}

console.log(getArray<string>("a")); // Tにstringが代入される
// ["a", "a", "a"]

// <string>を省略した場合には型推論によって、Tにstringが代入されるっぽい。
console.log(getArray("a")); 
// ["a", "a", "a"]

[2]クラスで使用する場合

class User<T>{...}

// 初期化する際には<T>も書く
u = new User<T>();

// <T>を省略した場合には型推論が使われる
u = new User();

Generics型に制約を与える

Generic型に対して、それが特定のフィールドを持つオブジェクトであるという制約を与えたい場合には、T extends (interface)としてやれば良い。なお、Tがinterfaceで定義されるプロパティを指定された形でもつことが最低条件になる。前述したように、これは、最低条件であり、これに加えてcというattributeを持っていたりしても構わない。

interface Result {
  a: number;
  b: number;
}

class MyData<T extends Result> {   // Tはa(number)とb(number)というattributeを持つことがmust
  constructor(public value: T) {}

  getArray(): T[] {
    return [this.value.a, this.value.b]
  }
}

var number_values = new MyData<Result>({a: 1, b: 2});
console.log(number_values.getArray());

// <Result>を省略した場合、T = Resultになるっぽい
var number_values = new MyData({a: 1, b: 2});
console.log(number_values.getArray());

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript (ES6) 基礎編 繰り返し処理とは?

こちらではJavaScript学習の備忘録となります。
プログラミング初心者や他の言語にも興味、関心をお持ちの方の参考になれば幸いです。


繰り返し処理とは?

よくある例だと

『1から100までの数字を出力する』

場合などに用いる繰り返し処理のことです。
繰り返し処理を行うには「While文」と「for文」がある。

While文

while文は下記のように、「条件式がtrueの間、{ }内の処理を繰り返す」ことができます。 {}の後にセミコロンは不要です。

script.js
while (条件式) {
  処理
}

上記『1から100まで数字を出力する』例で、while文を用いると下記のようになる。
まず条件式の判定が行われ、trueの時のみ{}の中の処理が1度実行される。
その後、再び条件式がtrueかどうかチェックされ、trueであれば処理が実行される。
条件式がfalseになるまで繰り返し処理が続く。

script.js
let number = 1;

while (number <= 100) {  // 変数numberの値が100以下の時に処理を繰り返す
  console.log(number);
  number += 1;
}

※ while文の注意点
while文を用いる場合、条件式の部分がいつかはfalseになる。
しかし、上記「number += 1;」のような変数numberを更新するコードを書き忘れてしまうと、条件が永遠にtrue(上記だと永遠に100以下)となるため、繰り返し処理も永遠に続いてしまう。
これを無限ループと呼ぶ。

for文

できることはWhile文と同じだが、while文よりシンプルに書くことができるのが特徴。
for文では「変数の定義」「条件式」「変数の更新」の3つを括弧の中に書く。括弧の中ではそれぞれをセミコロン(;)で区切ること。

script.js
for (変数の定義; 条件式; 変数の更新) {
  処理
}
script.js
// 上記の例をfor文で書いてみる

for (let number = 1; number <= 100; number += 1) {
  console.log(number);
}

計算式の省略

「number += 1」は「number ++」のように省略して書くことができる。
また、引き算の場合にも、「number -= 1」を「number --」と省略することができる。
while文やfor文では、この省略した書き方を使ってコードを短くすることができる。

script.js
// 足し算
number = number + 1

number += 1

number++

// 引き算
number = number - 1

number -= 1

number--

繰り返し処理の応用

またまた、よくある例ですが、
『1から100の数字を出力してください。ただし、3の倍数の時は「3の倍数です」と出力』する場合をfor文を用いて書く。

script.js
for (let number = 1; number <= 100; number++) {
  if (number % 3 === 0) {
    console.log("3の倍数です");
  } else {
    console.log(number); 
  }
}

おわりに

このあたりは、基本中の基本ですね。
何かありましたらご指摘願います。
宜しくお願いいたします!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者でもわかる】hover時のCSSをhtmlのタグに直接書く方法

どうも7noteです。hover時にだけCSSを当てる方法。

CSSは.cssファイルに書くか、<style></style>の中に書くのが一般的ですが、HTMLのタグの中に書くことも可能です。

index.html
<!-- HTMLのタグに直接CSSを書く方法 -->
<div style="background:#CCF;">てすとてすと</div>

しかし、この方法には欠点があり:hoverなどの疑似セレクタがついた時のCSSを指定することができません。
(※ほかにも管理がしにくいなどの欠点もあります。)

ですが、onMouseOutとonMouseOverの属性を使うことで:hoverと同じようにCSSを反映することができます!

hover時のCSSをhtmlのタグに直接書く方法

index.html
<div style="background:#CCF;" onMouseOut="this.style.background='#CCF';" onMouseOver="this.style.background='#EEF'">てすとてすと</div>

結果
sample.gif

このようにボタンの上にカーソルが乗った時に色が変わります。

文字色も変えたい!複数処理をするときはセミコロン(;)で繋ぐ。

index.html
<div style="background:#CCF;" onMouseOut="this.style.background='#CCF';this.style.color='#000';" onMouseOver="this.style.background='#EEF';this.style.color='#F00';">てすとてすと</div>

結果
sample02.gif

またjavascriptで関数を動かすこともできるので以下のような書き方でも同じ処理をすることが可能です。

index.html
<div id="hover-btn" style="background:#CCF;" onMouseOut="omOut();" onMouseOver="omOver();">てすとてすと</div>

<script>
function omOut(){
  document.getElementById('hover-btn').style.background='#CCF';
  document.getElementById('hover-btn').style.color='#000';
}
function omOver(){
  document.getElementById('hover-btn').style.background='#EEF';
  document.getElementById('hover-btn').style.color='#F00';
}
</script>

同じ結果になりました。
sample02.gif

まとめ

htmlだけやjsを使って:hoverの代わりの処理を入れることはできますが、可能なのであればやはりCSSファイルでちゃんと:hoverとかける方が作りやすいし管理もしやすいですね。
でもどうしてもhtmlのタグしかかけないときに使える技です!

困ったときに使えるTipsでした。

参考:https://teratail.com/questions/151338

おそまつ!

~ Qiitaで毎日投稿中!! ~
【初心者向け】WEB制作のちょいテク詰め合わせ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【メモ】JavaScriptのreplaceメソッドと正規表現について

忘れてしまいそうなので、メモっときます。

replaceメソッドの使い方

replace('検索文字','置換した後の文字')という感じ。

そして、$1とか$2とかは、検索文字の()の中身のこと。

var str = '今日は2019年6月16日、明日は2019年6月17日です。';
var result1 = str.replace( /(\d+)年(\d+)月(\d+)日/g , "$1/$2/$3" );

// 結果
今日は2019/6/16、明日は2019/6/17です。

みたいな感じになる。

正規表現

今回謎だったのが、下記の正規表現。

replace(/[\||](.+?)《(.+?)》/g, '<ruby>$1<rt>$2</rt></ruby>')

無知すぎて、この程度もわかりませんでした・・・

でも今らなわかる!!!

/パターン/フラグという正規表現の記述方法。
[\||]|に一致するものという意味。
\(バックスラッシュ)はエスケープするためのもの。
[]は角括弧に含まれるいずれか1文字にマッチする。
.+?は一文字以上の文字と一致する。
/gで一致するのがあっても、最後まで検索&置換を続ける

という感じ。

これで、|で始まって、文字《文字》という正規表現ができる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[サクラエディタ][マクロ][js]プログラム対象行数取得マクロ

早速ですが

VB.NET開発で、コーディング行数をコメントに記述する必要があり、毎回行数を数えるのが面倒のため、容易に取得できるマクロを作成

ざっくりな手順

①VisualStudioからプログラムをコピー
②サクラエディタに貼り付け
③マクロ実行

PGLineCnt.js
/*
 * タイトル:プログラム対象行数取得マクロ(サクラエディタマクロ用)
 * 説明    :プログラムのコメント行、空白行を除き、
 *           プログラム対象行数をクリップボードにコピーする。
 * 作成者  :●●●●
 * 設定手順:①[メニュー]-[設定]-[共通設定]から[マクロ]タブを選択
 *           ②空き番号行を選択、名前に任意の名前を入力し、Fileに当マクロを選択後、
 *             設定ボタンを押下
 *           ③[キー割当]タブを選択
 *           ④種別のリストから[外部マクロ]を選択、機能欄から②で登録したマクロを選択し、
 *             任意のキーを割付
 *           ⑤サクラエディタの再起動
 *
 * 変更履歴:2021.01.08 新規登録
 */

// 置換
//Editor.ReplaceAll('^[\r\n]+|^[\s|\t]*' + '\'' + '.*[\r\n]', '', 4);   // 置換
Editor.ReplaceAll('^[ |\t]*' + '\'' + '.*[\r\n]', '', 4);   // 置換1:コメント行を置換
Editor.ReplaceAll('^[\r\n]+', '', 4);                       // 置換2:空白行を置換

Editor.ReDraw(0);       // 再描画
Editor.GoFileTop(0);    // 先頭行移動

var lcnt = Editor.GetLineCount(0);  //対象行数取得
Editor.SetClipboard(0, lcnt);       //対象行数取得をクリップボードにコピー
Editor.InfoMsg(lcnt)                //対象行数取得をメッセージ表示

最後に

サクラエディタの正規表現は、秀丸の結果と微妙に違うのは私の勘違いと思うが、細かい事は気にしない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ジョーカーゲーム作成

定義
・ジョーカーを引いたら負け
・ランダム関数でカードを出力
・ユーザー1と2が交互にトランプを引いていく
・3枚まではジョーカーが出ない
・出たカードを真ん中に縦で並べていく
・リセットボタンも作成

使うものは連想配列(オブジェクト)、dom

補足
今回はあまりリッチなものにしない
順番に押せばゲームが出来る
ランダン関数で同じ番号が出たら関数内で再度引き直す
どっちがカードを出したかわかるようにして別途で順番を表記しない
カードをひっくり返した毎にそのカードの数字を配列に追加して、その配列と照らし合わせて重複を確認
重複したら自動的に引き直す

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="./style.css">
    <title>jokergame</title>
</head>
<body>
    <div>
        <button>ユーザー1</button>
        <button>リセットボタン</button>
        <button>ユーザー2</button>
    </div>
    <p id="game-log">ハート6</p>
    <p>ハート6</p>
    <script src="./index.js"></script>
</body>
</html>
div {
  display: flex;
  justify-content: space-around;
}
p {
  display: flex;
  justify-content: center;
}

組んでみたロジック

const trump = {
    1: "♠1",
    2: "♠2",
    3: "♠3",
    4: "♠4",
    5: "♠5",
    6: "♠6",
    7: "♠7",
    8: "♠8",
    9: "♠9",
    10: "♠10",
    11: "♠11",
    12: "♠12",
    13: "♠13",
    14: "♥1",
    15: "♥2",
    16: "♥3",
    17: "♥4",
    18: "♥5",
    19: "♥6",
    20: "♥7",
    21: "♥8",
    22: "♥9",
    23: "♥10",
    24: "♥11",
    25: "♥12",
    26: "♥13",
    27: "♦1",
    28: "♦2",
    29: "♦3",
    30: "♦4",
    31: "♦5",
    32: "♦6",
    33: "♦7",
    34: "♦8",
    35: "♦9",
    36: "♦10",
    37: "♦11",
    38: "♦12",
    39: "♦13",
    40: "♣1",
    41: "♣2",
    42: "♣3",
    43: "♣4",
    44: "♣5",
    45: "♣6",
    46: "♣7",
    47: "♣8",
    48: "♣9",
    49: "♣10",
    50: "♣11",
    51: "♣12",
    52: "♣13",
    53: "ジョーカーやで^_^",
}
var randoms = []
var turn = 0
var GameLog = document.getElementById('game-log')


function opencard(user) {
    var card = Math.floor(Math.random() * 53 + 1)
    var num=document.createElement('p')
    num.innerHTML = user + ':' + trump[card]
    if (user === 'user1') {
        num.style.color='red'
    } else {
        num.style.color='blue'
    }

    GameLog.appendChild(num)
    if (card == 53) {
        if (turn < 3) {
            alert('まだ試合を終えるには早すぎる')
            num.remove()
        } else {
            alert(user+' lose!!!!!!!!!!!!!!!!!!!!!!')
        }
    }
    for (var i = 0; i < randoms.length; i++){
        if (randoms[i] == card) {
            num.remove()
            turn = turn-1
        } 
    }
    randoms.push(card)
    turn = turn + 1
}

function reset() {
    turn = 0
    randoms = []
    while (GameLog.firstChild) GameLog.removeChild(GameLog.firstChild);

}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

爆音と共振でワイングラスを割るサイトを作りました

これは何?

音でグラスを壊したいという破壊衝動に駆られたため,Vue.jsの勉強も兼ねて初めて作ったサイト「爆音グラス破壊」です.

グラスを弾いた音から固有周波数を分析し,その周波数のサイン波を再生してグラスを激しく共振させ破壊します.

現状の手元環境では破壊できるだけの爆音がないので,悶々しています...助けて

使い方

https://hakai.yasaidev.work/usage

準備

  • スマホもしくはパソコン
  • 割れやすそうなワイングラス
  • いい感じの長さのストロー
  • できれば爆音スピーカー (最悪スマホスピーカーでもOK)

破壊

  • Analysisボタンを押す (マイクの確認ダイアログがでたら許可)
  • もう一度Analysisボタンを押す
  • カウントダウン後グラスをはじいて音を出す
  • サイトが音を自動分析
  • Playボタンは破壊音を再生!

破壊の仕組み

はじめにマイクからグラスを弾いた音をFFTして,周波数のピークをとり固有周波数として求めます.

次に,その固有周波数のサイン波を生成し再生します.

結果,グラスは共振し激しく揺れて限界を超えた際破壊されてしまいます.

Tech

  • JS framework: Vue.js
  • Design Framework: Vuetify
  • オーディオ/描画周り: p5.js

開発周りの工夫点

Vercelの自動デプロイを導入

mainブランチにpushするたびに自動でvercelにデプロイするようにしました.といってもほとんどvercel君がやってくれたので30分で導入できたのは神です.

ちなみに,Vercel bot君はプルリクエストに対しても勝手に検証用デプロイまで行ってくれて感動しました.(神では?)
image.png

Google Analyticsの導入

興味半分でvue-gtagで導入しました.クリックなどのイベントを事細かにGA側に送信することが可能といった知見を得れて便利だなと感じた一方,実際のサイトでもこれだけ事細かに情報が収集されているのかと感じ少し恐ろしくなりました.

かっこいい?OGPの導入

かっこいいね!

クソアプリなのにSNS映えを意識したそうです.今後のアプリ開発等にも役立つ知見を得れたのでヨシです.

P5.jsとVue.jsの組み合わせ

ここが一番コーディングで苦労しました.多少知見がたまったのでそのうち記事にします.

P5.jsが何かというと,クリエイティブ向けコーディング(Ex.オーディオビジュアライザー,ゲームなど)に特化したライブラリです.これを活用したことで,Web Audio周りに関して抽象的に記述できたり,Audioと合わせた描画等をまとめて処理を行うことができました.

ページの軽量化に取り組んだ

当初はNetlifyにデプロイし,速さを求めVercelにデプロイした.

また,PageSpeed Insightsを使って原因が適切なmin.jsを使わずに生のjsを使っていたことに気づき修正しました.
(これが主たる低速度の原因,もしかしたらNetlifyは悪くなかったかも知れないです)

さらに,サイズの大きいcomponentを非同期ロードすることで見かけの表示速度を早めました.

参考: 非同期コンポーネント:ロード状態のハンドリング

const AsyncComponent = () => ({
  // ロードすべきコンポーネント (Promiseであるべき)
  component: import('./MyComponent.vue'),
  // 非同期コンポーネントのロード中に使うコンポーネント
  loading: LoadingComponent,
  // ロード失敗時に使うコンポーネント
  error: ErrorComponent,
  // loading コンポーネントが表示されるまでの遅延時間。 デフォルト: 200ms
  delay: 200,
  // timeout が設定され経過すると、error コンポーネントが表示されます。
  // デフォルト: Infinity
  timeout: 3000
})

結果かなり当初よりは早くなりました.しかし,モバイル向けのスコアが低いのでもっと早めたいです.
image.png
image.png

やったけど取り消した点とか反省点

Github pagesにVue RouterのSPAサイトをデプロイした

当初はGithub Pagesに自動デプロイするようなworkflowを書いて動作させようとしましたが,Vue RouterのSPAのデフォルト設定とGithub pagesは少し相性が悪い.初学者でデプロイにハマりたくなかったのでnetlifyへと移行することを決心しました.

おそらくvueやreactなどはnetlifyやvercelのほうが脳死でデプロイできるので楽かもです.

Netlifyへデプロイした

Github workflowでGithub側でビルドしてNetlifyにデプロイする物を書きました.

参考: Qiita: GitHub Actions による GitHub Pages への自動デプロイ

こうすることでNetlify側のビルド時間制限を無視して好きなだけデプロイすることができるようにしました.

しかし,アクセスが遅い気がしたので,Vercelに移行しました.

(Pagespeed insightがモバイルで10点台)

これは後述するがp5.jsのminバージョンを使っていなかったことが大きいと思われます.このことに気づいていなかったです.

min.js を使わっていなかった

Vercelに移行後もやはりやけにページの読み込みが遅いと思ってGoogleのPageSpeed Insightsを使って確認したところP5.jsが大変ドデカサイズでありました(圧縮前5MB,説明不要でかい).

min.jsに切り替えたら600KiBぐらいになったのでサクサクになりました.

正直npm run buildしたら勝手にmin.jsに切りかわるなどしてファイルサイズの最適化がされると思いこんでいました.反省.

Githubのブランチ名がmasterからmainになったことを忘れていた

GitHub Actions による GitHub Pages への自動デプロイを参考にworkflowを書いていたのですが,何故かpushしてもactionが動かないことがありめちゃくちゃ悩みました.

そうです,元記事ではactionのトリガーのブランチ名がmasterだったのです...泣きました.

みなさんもブランチ名が絡むもののコピペには気をつけてください.世界は変わってしまいました.

もろもろの所感

手元の音響設備ではワイングラスを破壊するほどの音量が確保できなかったのがすごく残念です.爆音をお持ちの方はグラスワインを破壊して僕に自慢してほしいです.

あと,普段はPython書いているのですがJavascript書いていると文法がごっちゃになって最近はSyntax Errorの量産機になりました.悲しいです.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ytdl-coreとyt-searchを使って検索できるDiscord音楽ボットを作る

はじめに

Node.jsとytdl-coreを使ってDiscordの音楽ボットを探してみると,リンクをもとに流してるのが多いです。

↓こんな感じ

main.js
const discord = require('discord.js')
const client = new discord.client()
const ytdl = require('ytdl-core')
client.on('message',async message =>{

if(message.content.startsWith("!yt")&&message.content.match("https://")){
     // メッセージから動画URLだけを取り出す
var url = message.content.split
     // まず動画が見つからなければ処理を止める
     if (!ytdl.validateURL(url)) return message.channel.send('動画が存在しません。URLをお確かめください。\n')
     // コマンドを実行したメンバーがいるボイスチャンネルを取得
     const channel = message.member.voice.channel
     // コマンドを実行したメンバーがボイスチャンネルに入ってなければ処理を止める
     if (!channel) return message.reply('ボイスチャンネルに参加してください。\n')
     // チャンネルに参加
     const connection = await channel.join()
     // 動画の音源を取得
     const stream = ytdl(ytdl.getURLVideoID(url), { filter: 'audioonly',quality:'highestaudio' })
     // 再生
      dispatcher = connection.play(stream)
           // 再生が終了したら抜ける
     dispatcher.once('finish', () => {
         channel.leave()
     })
}
})
client.login(token)

しかしこれでは,URLを知っておく必要があるので,yt-searchというパッケージを作って,検索機能も搭載しちゃいましょう。
(これで某音楽ボット要らない...?)

前提条件

discord.jsがちょっとわかるくらい
ytdl-core
yt-search
ffmpeg-static
@discordjs/opus

のインストール(npmでインストールしましょう)

message.content.startsWith()を使ってる人もcomanndを使ってる人も両方のやり方を紹介します。

コード(message.contentを使う場合)

main.js
const ytdl = require('ytdl-core')
const yts = require('yt-search')
client.on('message',async message ={
    if(message.content.startsWith('!yt')&&message.content.match("https://")){
     // メッセージから動画URLだけを取り出す
    var url = message.content.replace('!yt','')
     // まず動画が見つからなければ処理を止める
     if (!ytdl.validateURL(url)) return message.channel.send('動画が存在しません。URLをお確かめください。')
     // コマンドを実行したメンバーがいるボイスチャンネルを取得
     const channel = message.member.voice.channel
     // コマンドを実行したメンバーがボイスチャンネルに入ってなければ処理を止める
     if (!channel) return message.reply('ボイスチャンネルに参加してください。\n')
     // チャンネルに参加
         const connection = await channel.join()
     // 動画の音源を取得
     const stream = ytdl(ytdl.getURLVideoID(url), { filter: 'audioonly',quality:'highestaudio' })
     // 再生
      dispatcher = connection.play(stream)
           // 再生が終了したら抜ける
     dispatcher.once('finish', () => {channel.leave()})
      }else{
        var douga = message.content.replace('!yt','')
    //Youtubeから検索する
    yts( douga, async function ( err, r ) {//検索
    //videosに検索結果が配列で返される
    var videos = r.videos
    //videoの最初のurlを取得する(動画ごとにデータが入った連想配列がある)
    const url =videos[0].url
    //この行は多分要らないが念の為
     if (!ytdl.validateURL(url)) return message.reply('検索結果に合う動画が見つかりませんでした。スペルミスが無いか 
   確認するか、検索ワードを変更してみてください。')
     // コマンドを実行したメンバーがいるボイスチャンネルを取得
     const channel = message.member.voice.channel
     // コマンドを実行したメンバーがボイスチャンネルに入ってなければ処理を止める
     if (!channel) return message.reply('ボイスチャンネルに参加してください。')
     // チャンネルに参加
     const connection = await channel.join()
     // 動画の音源を取得
     const stream = ytdl(ytdl.getURLVideoID(url), { filter: 'audioonly',quality:'highestaudio' })
     // 再生
      dispatcher = connection.play(stream)
     message.channel.send({embed:{
       title:"再生が開始されました。",
       description:'曲の情報:\n'+videos[0].title+'\nURL:'+videos[0].url+'\nチャンネル'+videos[0].author.name+'\n視聴回数'+videos[0].views+'',
       color:0x36393f,
       timestamp:new Date()

     }})
           // 再生が終了したら抜ける
          dispatcher.once('finish', () => {
       channel.leave()
       });
})
}
})

以上がmessage.contentを使ったやり方です。
メッセージにリンクが有るか無いかで処理を変えます。

command/argsを使う
main.js
const ytdl = require('ytdl-core')
const yts = require('yt-search')
const prefix = "!"
client.on('message',async message ={
    const args = message.content.slice(prefix.length).trim().split(/\s+/);
    const command = args.shift().toLowerCase();
    if(command == 'yt'&&message.content.match("https://")){
     // メッセージから動画URLだけを取り出す
    var url = args.join('\ ')
     // まず動画が見つからなければ処理を止める
     if (!ytdl.validateURL(url)) return message.channel.send('動画が存在しません。URLをお確かめください。')
     // コマンドを実行したメンバーがいるボイスチャンネルを取得
     const channel = message.member.voice.channel
     // コマンドを実行したメンバーがボイスチャンネルに入ってなければ処理を止める
     if (!channel) return message.reply('ボイスチャンネルに参加してください。\n')
     // チャンネルに参加
         const connection = await channel.join()
     // 動画の音源を取得
     const stream = ytdl(ytdl.getURLVideoID(url), { filter: 'audioonly',quality:'highestaudio' })
     // 再生
      dispatcher = connection.play(stream)
           // 再生が終了したら抜ける
     dispatcher.once('finish', () => {channel.leave()})
      }else{
        var douga = ars.join('\ ')
    //Youtubeから検索する
    yts( douga, async function ( err, r ) {//検索
    //videosに検索結果が配列で返される
    var videos = r.videos
    videoの最初のurlを取得する(動画ごとにデータが入った連想配列がある)
    const url =videos[0].url
    //この行は多分要らないが念の為
     if (!ytdl.validateURL(url)) return message.reply('検索結果に合う動画が見つかりませんでした。スペルミスが無いか確認するか、検索ワードを変更してみてください。')
   // コマンドを実行したメンバーがいるボイスチャンネルを取得
     const channel = message.member.voice.channel
     // コマンドを実行したメンバーがボイスチャンネルに入ってなければ処理を止める
     if (!channel) return message.reply('ボイスチャンネルに参加してください。')
     // チャンネルに参加
     const connection = await channel.join()
     // 動画の音源を取得
     const stream = ytdl(ytdl.getURLVideoID(url), { filter: 'audioonly',quality:'highestaudio' })
     // 再生
      dispatcher = connection.play(stream)
     message.channel.send({embed:{
       title:"再生が開始されました。",
       description:'曲の情報:\n'+videos[0].title+'\nURL:'+videos[0].url+'\nチャンネル'+videos[0].author.name+'\n視聴回数'+videos[0].views+'',
       color:0x36393f,
       timestamp:new Date()

     }})
           // 再生が終了したら抜ける
          dispatcher.once('finish', () => {
       channel.leave()
       });
})
}
})
おまけ(ファイル分割してる場合)
main.js
const yts = require('yt-search')
 const ytdl = require('ytdl-core')
module.exports = {
    name: 'yt',
    description: 'youtube-play',
    execute(message , args) {
    async function a(){
     // メッセージから動画URLだけを取り出す
    var url = args.join('\ ')
     // まず動画が見つからなければ処理を止める
     if (!ytdl.validateURL(url)) return message.channel.send('動画が存在しません。URLをお確かめください。')
     // コマンドを実行したメンバーがいるボイスチャンネルを取得
     const channel = message.member.voice.channel
     // コマンドを実行したメンバーがボイスチャンネルに入ってなければ処理を止める
     if (!channel) return message.reply('ボイスチャンネルに参加してください。\n')
     // チャンネルに参加
         const connection = await channel.join()
     // 動画の音源を取得
     const stream = ytdl(ytdl.getURLVideoID(url), { filter: 'audioonly',quality:'highestaudio' })
     // 再生
      dispatcher = connection.play(stream)
           // 再生が終了したら抜ける
     dispatcher.once('finish', () => {channel.leave()})
      }else{
        var douga = ars.join('\ ')
    //Youtubeから検索する
    yts( douga, async function ( err, r ) {//検索
    //videosに検索結果が配列で返される
    var videos = r.videos
    videoの最初のurlを取得する(動画ごとにデータが入った連想配列がある)
    const url =videos[0].url
    //この行は多分要らないが念の為
     if (!ytdl.validateURL(url)) return message.reply('検索結果に合う動画が見つかりませんでした。スペルミスが無いか確認するか、検索ワードを変更してみてください。')
   // コマンドを実行したメンバーがいるボイスチャンネルを取得
     const channel = message.member.voice.channel
     // コマンドを実行したメンバーがボイスチャンネルに入ってなければ処理を止める
     if (!channel) return message.reply('ボイスチャンネルに参加してください。')
     // チャンネルに参加
     const connection = await channel.join()
     // 動画の音源を取得
     const stream = ytdl(ytdl.getURLVideoID(url), { filter: 'audioonly',quality:'highestaudio' })
     // 再生
      dispatcher = connection.play(stream)
     message.channel.send({embed:{
       title:"再生が開始されました。",
       description:'曲の情報:\n'+videos[0].title+'\nURL:'+videos[0].url+'\nチャンネル'+videos[0].author.name+'\n視聴回数'+videos[0].views+'',
       color:0x36393f,
       timestamp:new Date()

     }})
           // 再生が終了したら抜ける
          dispatcher.once('finish', () => {
       channel.leave()
       });
})
}
   }
   a();
 }
}

最後に

Youtubeリンクと検索を使いこなして楽しいDiscordライフを♪
最後までありがとうございました!
このコードをコピーして是非ともいいボットをつくていただければと思います!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javaScriptでジブリ画像一括保存してみた

まえおき

現在、スタジオジブリが各タイトルのシーン画像を大量公開していますね。

Twitterでも大喜利など行われたりして盛り上がっていました。

「客先には業務経験3年ってことにしてあるからね」業界の闇とわかりみが詰まった#ジブリIT業界あるある まとめ

去年の12月に最後のタイトル追加がされましたので、いつ提供が終わってもおかしくないです。

公開されている画像を一括保存するスクリプトをJSでせこせこと書いたのですが、Chromeの拡張機能で簡単にできるやつがありました。
image downloader

しかしせっかく書いたスクリプトを抹消してしまうのも悲しい為、記事に残しておこうと思います。

ということで早速、私は画像を見ながら好きなシーンを保存していくことにしました。

michida「画像選ぶぞ〜よし、やっぱりまずは千と千尋から見ていこう。」
chihiro001.jpeg
michida「あー最初の車に揺られる千尋ね〜〜いいねいいね保存」

chihiro006.jpeg
michida「うわー豚のお父さん拡大して見ると迫力あるなーーー保存」

chihiro011.jpeg
michida「あー良い保存」

chihiro015.jpeg
michida「うん良い保存」
michida「一枚飛ばして」
chihiro022.jpeg
michida「保存一枚飛ばして保存」
chihiro026.jpeg

chihiro032.jpeg
chihiro037.jpeg
chihiro048.jpeg
michida「保存保存保存保存保存保存保存保存保存保存」

保存したい画像が多すぎる!!!!!!!!!!
保存したい画像が多すぎるのか保存したくない画像が少なすぎるのか。

ちなみに画像を保存するのはWEBページからデスクトップやフォルダなどにドラッグ&ドロップすれば保存できると思いますが、このジブリのサイトは一覧の画像をドラッグ&ドロップするとサムネイルの画像が保存となり、クリックしてモーダルを開いてドラッグ&ドロップしようとすると横スクロールが効いてしまいます。
期待する一枚絵を保存するには右クリックで名前をつけて保存をしないといけないのでサクサクできないんですよね。

非常に手間ですね。
いえ、文句ではないです。そんなこと言いません。

感謝の気持ちでいっぱいです。
ほんとにありがとうございます。

スクリプト書きます。(拡張機能があるとも知らず。。。)

本題

ということで書いたのがこちらです。

function makeGalleryIndex(index) {
  let galleryNumber = {};
  return index < 10
    ? galleryNumber.name = '00'+index
    : galleryNumber.name = '0'+index;
}

function download(i){
  return new Promise(resolve => {
    setTimeout(() => {
      // urlから作品名切り取り
      let contentName = location.href.split('/')[4];
      console.log(contentName);

      // aタグ作成
      let link = document.createElement('a');
      // ファイル名xxx001.jpegのインデックス部分を作成
      let galleryIndex = makeGalleryIndex(i);
      // 画像取得先設定
      link.href = 'https://www.ghibli.jp/gallery/'+ contentName + galleryIndex+'.jpg';
      console.log(link.href);
      // ダウンロード属性をつけ、保存する名前を決める
      link.setAttribute('download', contentName + galleryIndex)
      // 保存
      link.click();

      // 負荷をかけないように1秒待ちましょう
      resolve('1秒待ちます');
    }, 1000);
  })
}

async function galleryDownload() {
  const gallery = document.getElementsByClassName('row gallery');
  const galleriesSize = gallery[0].getElementsByTagName('figure').length;
  console.log(galleriesSize);
  for (let i = 1; i <= galleriesSize; i++) {
    console.log(await download(i));
  }
}

// 画像ダウンロード関数呼び出し。成功時、例外補足などは特に処理は書きません。
galleryDownload().then().catch();

一秒おきにsleepさせたいのでsetTimeoutとPromiseで実行してます。

実際に動かしてみましょう。

保存したい作品のページに遷移しデベロッパーツールのConsoleに貼り実行するとページ内の画像を保存してくれます。

ezgif.com-gif-maker.gif

個人的にConsoleでのPromiseの使い方がうまくできずに詰まりましたが同僚に聞きながら無事動かすことができました。

「あーダウンロードしておけば良かったー」となる前に急いでみなさん保存しましょう(誰)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GAS】lengthの意外?な使い方【JS】

let arr = [1, 2, 3, 4, 5];
console.log(arr.length); //5

配列の要素数を知りたい時に使う「length」。ちょいちょい使うので単純にそういうもんだとばかり思っていたら、それだけじゃなかった。

意外ではなく常識なのかもだけど、lengthプロパティが上書きできると知って驚き!こういう時、ちゃんと書籍とかで勉強しといた方が良いんだろうなって思う^^;

例えば、要素数5の配列のlengthを、あとから2にすると・・・。

let arr = [1, 2, 3, 4, 5];
console.log(arr.length); //5

arr.length = 2;

console.log(arr.length); //2
console.log(arr);        //[ 1, 2 ]
console.log(arr[3]);     //undefined

arr[2]以降は消滅!要素数2の配列になってしまった。arr.length=2を受け付けちゃうなんて、とても柔軟でホスピタリティ溢れる対応 ワァ(・∀・)オ

だから、こんなこともできちゃう。

let arr = [4, 3, 1, 2, 5];
arr.sort().length = 3;
console.log(arr); // [ 1, 2, 3 ]

配列内の小さい数字BEST3を抽出^^)b

こんなことも。

let arr = [1, 2, 3, 4, 5];
arr.length = 0;
console.log(arr); // []

arr.length=0で配列をリセット!こんな書き方する機会があるかは知らないけど^^;

おしまい

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【自動再生&音量注意】物理エンジンで自由落下+衝突時に音を鳴らしてみた

できたもの

See the Pen drop cubes by using cannon.js by kob58im (@kob58im) on CodePen.

鳴ったり鳴らなかったり…
スマホでは、ユーザー操作がないと鳴らせないらしい。
ブラウザアプリでのサウンド再生(苦悩編) | スマゲー作るよ

あと、画面外だと鳴らないっぽい(?)

効果音を連続的に再生させるやりかた

再生が完了しきらないうちに、次の再生を設定・要求すると、まともに再生できなかったので、
再生中の場合には、下記のように先頭から再生しなおしている。

audio.pause();
audio.currentTime = 0;
audio.play();

参考サイト

cannon.js - 衝突検出

音を鳴らす

chart.js

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

物理エンジンで自由落下+衝突時に音を鳴らしてみた

できたもの

See the Pen drop cubes by using cannon.js and make sound by kob58im (@kob58im) on CodePen.

鳴ったり鳴らなかったり…
スマホでは、ユーザー操作がないと鳴らせないらしい。
ブラウザアプリでのサウンド再生(苦悩編) | スマゲー作るよ

【追記:コメント頂いた内容を参考に、ボタンで開始するようにしてみました。】

あと、画面外だと鳴らないっぽい(?)

効果音を連続的に再生させるやりかた

再生が完了しきらないうちに、次の再生を設定・要求すると、まともに再生できなかったので、
再生中の場合には、下記のように先頭から再生しなおしている。

audio.pause();
audio.currentTime = 0;
audio.play();

参考サイト

cannon.js - 衝突検出

音を鳴らす

chart.js

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

物理エンジンで複数の物体を自由落下させて、衝突時に音を鳴らすようにしてみた

できたもの

グラフは、衝突した物体のペアの番号をプロットしたものです。(0番は地面)

See the Pen drop cubes by using cannon.js and make sound by kob58im (@kob58im) on CodePen.

鳴ったり鳴らなかったり…
スマホでは、ユーザー操作がないと鳴らせないらしい。
ブラウザアプリでのサウンド再生(苦悩編) | スマゲー作るよ

【追記:コメント頂いた内容を参考に、ボタンで開始するようにしてみました。】

あと、画面外だと鳴らないっぽい(?)

効果音を連続的に再生させるやりかた

再生が完了しきらないうちに、次の再生を設定・要求すると、まともに再生できなかったので、
再生中の場合には、下記のように先頭から再生しなおしています。

audio.pause();
audio.currentTime = 0;
audio.play();

参考サイト

cannon.js - 衝突検出

音を鳴らす

chart.js

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WiiリモコンをGamepadにして、HTML5で使う

以前の投稿で、Wiiの操作をブラウザから取得しました。

Wiiとブラウザの間をUbuntuが仲介し、ブラウザとUbuntuはMQTTを使っていました。
今回は、WindowsからみてBLE接続のM5StickCをGamepadとして見えるようにします。こうすることで、HTML5 Gamepad APIとしてWiiを使えるようになります。

 ブラウザ → HTML5 Gamepad API → Gamepad(ESP32) → Ubuntu → Wiiリモコン

繰り返しになりますが、HTML5 Gamepad APIとGamepad(ESP32)の間は、BLEです。
Gamepad(ES32)とUbuntuの間はMQTTではなくUDPにしました。シンプルにしたかったためというのと、ESP32は非力であるためです。
UbuntuとWiiリモコンの間は以前と同じです。
※ubuntuでなくてもラズパイなんかでもいいです

もろもろのソースコードはGitHubに上げておきました。

poruruba/WiiRemocon
 https://github.com/poruruba/WiiRemocon

また、以下のライブラリを利用していますので、あらかじめZIPダウンロードしてArduino IDEにインストールしておきます。

lemmingDev/ESP32-BLE-Gamepad
 https://github.com/lemmingDev/ESP32-BLE-Gamepad

Gamepadの準備

ESP32としてM5StickCを使いました。コンパイル環境はArduinoIDEを使いました。
ソースは、WiiGamepadフォルダにあります。

以下の部分を修正してください。

WiiGamepad/WiiGamepad.ino
const char* wifi_ssid = "【WiFiアクセスポイントのSID】";
const char* wifi_password = "【WiFiアクセスポイントのパスワード】";

以下の部分でUDP受信して、Jsonパースして解析し、Gamepadとして通知しています。

WiiGamepad/WiiGamepad.ino
uint32_t prev_buttons = 0;

void loop() 
{
  if( udp.parsePacket() ){
    int len = udp.read(message_buffer, sizeof(message_buffer));
    if( len > 0 && bleGamepad.isConnected() ){
      Serial.printf("received(%d) ", len);
      message_buffer[len] = '\0';
      Serial.printf("%s\n", message_buffer);
      DeserializationError err = deserializeJson(json_message, message_buffer, len);
      if( err ){
        Serial.println("Deserialize error");
        Serial.println(err.c_str());
        return;
      }

      int rsp = json_message["rsp"];
      if( rsp == WIIREMOTE_CMD_EVT ){
        uint8_t data[22];
        for( int i = 0 ; i < sizeof(data) ; i++ )
          data[i] = json_message["evt"][i]; 
        WII_REPORT report = parseReporting(data);
        if( report.report_id == WIIREMOTE_REPORTID_BTNS ){
          uint32_t diff = prev_buttons ^ report.btns.btns;
          bleGamepad.release(diff ^ (diff & report.btns.btns));
          bleGamepad.press(diff & report.btns.btns);
          prev_buttons = report.btns.btns;
        }else{
          Serial.println("Not support report id");
        }
      }
    }
  }
}

書き込まれ、起動が完了すると、Wifi接続し、接続が完了するとIPアドレスがSerialに表示されます。このIPアドレスは後で使うので覚えておきます。

ちなみに、Node.js v12.19.0で確認しました。
※前回まではv8で動かしていたのですが、v12に合わせていくつか修正してあります。

inquiryフォルダに移動します。

cd inquiry
npm install
sudo node inquiry_test.js

これで、Wiiを探している状態になるので、Wiiリモコンの①②を両方押して、Discoveryモードにすると、発見されてMACアドレスが表示されます。このMACアドレスを覚えておきます。

$sudo node inquiry_test.js
local: XX:XX:XX:XX:XX:XX
remote: XX:XX:XX:XX:XX:XX  ★これです!
status: 0
{ local: 'XX:XX:XX:XX:XX:XX', remote: 'XX:XX:XX:XX:XX:XX' }

次に、Wiiと通信してESP32にUDP通知するサーバを用意します。

cd wiiremocon
npm install
node-gyp rebuild

これで準備ができました。以下を起動します。

sudo index_dgram.js 【WiiのBLEのMACアドレス】 【ESP32のIPアドレス】

Wiiリモコンの①②を両方押して、Discoveryモードにすると、接続が完了します。(Wiiリモコンとの接続を切断した直後であれば、Wiiリモコンの①②を押さなくても接続が完了する場合があります)

Gamepadとして認識させる

まずは、WindowsにGamepadとして認識させます。

BLEデバイスを追加します。Bluetoothまたはその他のデバイスを追加する をクリックし、次の画面で、Bluetoothを選択します。

image.png

そうすると、ESP32 BLE Gamepadというのがあるのがわかります。クリックすると接続を試みます。

image.png

接続が完了しました。

image.png

こんな感じで「接続済み」となります。

image.png

Gamepadとして認識するには少し時間がかかります。(30秒ぐらい?)
デバイスとプリンタを選択し、ESP32 BLE Gamepadを右クリックすると、ゲームコントローラの設定というのがでてきていたら、認識完了です。

image.png

簡単な動作確認をしてみます。
ゲームコントローラの設定を選択します。

image.png

6軸32ボタンデバイスハットスイッチ付きが選択されている状態でプロパティを押下します。(現状はボタンしか対応させていませんが。。。)

image.png

ボタンを押せば、赤くなるのがわかります。

HTML5 Gamepad APIとして使う

今度は、ブラウザからアクセスしてみます。
WindowsがM5StickCをGamepadとして認識しているので、あとは、ブラウザのHTML5 Gamepad APIからアクセスします。

htmlフォルダにあるコンテンツをどこかのHTTPSのWebサーバに配備します。
ブラウザからそれを開きます。

 https://【Webサーバのホスト名】/wiigamepad/index.html

接続できると、gamepad_foundがtrueとなり、押したボタンの色が濃くなるかと思います。

image.png

html/js/start.js
        setInterval(() =>{
            var gamepadList = navigator.getGamepads();
            console.log(gamepadList);
            var found = false;
            for( var i = 0 ; i < gamepadList.length ; i++ ){
                var gamepad = gamepadList[i];
                if( gamepad ){
                    found = true;
                    for( var j = 0 ; j < gamepad.buttons.length ; j++ ){
                        this.$set(this.chk_btns, j, gamepad.buttons[j].pressed);
                    }
                }
            }
            this.gamepad_found = found;
        }, 100);

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(小ネタ)TypeScriptで型定義ファイルが無いモジュールの読み込み方法

はじめに

ml5を利用しようとしたところ、型定義が無く、エラーが発生したため、型定義が無い時のモジュールの読み込み方法について調べました。
*なお、ml5の型定義は現在draft版を作成中。
https://gist.github.com/dikarel/38a12ce263199a41ad67c15eac7f4b45

型定義が無い時のエラー

ml5を以下のようにモジュールをインポートするとエラーとなる。

import * as ml5 from "ml5";

具体的には以下のようなコンパイルエラーが発生する。

Could not find a declaration file for module 'ml5'.
Try npm install @types/ml5 if it exists or add a new declaration (.d.ts) file containing declare module 'ml5';

型定義が無い時の読み込み方法

型定義ファイル( d.ts )を自作すれば良いが、面倒な時はrequire でモジュールを読み込みます。
暗黙的に any 型になるので、型定義ファイル d.ts が見つからないエラーは消えます。

const ml5 = require('ml5')

ただし TSLint の設定によっては [tslint] require statement not part of an import statement (no-var-requires) という警告がでます。
// tslint:disable-next-line:no-var-requires とコメントで require('ml5') についてだけ警告を無効にします。

// tslint:disable-next-line:no-var-requires
const ml5 = require('ml5')

この状態では ml5any 型になり、型チェックも賢い補完も行われません。
自分の使う API だけ型付けして置くと、開発がスムーズです。

App.tsx
type ImageClassifierOptions = {
  alpha: number;
  topk: number;
  version: number;
};

interface IMl5 {
   imageClassifier(
    model: "MobileNet" | "Darknet" | "Darknet-tiny" | string,
    callback?: (error: any, result: any) => void
  ): undefined | Promise<any>;

  imageClassifier(
    model: "MobileNet" | "Darknet" | "Darknet-tiny" | string,
    options?: ImageClassifierOptions,
    callback?: (error: any, result: any) => void
  ): undefined | Promise<any>;

  imageClassifier(
    model: "MobileNet" | "Darknet" | "Darknet-tiny" | string,
    video?: MediaElement | HTMLVideoElement,
    options?: ImageClassifierOptions,
    callback?: (error: any, result: any) => void
  ): undefined | Promise<any>;

}
const ml5 = require('ml5') as IMl5;

終わりに

以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npmに自作モジュールを公開する方法

はじめに

先日Node.jsの勉強中にnpmについて改めて学ぶ機会がありました。その際npmにサンプルのモジュールを公開したので、具体的なやり方をシェアします。

npmとは

Node Package Managerが正式な名称です。その名の通りNode.jsのパッケージを管理するためのツールです。ホームページは下記リンク先からアクセスできます。

https://www.npmjs.com/

npmはNode.jsをインストールする際にインストールされます。インストール済みの場合、下記コマンドでバージョンが表示されます。

npm --version

npmに自作モジュールを公開する方法

公開までの流れ

流れは下記の通りです。

  • npmのアカウントを作成する。
  • 公開したいモジュールを作成する。
  • npmへログインする。
  • npmへ公開する。

それでは1つずつ見ていきます。

npmのアカウントを作成する

まずはnpmのアカウントを作成します。下記リンク先からSign Upできます。

https://www.npmjs.com/signup

公開したいモジュールを作成する

npm initでpackage.jsonを作成します。package nameを聞かれるので、npmレポジトリ上に存在していない名前を入力します。あらかじめnpmのサイトで利用されていない名前であることを確認しておきましょう。

npm init

package.jsonができたら、private属性をpackage.jsonに追記して公開設定にします。

"private": false

次に、README.mdファイルを準備します。ここにはどういった動きをするモジュールなのか、プロジェクトの概要を記述しておきます。

npmへログインする

ターミナルを起動し、プロジェクトのルートで下記コマンドを入力します。ユーザーネーム、パスワード、メールアドレスを聞かれるので入力します。

npm login

npmへ公開

ログインができたら、下記コマンドで公開できます。

npm publish —access public

※—dry runオプションをつけることで、お試しで実行することができます(実際には公開されません)。

npm publish —access public —dry run

最後に

簡単ですがサンプルのモジュールをGitHub上にpushしたので、package.json等の参考までに見てください。

https://github.com/n199603/npm-study

以上、npmに自作モジュールを公開する方法でした。見ていただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NimのコードをNode.js用のJavaScriptコードにトランスコンパイルする

概要

  • NimのコードをJSバックエンドでコンパイルしてNode.jsのスクリプトとして実行してみます
  • NimのコードからNode.js用のライブラリを生成してNode.jsから関数を呼び出してみます

NimはJavaScriptを生成できる

Nimは公式でNimのコードからJavaScriptのコードにトランスコンパイル可能です。

例えば、以下のようなNimコードに対して

index.nim
proc plus*(x, y: cint): cint {.exportc.} =
  return x + y

以下のコマンドでコンパイルします。すると、index.nimからindex.jsが生成されます。

$ nim js index.nim

$ ls index*
index.js index.nim

このようにJavaScriptコードを生成できます。

しかしここで生成されるJavaScriptはフロントエンド用で、HTMLから読み込む想定のものです。
Node.jsからは扱うことはできません。

NimはNode.jsのコードも生成できる

しかし、コンパイル時にオプションを渡すことにより、Node.js向けのNimコードを生成できます。
Nim Backend Integration - Nim

前述のコードを手直ししてNode.js用コンパイルを実施し、Node.jsで実行してみます。

index.nim
proc plus*(x, y: cint): cint {.exportc.} =
  return x + y

echo plus(1, 2)
$ nim js -d:nodejs index.nim

$ node index.js
3

node.jsで実行できました。

NimのコードをNode.js向けライブラリとして生成する

Node.js用の単体のスクリプトとして実行できることがわかりました。
次はスクリプトをNode.jsからライブラリとして読み込めることを確認します。

以下のNimコードを用意します。
以下のNimコードでは、plus関数を定義して、Node.jsからplus関数を呼び出せるようにするものです。

lib.nim
import jsffi

var module {.importc.}: JsObject

proc plus*(x, y: cint): cint =
  return x + y

module.exports.plus = plus

次に、上記ライブラリを読み込んで使用するindex.jsを定義します。

index.js
const lib = require("./lib");
console.log(lib.plus(1, 2));

これらのファイルがそれぞれ同じディレクトリに配置し、以下のコマンドを実行します。

$ nim js -d:nodejs lib.nim

$ ls lib*
lib.js lib.nim

$ node index.js
3

無事、Node.jsのスクリプトから、Nimで生成したNode.js用ライブラリを読み込んで、関数を呼び出すことができました。

まとめ

以下の内容について記載しました。

  • NimのコードをJSバックエンドでコンパイルしてNode.jsから関数を呼び出す
    • コンパイルするときはjsバックエンドで -d:nodejs を付ける
  • NimのコードからNode.js用のライブラリを生成してNode.jsから関数を呼び出してみます
    • module {.importc.}: JsObjectでモジュールを読み込んで関数をセットする

以上です

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cannon.jsの衝突イベントのはまりどころメモ

衝突時に音でもならしてみようかなと思って、cannon.jsの衝突検出時に発生するcollideイベントについて調べてみた。

衝突イベントは衝突毎に2回発生する

2つの物体が衝突するので、それぞれの物体に対してイベントを発火するようである。

cannon.js/World.js at master · schteppe/cannon.js · GitHubより抜粋:

if (!this.collisionMatrixPrevious.get(bi, bj)) {
    // First contact!
    // We reuse the collideEvent object, otherwise we will end up creating new objects for each new contact, even if there's no event listener attached.
    World_step_collideEvent.body = bj;
    World_step_collideEvent.contact = c;
    bi.dispatchEvent(World_step_collideEvent);

    World_step_collideEvent.body = bi;
    bj.dispatchEvent(World_step_collideEvent);
}

きわめて低速でも衝突イベントは発生する

下記を使うと衝突時の速度が得られるようである。これを使用して、速度に応じて処理を切り分けるとよいのではないか。※参考サイトのコードでは絶対値(abs)を取っている。詳細調べてないので正確な要否は不明だが、付けておいたほうが無難か。
contact.getImpactVelocityAlongNormal()

参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで変数の型がわからないときの調べかたメモ

JavaScriptのライブラリとかを使ってて、下記のように型が不明なときに手探りで調べた内容をメモしておく。

例:CANNON.jsの下記のeの型がわからない。

cannonObj.addEventListener("collide",function(e){・・・});

手段

とりあえずキーワードでググる。

  • ライブラリのドキュメントを探す。(GitHubとか)
  • ライブラリのソースを探す&ソース上でキーワード検索する。(同上) ・・・collideで検索した結果
  • ライブラリのデモコードを探す。(同上) ・・・
  • リフレクションを使う。

リフレクションを使う

Reflect - JavaScript | MDN

プロパティ名を得る

Reflect.ownKeys() - JavaScript | MDN

実行例:

cannonObj.addEventListener("collide",function(e){
  console.log(Reflect.ownKeys(e));
});

実行結果:

["type", "body", "contact", "target"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで、ライブラリとかの変数の型や詳細情報がわからないときの調べかたメモ

JavaScriptのライブラリとかを使ってて、下記のように型が不明なときに手探りで調べた内容をメモしておく。

例:CANNON.jsの下記のeの型がわからない。

cannonObj.addEventListener("collide",function(e){・・・});

手段

静的に調べる

とりあえずキーワードでググる。

  • ライブラリのドキュメントを探す。(GitHubとか)
  • ライブラリのソースを探す&ソース上でキーワード検索する。(同上) ・・・collideで検索した結果
  • ライブラリのデモコードを探す。(同上) ・・・

動的に調べる

  • クラス名を調べる。
  • リフレクションを使う。

クラス名を調べる

下記が詳しいので、ここで特に書くことはないです。(他力本願!)

おまけ:

リフレクションを使う

クラスとかじゃない場合は、リフレクションを使うと多少の情報は得られそう。

Reflect - JavaScript | MDN

プロパティ名を得る

Reflect.ownKeys() - JavaScript | MDN

実行例:

cannonObj.addEventListener("collide",function(e){
  console.log(Reflect.ownKeys(e));
});

実行結果:

["type", "body", "contact", "target"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

herokuデプロイ後sequelizeでPostgreSQLに接続できないときの解決法

sequelizeを使ってPostgreSQLと接続したい。

ローカル環境では接続に成功していたが、herokuにデプロイした後は失敗する。

失敗

const Sequelize = require('sequelize');

// herokuまたはローカル環境のPostgreSQLに接続する
const sequelize = new Sequelize(process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost/hogehoge');

成功

const Sequelize = require('sequelize');

const sequelize = new Sequelize(
  process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost/hogehoge', 
  {dialectOptions: { ssl: true }}
);

ログを見るとssl off(うろ覚え)とあったので、このようにオプションを追加してみると接続に成功した。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Node.js】現在日時を取得する方法3選

目標

Node.jsで現在日時をYYYYMMDDHHmmssの14桁のフォーマットで出力します。
nodeコマンドで下記のような出力が得られるプログラムですね。

$ node index.js
202101082341

前提

Node.js 14.15.4
date-utils 1.2.21
moment 2.29.1

Javascriptで頑張る

index.js
const date = new Date();
const currentTime = formattedDateTime(date);
console.log(currentTime)

function formattedDateTime(date) {
  const y = date.getFullYear();
  const m = ('0' + (date.getMonth() + 1)).slice(-2);
  const d = ('0' + date.getDate()).slice(-2);
  const h = ('0' + date.getHours()).slice(-2);
  const mi = ('0' + date.getMinutes()).slice(-2);
  const s = ('0' + date.getSeconds()).slice(-2);

  return y + m + d + h + mi + s;
}

date-utiliesを利用

index.js
require('date-utils');
const date = new Date();
const currentTime = date.toFormat('YYYYMMDDHH24MISS');
console.log(currentTime);

Moment.jsを利用

index.js
const moment = require('moment');
const currentTime = moment();
console.log(currentTime.format("YYYYMMDDHHmmss"));

結論

モジュールって便利!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Node.js】現在日時を取得する方法4選

目標

Node.jsで現在日時をYYYYMMDDHHmmssの14桁のフォーマットで出力します。
nodeコマンドで下記のような出力が得られるプログラムですね。

$ node index.js
202101082341

前提

Node.js 14.15.4
date-utils 1.2.21
moment 2.29.1

更新情報

【2020/01/09 toLocaleStringメソッドを利用 追加】
@il9437 様、ありがとうございます!

Javascriptで頑張る

index.js
const date = new Date();
const currentTime = formattedDateTime(date);
console.log(currentTime)

function formattedDateTime(date) {
  const y = date.getFullYear();
  const m = ('0' + (date.getMonth() + 1)).slice(-2);
  const d = ('0' + date.getDate()).slice(-2);
  const h = ('0' + date.getHours()).slice(-2);
  const mi = ('0' + date.getMinutes()).slice(-2);
  const s = ('0' + date.getSeconds()).slice(-2);

  return y + m + d + h + mi + s;
}

date-utiliesを利用

index.js
require('date-utils');
const date = new Date();
const currentTime = date.toFormat('YYYYMMDDHH24MISS');
console.log(currentTime);

Moment.jsを利用

index.js
const moment = require('moment');
const currentTime = moment();
console.log(currentTime.format("YYYYMMDDHHmmss"));

toLocaleStringメソッドを利用 (@il9437 様からのご教示)

記事を出した時点では上記のやり方で実装する必要があるとの認識でしたが、
@il9437 様にtoLocaleStringメソッドでスウェーデン語を指定してから加工する効率的な方法を教えて頂いたので記事内でも紹介します

index.js
const date = new Date().toLocaleString('sv').replace(/\D/g, '');
console.log(date);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む