20211123のJavaScriptに関する記事は18件です。

YahooAPIを利用してルビを振る V2

 以前作った「YahooAPIを利用してルビを振る」で使っていたAPIが廃止になるようだ。それに変わってバージョンが上がったものがある。なのでそれを使って「YahooAPIを利用してルビを振る」のをやってみようと思う。 変更点がXMLからJSONになった。xmlhttpreqestの要求もjsonをテキスト化して送っている。リクエストヘッダーにユーザーエージェントを指定しているので、ブラウザからxmlhttpreqestではリクエストを送れなかった。 http.setRequestHeader("Content-Type", "application/json"); http.setRequestHeader('User-Agent','Yahoo AppID: ' + Appid); // 要求 var param_dic = { "id": "1234-1", "jsonrpc": "2.0", "method": "jlp.furiganaservice.furigana", "params": { "q": Sentence, "grade": Grade } } http.send(JSON.stringify(param_dic)); XMLからJSONに変更になったが、向こう側でXMLで作ったものをJSONに変換しているだけなのではないかと思ったので。XMLのパースもJSONのパースもあまり変わらない感じである。 以下全体のコードである。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】追加学習分〜③「配列を操作するメソッド」

1. はじめに これまで記事にしてきたJavaScriptの学習に関して、抜けがあった分を記載する。 今回は「配列を操作するメソッド」に関して記述していきたい。 前回学んだ「関数」をたくさん使うので、配列操作のメソッドを学ぶとともに関数を復習する意味で進めていく。 2. forEachメソッド forEachメソッドとは? JavaScriptにおいて、配列に特化したループ処理である。 コールバック関数※に実行したい内容を記述することで、 配列の要素ひとつひとつに対して繰り返し処理を実行する。 ※ある関数を呼び出す時に、引数に指定する別の関数のこと。 過去に繰り返し処理の記事を書いているので、復習の意味も込めてリンクを掲載。 繰り返し処理の記事一覧 for while do while forEachでできること forEachでは、配列の各要素に対する繰り返し処理を一括で指示することができる。 forでのデメリット 配列内のデータを順に出力するには ループの終了条件 や カウンターなどの設定 が必要となり、少し複雑なコードになりがちになる。 forEachにすることのメリット コールバック関数に実行したい処理内容を記述するだけで済むので、 コードをシンプルにまとめることができ、ミスの軽減や管理がしやすくなるといったメリットがある。 forEachの基本と使い方 forEachの書式や基本的な使い方は以下の通り。 書式 index.js 配列名.forEach( コールバック関数(要素の値) ) 配列名.forEach( コールバック関数(要素の値, 要素のインデックス) ) 配列名.forEach( コールバック関数(要素の値, 要素のインデックス, 配列) ) 書式の概要 forEachメソッドは、 配列に含まれる要素を先頭から順に取り出しコールバック関数を呼び出す。 コールバック関数は、 現在取り出されている要素の値、 要素のインデックス、 そして配列そのものを引数にして呼び出される。 引数の要素のインデックスおよび配列は必要なければ省略可能。 基本的な使い方 今回は3パターン用意した。 (他にも使い方の例はあるかもだけど、今回はとりあえず3つ) forEachメソッドの引数に記述したコールバック関数を呼び出す サンプル index.js let fruit = ['Apple', 'Melon', 'Orange']; fruit.forEach(function (element) { console.log(element); }); 【実行結果】 Apple Melon Orange 配列に含まれる要素の値である 'Apple', 'Melon', 'Orange' を順に取り出し、 forEachメソッドの引数に記述したコールバック関数を呼び出している。 アロー関数を使った記述 サンプル index.js let fruit = ['Apple', 'Melon', 'Orange']; fruit.forEach((element) => console.log(element)); 【実行結果】 Apple Melon Orange 実行結果は1つ前の例題と同様となる。 こちらの方が簡潔に書けるのがメリット。 要素のインデックスや配列をコールバック関数の引数として受け取って処理する場合 サンプル index.js let fruit = ['Apple', 'Melon', 'Orange']; fruit.forEach(function (element, index, array) { console.log('Index:' + index); console.log('Element:' + element); console.log('Array:' + array); }); 【実行結果】 Index:0 Element:Apple Array:Apple,Melon,Orange Index:1 Element:Melon Array:Apple,Melon,Orange Index:2 Element:Orange Array:Apple,Melon,Orange 下記図解にて解説。 図を使って解説 forEachの()内を入れ換えると以下の出力結果になる。 サンプル:()内入れ替え index.js let fruit = ['Apple', 'Melon', 'Orange']; fruit.forEach(function (index, element, array) { console.log('Index:' + index); console.log('Element:' + element); console.log('Array:' + array); }); 【出力結果】 Index:Apple index.js:5 Element:0 index.js:6 Array:Apple,Melon,Orange index.js:4 Index:Melon index.js:5 Element:1 index.js:6 Array:Apple,Melon,Orange index.js:4 Index:Orange index.js:5 Element:2 index.js:6 Array:Apple,Melon,Orange 出力結果がめちゃくちゃなことになっているが、それには理由がある。 forEachの書式で書いたが、 index.js 配列名.forEach( コールバック関数(要素の値, 要素のインデックス, 配列) ) なので、正しく入力しないと出力結果が狙ったものにならないので注意すること。 配列の繰り返し処理におけるforEachメソッドとforの違い 配列の要素を順に取り出して処理する方法として forEachメソッドを使う以外にforやfor...ofを使うことも出来る。 (for...ofはこちらで解説されているので参考までに) forEach、for、for...ofの違い 下記はforEach、for、for...ofを使ってコードを書いてみた。 forEach index.js let alpha = ['A', 'B', 'C']; alpha.forEach(function (element) { console.log(element); }); 【実行結果】 A B C for index.js let alpha = ['A', 'B', 'C']; for (let i = 0; i < alpha.length; i++) { console.log(alpha[i]); } 【実行結果】 A B C for...of index.js let alpha = ['A', 'B', 'C']; for (let element of alpha) { console.log(element); } 【実行結果】 A B C いずれの形式でも同じ結果となる。 ただし、 配列の要素のインデックスが連続しておらず、 途中に存在しない要素がある場合には異なった結果となる。 実際どのようなものか? 下記は変数alphaに対し配列['A', 'B', , , 'C'];を格納した上で3つの文法を書いてみた。 forEach index.js let alpha = ['A', 'B', , , 'C']; alpha.forEach(function (element) { console.log(element); }); 【実行結果】 A B C for index.js let alpha = ['A', 'B', , , 'C']; for (let i = 0; i < alpha.length; i++) { console.log(alpha[i]); } 【実行結果】 A B undefined undefined C for...of index.js let alpha = ['A', 'B', , , 'C']; for (let element of alpha) { console.log(element); } 【実行結果】 A B undefined undefined C 実行結果を見てわかるように、 forEachだけ出力された結果は3つで、 forとfor...ofだけundefindedを含む5つ という結果となった。 なぜかと言うと、 forEachメソッドは存在しない要素に対しては何も行われないから。 forとfor...ofでは存在しない要素に対しても同じように処理を行うから。 forEach、for、for...ofの違い〜breakを使用した場合〜 forEachメソッドを使用している場合は、 breakが使用できないため繰り返し処理を途中で止めることは出来ない。 orまたは for...ofの場合は、 breakを使用することで繰り返し処理を途中で終了させることが出来る。 そもそも過去に記事にしたforやwhileでbreakの箇所を書いていなかったので、改めて記事にしてみたいと思う。 breakを使ったforやwhile breakが実行されると、 breakが記述されている最も内側の繰り返し処理が終了し次の処理へ移る。 書式は、終了してほしい箇所(次に移りたい箇所)でbreak;と入力するだけ。 実際にforとwhileでbreakを含めてコードを作成すると以下の図ようになる。 for図解 whileの図解 forとwhileとでは繰り返し処理の性質は異なるが、 breakの意味と役割は同様のものとなる。 bleakを織り交ぜた例題〜for 実際に例題を挙げて解説していく。 例題の内容は以下の通り。 変数numの値を順に2倍しながら数値をコンソールに出力 繰り返す回数は10回 for+break index.js let num = 1; for (let i = 0; i < 10; i++) { console.log(num); num *= 2; if (num > 10) { console.log('10を超えたので繰り返し処理を抜けます'); break; } } console.log('end'); 【出力結果】 1 2 4 8 10を超えたので繰り返し処理を抜けます end 変数numの値が10を超えた場合は、 その時点で繰り返し処理を終了させてforの次の処理へ移っている。 先ほどのコードでbreakが記述されている最も内側のブロックはif文だが、 if文はbreakの対象ではないためその外側のforのブロックを抜けている。 bleakを織り交ぜた例題〜while whileの条件式がtrueとなっており、 ランダムの数値が6だった場合にbreakが実行されるようにする。 while+break index.js while (true) { let dice = Math.floor(Math.random() * 6) + 1; console.log(dice); if (dice == 6) { break; } } console.log('end'); Math.floor:対象の値の小数点以下を切り捨てた値を取得するメソッド。 Math.random():乱数を生成し返してあげるメソッド。-1は最小値で、6は最大値の意味。 【出力結果】 2 6 end 乱数で出た数値が6だった場合にはbreakが実行されて whileの次へ処理が移るようになる。 ※出力結果はランダムなので結果もランダム 3. findメソッド findメソッドとは? コールバック関数の処理部分に 記述した条件式に合う1つの要素を配列の中から取り出すメソッド のこと。 簡潔に言うと、 配列の中から上から順番に検索して、最初に条件に合致するものを返す ということ。 実際に例題を書いて説明していく。 例題(1) 内容 配列内にランダムな整数を格納し、その中から3より大きい整数をコンソールへ出力する。 実際に書いていくと以下のようになる。 index.js index.js const numbers = [1, 3, 5, 7, 9]; const foundNumber = numbers.find((number) => { return number > 3; }); console.log(foundNumber); 【出力結果】 5 解説 図を使って解説していく。 図で記載の通り、 findメソッドは最初に当てはまる最初の要素を返す という性質があるので、返される数値は5ということになる。 他にも例題を書いてみた。 例題(2) 内容 今度は内容を複雑化してみた。 配列内にid・name属性を持たせた複数の配列に対し、 特定のidの番号をfindメソッドを使って返す。 実際に書いていくと以下のようになる。 index.js index.js const persons = [ { id: 1, name: "鈴木" }, { id: 2, name: "田中" }, { id: 3, name: "茂木" }, { id: 4, name: "岡島" }, { id: 5, name: "小原" } ]; const foundPerson = persons.find((person) => { return person.id === 4; }); console.log(foundPerson); 【出力結果】 {id: 4, name: '岡島'} 解説 こちらも図で解説していく。 今回の例は、オブジェクトのプロパティを条件としている。 よって、その場合はプロパティが保持しているオブジェクトそのものが取り出される。 なので、定数persons内の中に格納されているid〜の箇所すべてがコンソールへ出力される。 4. filterメソッド filterメソッドとは? 条件に合う要素のみを取り出して新しい配列を作成するメソッドのこと。 実際に例題を書いて説明していく。 例題(1) 内容 配列numbersの要素が1つずつ引数numberに代入される。 その後filterメソッド内で「3より大きい数字」かどうかを判定し、 条件に合う要素が定数filteredNumbersに配列として代入される。 index.js index.js const numbers = [1, 3, 5, 7, 9]; const filteredNumbers = numbers.filter((number) => { return number > 3; }); console.log(filteredNumbers); 【出力結果】 [5, 7, 9] 解説 コードの読み解き方としてはfindメソッドと同様なので割愛する。 findメソッドと違い、該当する値すべてが出力されることがわかる。 例題(2) 配列内にname(名前)・age(年齢)を持たせた複数の配列に対し、 age(年齢)をfindメソッドを使って返す。 index.js index.js const animals = [ { name: "cat", age: 7 }, { name: "dog", age: 9 }, { name: "elephant", age: 14 }, { name: "mouse", age: 13 }, { name: "lion", age: 16 } ]; const filteredAnimals = animals.filter((animal) => { return animal.age > 10; }); console.log(filteredAnimals); 【出力結果】(デベロッパーツールよりキャプチャー) 解説 findメソッドで解説した内容と被ってしまうが、 プロパティが保持しているオブジェクトそのものが取り出される。 5. mapメソッド 配列内のすべての要素に処理を行い、その戻り値から新たな配列を作成するメソッドのこと。 実際に例題を書いて説明していく。 例題(1) 内容 配列numbersのすべての要素を3倍した要素を持つ、新たな配列を作成する。 index.js index.js const numbers = [1, 2, 3]; const tripleNumbers = numbers.map((number) => { return number * 3; }); console.log(tripleNumbers); 【出力結果】 [3, 6, 9] 解説 mapメソッドを使い、引数numberに代入された値を3倍にしてreturnで返している。 例題(2) 今度は文字列を使ったmapメソッドを見ていく。 ファーストネームとラストネームをmapメソッドでつなげていく。 index.js index.js const names = [ { firstName: 'Andrew', lastName: 'Jones' }, { firstName: 'Casey', lastName: 'McGehee' }, ]; const fullNames = names.map((name) => { return name.firstName + name.lastName; }); console.log(fullNames); 【出力結果】 ['AndrewJones', 'CaseyMcGehee'] 解説 nameオブジェクトを、それぞれ配列内で記述したプロパティに紐付ける。 ↓ それをreturnで返してやるとプロパティ同士がつながって出力される。 6. pushメソッド 配列の最後に新しい要素を追加するメソッドのこと。 実際に例題を書いて説明していく。 例題 内容 1〜3の配列を出力した後に、新たに4を配列の最後に出力する。 index.js index.js const numbers = [1, 2, 3]; console.log(numbers); numbers.push(4); console.log(numbers); 【出力結果】 [1, 2, 3] [1, 2, 3, 4] 解説 簡素なコードになっているが、これで新たな配列を出力することが出来る。 (文字列の場合も同様なので割愛する) 7. おわりに まだまだ学習漏れな部分があると思うので、見つけ次第どんどん追加していきたい。 また、propsメソッドを書こうかどうか悩んだが、これはReactやNext.jsといった ライブラリやフレームワークで使われるものなので今回は書かなかった。 (学習にはまだ早すぎる…)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQuery Treeview を使ってサーバ側のツリー構造データをブラウザで表示・検索するプロトを爆速で実装した話

はじめに まさに掲題の必要性に迫られて調べた時のメモを整理した記事。 もちろんツリー表示を自前で実装する気は1ミリもなく、使えそうなJavaScriptライブラリを色々と調べた結果、少々古いもののシンプルさと実績を考慮しjQuery Treeview に決定した。 さらにjQuery Treeviewの実装例を調べた所、HTML内の静的なデータをツリー表示させる例が多く、サーバからダイナミックにデータを取得して表示する例が少なかったが、rails と jquery treeview でディレクトリツリーをらくらく実装する の記事を参考に、無事実現したいことをができたため、本記事でノウハウを共有したい。 環境 今回作成するプロトタイプの環境は以下の通りである。サーバ側はもちろん我らがPythonだ。 クライアント側 jQuery 1.2.6 jQuery Treeview (https://github.com/jzaefferer/jquery-treeview) サーバ側 Python 3系 flask 2.0 ライブラリのインストール手順 jQueryのインストール jQuery 1.2.6については以下のDownloadingのリンクを適当なフォルダに保存すればよい https://blog.jquery.com/2008/05/24/jquery-1-2-6-released/ jQuery Treeviewのインストール jQueryViewは、以下のGitHubのファイルを一式ダウンロードして適当なフォルダに保存すればよい。 https://github.com/jzaefferer/jquery-treeview 今回作成するプロトタイプの仕様 今回TreeViewで作るプロトタイプを説明する。 表示対象のデータはツリー構造であれば何でも良かったのだが、今回はサーバ側のある特定のディレクトリ配下のファイルやディレクトリの構造を表示、検索するシステムとした。 図で表すとこんなイメージだ。 詳しい仕様は次の通りである。 フォルダの場合、+もしくは-ボタンにより、ツリーを開いたり閉じたりすることができる。 初回表示時は、対象フォルダの配下の2階層目までのデータを取り込み、ツリーが展開された状態で表示する。3階層以降のデータは+がクリックされたタイミングでネットワークからダイナミックにデータを取得して表示するものとする。 キーワードを入れて検索ボタンをクリックすると、そのキーワードに合致するノードを含むツリーのみ表示する。ただし、中間のディレクトリがマッチした場合は、その先のノードは表示しない。 やや複雑ではあるが、ツリーの表示のさせ方のバリエーションを検討するため、このような仕様とした。 jQuery TreeView の使い方 jQuery TreeViewの使い方を解説する。 クライアントの実装方法 ヘッダではこんな感じでcssやjsをインクルードしておく <link rel="stylesheet" href="./treeview/jquery.treeview.css"> <script src="./jquery-1.2.6.js"></script> <script src="./treeview/jquery.treeview.js"></script> <script src="./treeview/jquery.treeview.edit.js"></script> <script src="./treeview/jquery.treeview.async.js"></script> また、TreeViewを表示したい位置に以下のようにタグを入れる。idにはhtml上で一意な値を設定する。 <ul id="treeview"></ul> そして、scriptタグの中で以下のようにTreeViewの設定を行う。 urlには、この後実装するサーバのURLを指定する。 $("#treeview").empty(); $("#treeview").unbind(); $("#treeview").treeview({ animated: 100, url: "http://127.0.0.1:8889/ }); 重要な点として、初期化時にはroot=source、ノードの+がクリックされた際にはroot=<id>というクエリがそれぞれURLの末尾に付加されてサーバに送られる。idとは後述するが、ノードを特定するための固有のidである。勘のいい人はお分かりかもしれないが、サーバ側はrootパラメータの値に応じて適切なデータを返すように実装しておけばよい。 サーバ側の実装方法 ではサーバ側の実装を解説しよう。 大きくはrootパラメータに応じて以下のようなデータを返す機能を実装する必要がある。 root=sourceの場合は、初回に表示させたいツリーデータをjson形式で返すようにする。 root=<id>の場合は、ノードid の配下に表示させたいツリーデータをjson形式で返すようにする。 では、json形式によるツリーデータの具体的書き方であるが、以下2つを抑えておくとよい。 各ノードはjsonオブジェクトで表す。 jsonオブジェクトの"chidren"プロパティに、jsonオブジェクトの配列(子供が複数の場合もあるため)を持たせることで、親子関係を表現する。 jsonオブジェクトとしては以下のプロパティを設定しておけばよい。 プロパティ 説明 id ノードを特定するid。クライアントがツリーのデータを動的に取得する場合に、このidをキーにするためサーバ側では、一意なidを生成して返す必要がある。今回のプロトでは各ディレクトリやファイルのパス(フルパス)とした。 text ツリーのノードに表示させる文字列を設定する。 classes cssのスタイルを指定する。TreeViewにスタイルを指定しない場合は意味がないかも。今回は既存の事例に従いフォルダにはfolder, 末端ノードの場合fileを指定した。(効いてないかも) hasChildren 子がいるかどうかをTrue/Falseで指定。これがTrueかどうかで展開用の+/-ボタンが表示されるかどうかが決まる。 children 子のjsonオブジェクトを配列で格納する。最初は子のidを指定するのかも?と悩んだが試行錯誤した結果この解に辿り着いた。 expanded ノードを最初から展開させておきたい場合、Trueを指定する。 ちなみに上のプロパティの説明はドキュメントには見当たらなかったため、自分で試行錯誤しながらまとめたものである(笑) 実装してみた 百聞は一見に如かず。実装してみよう。 サーバ側 こんな感じ。ディレクトリの中を探索する処理や、keywordオプションが指定された時に別の処理をやってたりするので、複雑にみえるが雰囲気は伝わると思う。 main.py import os import glob from flask import Flask, request import json # Flaskオブジェクトの生成 app = Flask(__name__) # CRLFのエラーが発生しないようヘッダを設定 @app.after_request def after_request(response): response.headers.add('Access-Control-Allow-Origin', '*') response.headers.add('Access-Control-Allow-Headers', 'X-Requested-With, Origin, X-Csrftoken, Content-Type, Accept') response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS') return response @app.route("/", methods=["GET"]) def get_dir_list(): req = request.args root = req.get("root") keyword = req.get("keyword") # デフォルトの階層を設定 if root == "source" or root is None: root = "c:/test/Music" array = [] if keyword == "" or keyword is None: get_dir_list_sub(root, array, 1, 2) else: get_dir_list_sub(root, array, 1, None, keyword) text = json.dumps(array) return text def get_dir_list_sub(path, array, currentDepth, viewDepth=None, keyword=None): paths = glob.glob(f"{path}/*") is_any = False for path in paths: name = os.path.split(path)[1] h = {} h["id"] = path h["text"] = name is_keyword_in_children = False if os.path.isdir(path): h["classes"] = "folder" if viewDepth is not None and (currentDepth +1 > viewDepth): # フォルダの配下が階層を超える場合、この時点までしか表示しないものとする。 # ただし、子は存在するため、+で展開時に子階層を検索させるべくhasChildrenをTrueにしておく h["hasChildren"] = True else: # フォルダの配下を返す場合 h["children"] = [] is_keyword_in_children = get_dir_list_sub(path, h["children"], currentDepth + 1, viewDepth, keyword) # 最初から展開させない場合は、以下をFalseにする h["expanded"] = True # ※注意 hasChildrenはここはTrueにしなくてよい。Trueにすると冗長な子階層が表示される。 else: h["classes"] = "file" h["hasChildren"] = False if keyword is None: array.append(h) is_any = True else: # キーワードが指定されている場合、自身または子がキーワードを含場合のみ格納 if keyword in name or is_keyword_in_children: array.append(h) is_any = True return is_any if __name__ == "__main__" : app.run(debug=True, port=8889) クライアント側 続いてクライアント側の処理である。たったこれだけで素晴らしいツリービューの完成だ。 恐るべし、jQuery Treeview。 treeview_search.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="./treeview/jquery.treeview.css"> <script src="./jquery-1.2.6.js"></script> <script src="./treeview/jquery.treeview.js"></script> <script src="./treeview/jquery.treeview.edit.js"></script> <script src="./treeview/jquery.treeview.async.js"></script> </head> <body> <h4>サーバと連動したツリービュー表示</h4> <input id="keyword" type="text" /> <input type="button" onClick="javascript:updateTree()" value="検索"/> <br/><br/> <ul id="treeview"></ul> </body> <script> window.onload = function() { updateTree(); }; function updateTree(){ var keyword = document.getElementById("keyword").value; $("#treeview").empty(); $("#treeview").unbind(); $("#treeview").treeview({ animated: 100, url: "http://127.0.0.1:8889/?keyword=" + keyword }); } </script> </html> 使い方 以下のようにサーバを起動する。その後ブラウザでtreeview_search.htmlを直接表示すればよい。 python main.py おまけ ノードにリンクをつける ノードにリンクを張りたいときは、サーバ側でtextプロパティにaタグを埋め込んで返せばよい。 例えばこんな感じ。 h["text"] = "<a href=\"javascript:alert('"+name +"')\">"+name+"</a>" おわりに 思った通りのことができそうで、ホッとしている。 新しいライブラリを使えばもっと素敵なことができるかもしれないが.. 参考文献 https://github.com/jzaefferer/jquery-treeview http://www.ne.jp/asahi/hatakeyama/design/csslayout/ajaxtest/index13.html TreeViewコントロールを作成する[jquery.treeview.js] rails と jquery treeview でディレクトリツリーをらくらく実装する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptでもっと効率的に記述するfor文

for文の書き方3選! 扱うリスト const todos = [ { id: 1, title: 'by some strawberries', completed: true }, { id: 2, title: 'buy a fresh cream', completed: true }, { id: 3, title: 'make a birthday cake', completed: false, }, ] 出力したい結果 1 'by some strawberries' 2 'buy a fresh cream' 一般的なfor文 for(let i = 0; i < todos.length; i ++){ let todo = todos[i]; if(todo.completed == true){ console.log(todo.id, todo.title) } } 変数iの宣言/変数iの範囲/変数iの増加量とするごくごく一般的な、progateとかでよく扱われる方法。 inを使ったfor文 for(let i in todos){ todo = todos[i] if(todo.completed == true){ console.log(todo.id, todo.title) } } 宣言した変数iを扱うのではなく、リストのアイテムごとに割り振られた添字扱ってリストを扱う。 ofを使ったfor文 for(let v of todos){ if(v.completed == true){ console.log(v.id, v.title) } } ofを使うとリストの中身でリストを扱うことができる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Magenta.js の MusicRNN の basic_rnn/melody_rnn/chord_pitches_improv を試す(環境は p5.js Web Editor上)

以下の記事に書いた 「Magenta」の JavaScript 版である Magenta.js のお試しの話の続きです。 ●機械学習・AI で音を作れる「Magenta」の JavaScript 版(Magenta.js)を p5.js Web Editor上で動かしてみる - Qiita  https://qiita.com/youtoy/items/32eedd5c5c9280fe3f0f 上記の記事の中では、公式の手順を試す中で MusicRNN の「basic_rnn」を使う部分がありました。 それ以外に、「melody_rnn」や「chord_pitches_improv」といったものもあるようだったので、今回はそれらを試すプログラムを作っていきます。 なお、上記の画像の中で「drum_kit_rnn」というのがありますが、これは別の機械に試していければと思います。 音を作る p5.js Web Editor の下準備 前回の記事の手順と同じように、まずは p5.js Web Editor の index.html でライブラリを読み込みます(前回と同じ、以下を読み込み)。 <script src="https://cdn.jsdelivr.net/npm/@magenta/music@^1.0.0"></script> 次に、sketch.js を作っていきます。 JavaScript のプログラム 今回、入力として与えるデータを 2つ増やしてみました(「チューリップ」と「かえるの歌」を用意してみました)。 また preload() の部分で「basic_rnn/melody_rnn/chord_pitches_improv」の 3つをそれぞれ読み込んでいます。 本来は、もう少しきれいに書けそうな内容になってますが、とりあえず版ということで... const TWINKLE_TWINKLE = { notes: [ { pitch: 60, startTime: 0.0, endTime: 0.5 }, { pitch: 60, startTime: 0.5, endTime: 1.0 }, { pitch: 67, startTime: 1.0, endTime: 1.5 }, { pitch: 67, startTime: 1.5, endTime: 2.0 }, { pitch: 69, startTime: 2.0, endTime: 2.5 }, { pitch: 69, startTime: 2.5, endTime: 3.0 }, { pitch: 67, startTime: 3.0, endTime: 4.0 }, { pitch: 65, startTime: 4.0, endTime: 4.5 }, { pitch: 65, startTime: 4.5, endTime: 5.0 }, { pitch: 64, startTime: 5.0, endTime: 5.5 }, { pitch: 64, startTime: 5.5, endTime: 6.0 }, { pitch: 62, startTime: 6.0, endTime: 6.5 }, { pitch: 62, startTime: 6.5, endTime: 7.0 }, { pitch: 60, startTime: 7.0, endTime: 8.0 }, ], totalTime: 8, }; const TULIP = { notes: [ { pitch: 60, startTime: 0.0, endTime: 0.5 }, { pitch: 62, startTime: 0.5, endTime: 1.0 }, { pitch: 64, startTime: 1.0, endTime: 2.0 }, { pitch: 60, startTime: 2.0, endTime: 2.5 }, { pitch: 62, startTime: 2.5, endTime: 3.0 }, { pitch: 64, startTime: 3.0, endTime: 4.0 }, { pitch: 67, startTime: 4.0, endTime: 4.5 }, { pitch: 64, startTime: 4.5, endTime: 5.0 }, { pitch: 62, startTime: 5.0, endTime: 5.5 }, { pitch: 60, startTime: 5.5, endTime: 6.0 }, { pitch: 62, startTime: 6.0, endTime: 6.5 }, { pitch: 64, startTime: 6.5, endTime: 7.0 }, { pitch: 62, startTime: 7.0, endTime: 7.5 }, ], totalTime: 7.5, }; const FROG = { notes: [ { pitch: 60, startTime: 0.0, endTime: 0.5 }, { pitch: 62, startTime: 0.5, endTime: 1.0 }, { pitch: 64, startTime: 1.0, endTime: 1.5 }, { pitch: 65, startTime: 1.5, endTime: 2.0 }, { pitch: 64, startTime: 2.0, endTime: 2.5 }, { pitch: 62, startTime: 2.5, endTime: 3.0 }, { pitch: 60, startTime: 3.0, endTime: 4.0 }, ], totalTime: 4, }; let player; function preload() { music_rnn_melody_rnn = new mm.MusicRNN( "https://storage.googleapis.com/magentadata/js/checkpoints/music_rnn/melody_rnn" ); music_rnn_melody_rnn.initialize(); music_rnn_chord_pitches_improv = new mm.MusicRNN( "https://storage.googleapis.com/magentadata/js/checkpoints/music_rnn/chord_pitches_improv" ); music_rnn_chord_pitches_improv.initialize(); music_rnn_basic_rnn = new mm.MusicRNN( "https://storage.googleapis.com/magentadata/js/checkpoints/music_rnn/basic_rnn" ); music_rnn_basic_rnn.initialize(); } function setup() { createCanvas(400, 400); background(220); player = new mm.Player(); } function draw() {} function keyPressed() { if (!player.isPlaying()) { let rnn_steps, rnn_temperature; switch (key) { case "q": player.start(TWINKLE_TWINKLE); break; case "w": makeMusicBasic(TWINKLE_TWINKLE, 4, 16 * 2, 1.5); break; case "e": makeMusicBasic(TWINKLE_TWINKLE, 4, 16 * 8, 1.5); break; case "r": makeMusicMelody(TWINKLE_TWINKLE, 4, 16 * 2, 1.5); break; case "t": makeMusicChord(TWINKLE_TWINKLE, 4, 16 * 2, 1.5, ["G", "Em", "C", "D"]); break; case "a": player.start(TULIP); break; case "s": makeMusicBasic(TULIP, 4, 16 * 2, 1.5); break; case "z": player.start(FROG); break; case "x": makeMusicBasic(FROG, 4, 16 * 2, 1.5); break; } } else if (player.isPlaying()) { switch (key) { case " ": console.log("Stop"); player.stop(); break; } } } function makeMusicBasic(SONG, steps_per_quarter, rnn_steps, rnn_temperature) { const qns = mm.sequences.quantizeNoteSequence(SONG, steps_per_quarter); music_rnn_basic_rnn .continueSequence(qns, rnn_steps, rnn_temperature) .then((sample) => player.start(sample)); } function makeMusicMelody(SONG, steps_per_quarter, rnn_steps, rnn_temperature) { const qns = mm.sequences.quantizeNoteSequence(SONG, steps_per_quarter); music_rnn_melody_rnn .continueSequence(qns, rnn_steps, rnn_temperature) .then((sample) => player.start(sample)); } function makeMusicChord( SONG, steps_per_quarter, rnn_steps, rnn_temperature, chord_progression ) { const qns = mm.sequences.quantizeNoteSequence(SONG, steps_per_quarter); music_rnn_chord_pitches_improv .continueSequence(qns, rnn_steps, rnn_temperature, chord_progression) .then((sample) => { console.log(sample); player.start(sample); }); } 上記を実行して、あとは特定のキーを押下していけば、以下の内容を試せます。 入力をそのまま実行 きらきら星、チューリップ、かえるの歌 の3つ 音を生成1: basic_rnn 3つの入力のそれぞれを、32ステップで指定(他のパラメータは前回の記事と同様) きらきら星について、128ステップで指定(他のパラメータは前回の記事と同様) 音を生成2・3: melody_rnn/chord_pitches_improv きらきら星について、32ステップで指定(他のパラメータは前回の記事と同様) 上記を試してみると、それぞれで異なる音が鳴るのが確認できました。 補足: chord_pitches_improv を使った場合 chord_pitches_improv を使った場合だけ、 continueSequence() でのパラメータ指定が他と異なります。 公式情報の continueSequence の部分で、以下の画像中に出てきている「chordProgression」の指定をしています。 これは、basic_rnn や melody_rnn を使った場合と同じ処理内容を、chord_pitches_improv を使った場合にも用いてみたところ、エラーメッセージで chordProgression に関するメッセージが出たためです。 今回のプログラム中では、公式情報の例に記載されている「["G", "Em", "C", "D"]」を指定してみました。 余談 上記の補足の話の中に出てくる「tonalパッケージ」というのも気になりました。 ●tonaljs/tonal: A functional music theory library for Javascript  https://github.com/tonaljs/tonal ドキュメントをざっくり見てみたところ、音系の処理を扱うのに便利そうでした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PIXI.js】Graphicsのtintで塗りつぶしを設定する方法

beginFillで塗りつぶしたPIXI.Graphicsの色をtintで指定しようとしたら、上書きされずに色がブレンドされたので、その解決策についてまとめる。 PIXI.Graphicsで作成したグラフィックシェイプを塗りつぶす方法はbeginFillとtintの2通りある。 beginFill・・・他のグラフィックスメソッド(lineToやdrawCircleなど)から​​endFillが呼び出されるまでの描画を単色で塗りつぶす const circle = new PIXI.Graphics() .beginFill(0xff00ff) .drawCircle(0, 0, 100) .endFill() tint・・・グラフィックシェイプの色合いを指定する circle.tint = 0xff00ff 後からグラフィックシェイプの色を変更したい場合は、tintで色合いを指定する方がおすすめです。 グラフィックシェイプの色合いがブレンドされる ただし、beginFillで塗りつぶした色をtintで上書き指定すると、色合いがブレンドされてしまう。 tintは「塗りつぶし」ではなくあくまで「色合い指定」のプロパティであるため。 例として、beginFillで青色に塗りつぶした色を2秒後にtintで緑色に指定する。 そうすると、グラフィックシェイプは緑ではなく、黒色に変色してしまう。 const circle = new PIXI.Graphics() .beginFill(0x0000ff) .drawCircle(0, 0, 100) .endFill() setTimeout(() => { circle.tint = 0x00ff00 }, 2000) beginFillでは0xffffffを指定する PIXI.Graphicsで作成するグラフィックシェイプのbeginFillは無条件で0xffffff(白)を使用し、塗りつぶしたい色はtintで指定する。 つまり、beginFillでは無彩色の白を指定し、その後の色指定は全てtintを使うことでブレンドを防ぐことができる。 以下のコードはbeginFillで0xffffff、tintで青色に塗りつぶし、2秒後にtintで緑色に指定している。 GIFを見ての通りブレンドされることなく、2秒後に緑色に変更されていることが分かる。 const circle = new PIXI.Graphics() .beginFill(0xffffff) .drawCircle(0, 0, 100) .endFill() circle.tint = 0x0000ff setTimeout(() => { circle.tint = 0x00ff00 }, 2000)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Chrome拡張でリクエストの簡易フィルタリング

Chrome拡張実装(リクエストのフィルタリング) 初めてChrome拡張を実装してみましたので、投稿してみます。 何か意見・指摘などあれば是非よろしくお願い致します。 目的 バックグラウンドページを使い、リクエストのフィルタリングをしてみたかった。 環境 Windows 10 Home Google Chrome 96.0.4664.45 javascript フォルダ構成 ├─manifest.json ├─jquery │ └─3.6.0 │ └─min.js ├─background.js ├─blacklist │ └─ block.js └─whitelist └─ pass.js 各ファイルの内容 *jQueryはもちろん除きます。 manifest.json { "name": "Sample Extension Ishy", "version": "1.0.0", "manifest_version": 2, "description": "Sample Chrome Extension", "permissions": [ "<all_urls>", "webRequest", "webRequestBlocking" ], "background": { "scripts": [ "whitelist/pass.js", "blacklist/block.js", "jquery/3.6.0/min.js", "background.js" ], "persistent": true } } background.js if(chrome.webRequest) { function translateToRegex(lists) { var regexes = new Array(); lists.forEach(function(map) { var obj = new Array(); obj.push(map[0]); obj.push(new RegExp(map[1].join("|"))); this.push(obj); }, regexes); return regexes; } function getWhiteLists() { var lists = [ [ "whites", whites ] ]; return translateToRegex(lists); } function getBlackLists() { var regexes = new Array(); var lists = [ [ "blacks", blacks ] ]; return translateToRegex(lists); } function getDetailMsg(details, msg) { var shortUrl = details.url.substring(0, 120); var now = new Date(); //var header = now.toISOString() + " " + details.initiator; var header = now.toTimeString().substring(0, 8) + " " + details.initiator; return header + " #" + msg + " " + shortUrl; } function applyFilterRegex(details, filters, msg) { for(var i=0; i<filters.length; i++) { var filter = filters[i]; if(filter[1].test(details.url)) { var detailMsg = getDetailMsg(details, msg + "(" + filter[0] + ")"); console.debug(detailMsg); return true; } } return false; } var whiteRegexes = getWhiteLists(); var blackRegexes = getBlackLists(); chrome.webRequest.onBeforeRequest.addListener( function(details) { var result = applyFilterRegex(details, whiteRegexes, "Approve"); if(result) { return { cancel: false }; } var result = applyFilterRegex(details, blackRegexes, "Block"); if(result) { return { cancel: true }; } var detailMsg = getDetailMsg(details, "Pass"); console.info(detailMsg); return { cancel: false }; }, { urls: ['<all_urls>'] }, ["blocking"] ); } else { console.error("webRequest : not active" ); } whitelist/pass.js var whites = [ 'youtube.com/', 'google.com/', 'google.co.jp/', 'googlevideo' ]; blacklist/block.js var blacks = [ 'twitter', 'facebook', 'fc2', 'ytimg' ]; コンソールの出力例 background.js:68 20:46:22 https://qiita.com #Pass https://qiita.com/api/preview background.js:68 20:46:23 https://qiita.com #Pass https://qiita.com/graphql background.js:47 20:47:00 undefined #Approve(whites) https://www.youtube.com/?gl=JP&tab=r11 background.js:47 20:47:00 https://www.youtube.com #Block(blacks) https://i.ytimg.com/generate_204 background.js:47 20:47:01 https://www.youtube.com #Approve(whites) https://www.youtube.com/getDatasyncIdsEndpoint 結果 まあまあ予定通りのものができました。 フィルタ用のキーワードはjavascriptの配列なので、コマンドで生成するのが楽でいいかもしれないです。 配列の配列でホワイトリスト/ブラックリストの正規表現を格納しているので、ログを採取してから優先順位をつければ応答も改善できそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactエンジニアがReact Nativeを使ってみた

はじめに もともとReactを使用してweb開発をしていましたが、React Nativeを使用したモバイル開発に関わることになりました。Reactを使っていた人間がReact Nativeを勉強してみた所感を書きたいと思います。本記事はReact Native公式ドキュメント[1]を参考に書いていきます。 前提 Reactが使える TypeScriptが使える モバイルは初めて 環境構築と簡単な動作確認が知りたい人は前回の記事[1]で行っているのでそちらを参照してください。 React Nativeの必要性 現在モバイル端末での使用OSはAndroidOSとiOSで二極化しており、なおかつそれぞれでの使用プログラミング言語も異なります。昔はiOSではObjective-C、AndroidではJavaが使用されていました。しかし、最近ではiOSではSwift、AndroidではKotlinという言語が使われており、2つのOS合わせると最大4つのコードが存在する可能性があるわけです。もちろんこれは開発効率の観点から見て良くなく、これを解決するためにReact Nativeなどのクロスプラットフォームで開発出来るフレームワークが開発されてきたわけです。 React Nativeでないとだめなのか 近年よく比べられるフレームワークとしてFlutterがあります。よくある議論として「Flutter vs React Native」のようなものがありますが、開発コストという点で見ればReact Nativeの方がFlutterよりも明らかに効率的です。FlutterではDartという言語を新しく覚える必要があり、学習コストは高いです。それに対してReact NativeはReactの知識を利用して開発出来るので、学習コストは比較的低いです。パフォーマンス的にはFlutterの方が良いのかもしれませんが、そこまでパフォーマンスを求めないのであれば手軽に作成できるReact Nativeの方が良いでしょう。ただそのまま使えるわけではなく、多少は覚えることがあるので注意です。 ここからはReact Nativeの概念について話していきたいと思います。 React Nativeのコンポーネント React Native公式ではコンポーネントを何種類かに分けて呼んでいます。 Native Components それぞれのOSでのViewを担当する部分がバックアップしているコンポーネント。 Core Components React Nativeですぐに使えるNative Componentsのセットコンポーネント。 community-contributed components React Nativeコミュニティが開発する各OS独自のNative Component。 各OSの特定の部分に対応するものがReact Nativeコンポーネントであり、それをまとめたものがCore Componentsであると。恐らく僕たちが使うのは基本的にCore Componentsになるということでしょう。それで足りない場合はcommunity-contributed componentsなどにないか探してみるというような感じなのでしょうか。 公式のベン図的な画像を載せておきます。 [1]より引用 あくまでReact Native ComponentsはReact Componentsの部分集合であるということなので、この図からもReact Componentsを理解していれば問題なさそうということが分かりますね。 React Nativeの文法 さてここからが本題です。ReactにはないReact Native特有の部分を見ていきたいと思います。 App.tsxを理解する まずはexpo initコマンドで作成されたテンプレートのApp.tsxの解読をしていきましょう。文章はHello,Worldに変更してあります。 App.tsx import { StatusBar } from "expo-status-bar"; import React from "react"; import { StyleSheet, Text, View } from "react-native"; export default function App() { return ( <View style={styles.container}> <Text>Hello,World</Text> <StatusBar style="auto" /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#fff", alignItems: "center", justifyContent: "center", }, }); まず、ReactをimportしているようですがこれはReactと違って必要でした。内部的に使っているんでしょうか。StatusBarというやつはなんだろうと調べると、モバイル上部に表示される電源残量や電波状況を表すアイコンが並んでいる部分をステータスバーというようです。ViewとTextはReact Nativeのコアコンポーネントで、詳しくは後ほど説明します。Viewに対してstyleを適用しているようですが、stylesというオブジェクトのプロパティとしてcontainerを設定し、スタイルを記述しています。スタイルについても後ほど詳しく記述しますが、ここではキャメルケースでの書き方でないといけないようで、Reactでのインラインスタイルによるスタイリングと同じですね。 ざっと見ていきましたが、大枠はReactと変わらないようなので、あとは各コンポーネントを覚えていくだけかなと思います。 コンポーネントのprops React Nativeのリファレンスを見ると、propsがiOSとAndroidOSで異なる物が多いようです。また、propsの数自体相当多く、これを全て覚えるのはかなり大変だと思います。この記事では各OS限定のpropsは基本的に扱わず、使いそうなpropsがあれば紹介するという感じで紹介していきますので、詳しく知りたい方は公式のリファレンス[3]を参考にしてください。 View React Nativeにおける一番基本的なコンポーネントです。divタグのようなものだと思って貰えばいいと思います(厳密には少し違う)。 SafeAreaView(iOS) iOSデバイスではノッチや角丸などの物理な違いによって安全にレンダリング出来る範囲が異なります。このコンポーネントでは安全な範囲内でコンテンツをレンダリングすることが出来ます。 Text 文字を書く場合は基本的にこれを使います。Viewに直接書いても反映されません。 ネスト出来る AndroidとiOSでは文字列の範囲に特定の書式をつけることが出来、この実現のためにTextはネストすることが出来ます。ネストしたテキストだけをスタイリングすることで、任意の範囲に書式をつけることが出来ます。 ・レイアウトが特殊 また、Textはレイアウトに関して特殊です。Test内の要素は長方形ではなくなる場合があり、行の終わりを見て折り返します。 ・スタイルの継承が制限されている テキストのスタイルはTextによってしか継承されません。つまりViewでテキストに関するスタイルをしても繁栄されません。特定のスタイルのTextを使いまわしたい場合は既存のTextを拡張したコンポーネントを定義することが公式では進められています。Text内では継承されるため、ネストしたTextでは親のスタイルが適用されます。 Image 画像を利用する際に使います。propsのsourceに参照を渡すことで画像が表示できます。下記に公式の例を載せます。 import React from 'react'; import { View, Image, StyleSheet } from 'react-native'; const styles = StyleSheet.create({ container: { paddingTop: 50, }, stretch: { width: 50, height: 200, resizeMode: 'stretch', }, }); const DisplayAnImageWithStyle = () => { return ( <View style={styles.container}> <Image style={styles.stretch} source={require('@expo/snack-static/react-native-logo.png')} /> </View> ); } export default DisplayAnImageWithStyle; [3]より引用。 TextInput 入力で使用します。全部説明すると長くなるので例を下記に示します。 import React from "react"; import { SafeAreaView, StyleSheet, TextInput } from "react-native"; const UselessTextInput = () => { const [text, onChangeText] = React.useState("Useless Text"); const [number, onChangeNumber] = React.useState(null); return ( <SafeAreaView> <TextInput style={styles.input} onChangeText={onChangeText} value={text} /> <TextInput style={styles.input} onChangeText={onChangeNumber} value={number} placeholder="useless placeholder" keyboardType="numeric" /> </SafeAreaView> ); }; const styles = StyleSheet.create({ input: { height: 40, margin: 12, borderWidth: 1, padding: 10, }, }); export default UselessTextInput; [3]より引用 一番ある使い方はonChangeTextにstateのセッターを入れておいて、入力を反映しつつstateで保持みたいな感じだと思います。気になるのはTypeScriptで使う際にnumberとstringどうやって分けているんだろうというとこですね。時間のある時に検証したら更新します。 ・下にボーダーがある TextInputはデフォルトでビューの下部にボーダーを持ちます。これはシステムが提供する背景画像によってパディングが設定されており、変更することは出来ません。この問題を回避するには、高さを明示的に設定しないようにして、システムが適切な位置にボーダーを表示するようにするか、underlineColorAndroidをtransparentに設定してボーダーを表示しないようにすることが必要なようです。詳しくは公式リファレンスを見てください。 ScrollView リファレンスを直訳したんですが、少し意味がわからないところがあったので分かりにくいかもしれません。 スクロールしたい場合に使います。React NativeではViewを使って表示する場合、表示画面領域より大きいものをレンダリングしようとしても自動的にスクロールするようにはならないようです。スクロールしないことは少ないと思うので基本的にはこれを使うことになるんでしょうか。 ・全ての親Viewが高さ制限されている必要がある リファレンスではboundedと書かれていたのですが、この訳で合っているのかあやしいです。高さを制限する方法は2つありViewの高さを直接設定すること(非推奨)と全ての親Viewに高さを設定することです。{flex:1}を忘れるとエラーが出るとリファレンスには書いてあるのですが、よく分かりません。下記に例を載せておきます。 import React from 'react'; import { StyleSheet, Text, SafeAreaView, ScrollView, StatusBar } from 'react-native'; const App = () => { return ( <SafeAreaView style={styles.container}> <ScrollView style={styles.scrollView}> <Text style={styles.text}> Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. </Text> </ScrollView> </SafeAreaView> ); } const styles = StyleSheet.create({ container: { flex: 1, paddingTop: StatusBar.currentHeight, }, scrollView: { backgroundColor: 'pink', marginHorizontal: 20, }, text: { fontSize: 42, }, }); export default App [3]より引用。 showsVerticalScrollIndicator このpropsをfalseにすることでスクロールバーを表示しないように出来ます。 horizontal このpropsをtrueにすることで横スクロールにも対応できます。 FlatlListとの違い ScrollViewでは全ての子コンポーネントを一度にレンダリングするため、パフォーマンス上の問題があります。FlatListではアイテムが表示されようとしているときに遅延的にレンダリングし、画面外に大きくスクロールするアイテムを削除することでメモリや処理時間の節約が出来ます。FlatListはほかにもアイテム間の距離、複数列、無限スクロールローディングなどの機能をサポートしているので便利です。 ここまで書いていて思いましたが、もしかして割とScrollViewって要らない子?状況に応じて使い分けるんですかね。 StyleSheet ここまででも使用してきていますが、cssのスタイルを使用するために利用しています。特に言うことはないのですが、React NativeはTailwind CSSとは相性が良くなさそうだなーと思います。調べた感じライブラリなどを使えばいけるようですが、vscodeのインテリセンスなどの関係上どうなのかなーという感じです。 逆にCSS in JSは特に変化なく使えそうだなと思います。webとほとんど変わらず記述出来るので、開発コストを削減するという観点から見たら、React NativeではCSS in JSを用いるのがいいんじゃないかなと思います。僕はstyled-componentsを用いて書いているので苦労することはなさそうです。 Button ボタンに相当するコンポーネントです。見たほうが早いと思うので下記に例を載せます。 <Button title="Press me" disabled onPress={() => Alert.alert('Cannot press this one')} /> [3]より引用。 propsとしてonPressとtitleは必須のようです。titleでボタンの表示を決めるようですが、childrenでは無理ということでしょうか。Alert.alertでwindow.alertのようなことが出来るようです。またこの例ではdisabledにしているので利用できないような見た目になります。動的に決定したい場合はdisabledのところにboolean型の変数を入れれば可能でしょう。 Switch このSwitchはswitch文のことではなく、切り替えるためのボタンのことです(名前あるのかな)。リファレンスのダークモード切り替えの部分によくあるやつです。下記に例を示します。 <Switch trackColor={{ false: "#767577", true: "#81b0ff" }} thumbColor={isEnabled ? "#f5dd4b" : "#f4f3f4"} ios_backgroundColor="#3e3e3e" onValueChange={toggleSwitch} value={isEnabled} /> [3]より引用 タップすることでboolean型変数がトグルし、valueの値によって色々変わるという感じです。 TouchableOpacity 押されるとラップされたViewの不透明度が減少し、薄暗くなります。 <TouchableOpacity style={styles.button} onPress={onPress} > <Text>Press Here</Text> </TouchableOpacity> [3]より引用 FlatList 先程も説明しましたが、リスト表示をする際に利用します。 data 表示したい配列データ。 renderItem dataを使ってどのようなものを表示するかを決める関数 extraData 追加データ。下の例では再レンダリングするためにstateを渡しています。 keyExtractor デフォルトのkeyプロパティの代わりに使用するidの指定。 ・コンテンツがレンダリング領域の外にいったら内部状態は保持されない FlatListの仕様上レンダリングの外のコンテンツの内部状態は保存されません。そのためstateの更新時とレンダリングについて気をつける必要があります ・コンテンツは画面外で非同期にレンダリングされる この特徴によりコンテンツ外のレンダリングよりも早くスクロールすると、一時的に空白のコンテンツが表示される可能性があります。 import React, { useState } from "react"; import { FlatList, SafeAreaView, StatusBar, StyleSheet, Text, TouchableOpacity } from "react-native"; const DATA = [ { id: "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba", title: "First Item", }, { id: "3ac68afc-c605-48d3-a4f8-fbd91aa97f63", title: "Second Item", }, { id: "58694a0f-3da1-471f-bd96-145571e29d72", title: "Third Item", }, ]; const Item = ({ item, onPress, backgroundColor, textColor }) => ( <TouchableOpacity onPress={onPress} style={[styles.item, backgroundColor]}> <Text style={[styles.title, textColor]}>{item.title}</Text> </TouchableOpacity> ); const App = () => { const [selectedId, setSelectedId] = useState(null); const renderItem = ({ item }) => { const backgroundColor = item.id === selectedId ? "#6e3b6e" : "#f9c2ff"; const color = item.id === selectedId ? 'white' : 'black'; return ( <Item item={item} onPress={() => setSelectedId(item.id)} backgroundColor={{ backgroundColor }} textColor={{ color }} /> ); }; return ( <SafeAreaView style={styles.container}> <FlatList data={DATA} renderItem={renderItem} keyExtractor={(item) => item.id} extraData={selectedId} /> </SafeAreaView> ); }; ・・・ [3]より引用 map関数によるレンダリングの代わりなのかなーとおもっています。 Section List FLatListに見出しがついたような感じのものです。ほとんど同じなので使い方は下のサンプルプログラムを見てください。 import React from "react"; import { StyleSheet, Text, View, SafeAreaView, SectionList, StatusBar } from "react-native"; const DATA = [ { title: "Main dishes", data: ["Pizza", "Burger", "Risotto"] }, { title: "Sides", data: ["French Fries", "Onion Rings", "Fried Shrimps"] }, { title: "Drinks", data: ["Water", "Coke", "Beer"] }, { title: "Desserts", data: ["Cheese Cake", "Ice Cream"] } ]; const Item = ({ title }) => ( <View style={styles.item}> <Text style={styles.title}>{title}</Text> </View> ); const App = () => ( <SafeAreaView style={styles.container}> <SectionList sections={DATA} keyExtractor={(item, index) => item + index} renderItem={({ item }) => <Item title={item} />} renderSectionHeader={({ section: { title } }) => ( <Text style={styles.header}>{title}</Text> )} /> </SafeAreaView> ); ・・・ [3]より引用。 おわりに ざっと概念と文法を見てきましたが、理解すれば簡単に使えそうです。ただ、想像していたよりは覚えることがあったのであとは実際に開発してから使用感を確かめていきたいと思います。 参考文献 [1]:Introduction [2]:Ubuntu20.04LTSでReact NativeとExpoを試す [3]:Core Components and APIs
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

機械学習・AI で音を作れる「Magenta」の JavaScript 版(Magenta.js)を p5.js Web Editor上で動かしてみる

公式ページで「Make music and art using machine learning」と書かれているように、機械学習で音楽・アートを作成できる Magenta の話です。 今どきの言い方だと、「AIで作曲」といった表現がされそうな所でしょうか。 その Magenta の JavaScript版(Magenta.js)を p5.js Web Editor上でまずは軽く動かしてみる、という話です。 Magenta の JavaScript版について 公式ページ Magenta は、Googleさんが提供している仕組みで、公式ページ等は以下があります。 ●Magenta  https://magenta.tensorflow.org/ ●Magenta – Google Research  https://research.google/teams/brain/magenta/ Python版と JavaScript版 上記の 1つ目のページを少し下に移動していくと、以下のような部分があります。 利用可能な言語として、Python と JavaScript があり、JavaScript版のほうは「Magenta.js」というライブラリになるようです。 上記の部分の Magenta.js のほうで、「Try it live」を選んで進めていきます。 JavaScript版を試す そうすると、以下のページに遷移します。 ●Making music with magenta.js  https://hello-magenta.glitch.me/ この中の、以下の内容を p5.js Web Editor上で使えるようにしていきます。 Step 1: Making sounds with your browser Playing a NoteSequence Step 2: Using Machine Learning to make music MusicRNN p5.js Web Editor での下準備 まずは、p5.js Web Editor の index.html でライブラリを読み込みます。 「Step 0: First things first!」という部分を見ると、以下を読み込むようにと書いてあるので、その通り進めます。 <script src="https://cdn.jsdelivr.net/npm/@magenta/music@^1.0.0"></script> 次に、sketch.js を作っていきます。 プログラムを書いていく 上で「今回試す部分」と書いていたところを、1つのプログラムでまとめて動かせるようにします。 具体的には、以下のとおりです。 // ●Making music with magenta.js https: hello-magenta.glitch.me/ const TWINKLE_TWINKLE = { notes: [ { pitch: 60, startTime: 0.0, endTime: 0.5 }, { pitch: 60, startTime: 0.5, endTime: 1.0 }, { pitch: 67, startTime: 1.0, endTime: 1.5 }, { pitch: 67, startTime: 1.5, endTime: 2.0 }, { pitch: 69, startTime: 2.0, endTime: 2.5 }, { pitch: 69, startTime: 2.5, endTime: 3.0 }, { pitch: 67, startTime: 3.0, endTime: 4.0 }, { pitch: 65, startTime: 4.0, endTime: 4.5 }, { pitch: 65, startTime: 4.5, endTime: 5.0 }, { pitch: 64, startTime: 5.0, endTime: 5.5 }, { pitch: 64, startTime: 5.5, endTime: 6.0 }, { pitch: 62, startTime: 6.0, endTime: 6.5 }, { pitch: 62, startTime: 6.5, endTime: 7.0 }, { pitch: 60, startTime: 7.0, endTime: 8.0 }, ], totalTime: 8, }; let player, player2; function preload() { player1 = new mm.Player(); player2 = new mm.SoundFontPlayer( "https://storage.googleapis.com/magentadata/js/soundfonts/sgm_plus" ); music_rnn = new mm.MusicRNN( "https://storage.googleapis.com/magentadata/js/checkpoints/music_rnn/basic_rnn" ); music_rnn.initialize(); } function setup() { createCanvas(400, 400); background(220); } function draw() {} function keyPressed() { if (!player1.isPlaying() && !player2.isPlaying()) { switch (key) { case "a": player1.start(TWINKLE_TWINKLE); break; case "s": player1.setTempo(200); player1.start(TWINKLE_TWINKLE); break; case "d": player2.start(TWINKLE_TWINKLE); break; case "z": const rnn_steps = 20; const rnn_temperature = 1.5; const qns = mm.sequences.quantizeNoteSequence(TWINKLE_TWINKLE, 4); music_rnn .continueSequence(qns, rnn_steps, rnn_temperature) .then((sample) => player1.start(sample)); break; } } } player2 と music_rnn は、外部からファイルを読み込む処理になるようなので、「preload()」の中に入れることにしました。 player も「preload()」の中に入っているのは、それらと一緒の場所に書いた方が分かりやすいというだけで、「setup()」の中で読み込んでも問題はないです。 それと player 、 player2 は同時には使えないようだったので(音を出す仕組みのところで何やら競合するような感じらしい?)、どちらか 1つで音を鳴らしている途中は、別の音を鳴らす処理を実行させないようにしました。 あとは、プログラムを実行して特定のキーを押下するだけです。 試してみると、きらきら星が流れたり、それをベースに生成された音が流れたりするのが確認できました。 補足1: 音を作る部分の補足 きらきら星のメロディをベースに音を作る部分は、「MusicRNN」を使っています。 公式手順の中で、以下のように説明されているものです。 ディープラーニングの話なんかで、時系列データ解析等の話で良く出てくる LSTM(Long short-term memory)をベースにした機械学習モデルになるようです。 補足2: quantizeNoteSequence について MusicRNN の処理の関連で出てくる「quantizeNoteSequence」が気になったので、公式情報を見てみました。 以下は、「"core/sequences"」の中の「quantizeNoteSequence」について書かれた部分のキャプチャです。 今回の記事の中で試した処理の前後の内容を出力すると、以下のようになりました(一部のみ抜粋)。 > 入力 > 出力 こちらについて MusicRNN の出力にどういう影響があるのかは、まだ調査中です。 【追記】 basic_rnn 以外も試してみました ●Magenta.js の MusicRNN の basic_rnn/melody_rnn/chord_pitches_improv を試す(環境は p5.js Web Editor上) - Qiita  https://qiita.com/youtoy/items/e3b3c26ea32dba5e8624
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】非同期処理⑦ AwaitとAsync

はじめに Udemyの【JS】ガチで学びたい人のためのJavaScriptメカニズムの講座の振り返りです。 前回の記事 目的 非同期処理についての理解を深める 本題 1.AwaitとAsync Promiseをさらに直感的に記述できるようにしたもの Async Promiseを返却する関数の宣言を行う Await Promiseが返却する関数の非同期処理が完了するまで待つ 例1 前提 function sleep(val) { return new Promise(function(resolve) { setTimeout(function() { console.log(val++); resolve(val); }, 1000); }); } sleep(0).then(function(val) { return sleep(val); }).then(function(val) { return sleep(val); }).then(function(val) { return sleep(val); }).then(function(val) { return sleep(val); }).then(function(val) { return sleep(val); }) 上記のPromiseで書かれたコードをAwaitとAsyncで書き換える //基本的な書き方 function sleep(val) { return new Promise(function(resolve) { setTimeout(function() { console.log(val++); resolve(val); }, 1000); }); } // 先頭にasyncとつけた関数を用意 async function init(){ // 中でawaitを使う await sleep(0); } // initを実行すると0と出力される init(); function sleep(val) { return new Promise(function(resolve) { setTimeout(function() { console.log(val++); resolve(val); }, 1000); }); } async function init(){ // 変数に格納してみる let val = await sleep(0); // valには1が格納されている // Promise構文のresolveのvalが渡されている console.log(val); } init(); 例2 awaitを使ってチェーンを作る function sleep(val) { return new Promise(function(resolve) { setTimeout(function() { console.log(val++); resolve(val); }, 1000); }); } async function init(){ let val = await sleep(0); // 上記変数式をコピーし、sleepの引数をvalとする val = await sleep(val); val = await sleep(val); val = await sleep(val); val = await sleep(val); // 1,2,3...と出力されるようになる console.log(val); } init(); // 関数ないでawaitを使用している場合が必ずasyncセット(非同期を照明するため) // asyncは関数コンテキストのみで使える 例3 thenメソッド、catchメソッドを使用することも可能 function sleep(val) { return new Promise(function(resolve) { setTimeout(function() { console.log(val++); resolve(val); }, 1000); }); } async function init() { let val = await sleep(0); val = await sleep(val); val = await sleep(val); val = await sleep(val); val = await sleep(val); console.log(val); } init().then(function(){ // このようにすると上記1から5の出力を待ってからhelloと出力される console.log("hello "); }) function sleep(val) { return new Promise(function(resolve) { setTimeout(function() { console.log(val++); resolve(val); }, 1000); }); } async function init() { let val = await sleep(0); val = await sleep(val); val = await sleep(val); val = await sleep(val); val = await sleep(val); // ここでreturnを宣言すると return val } // ここの引数でvalが使える init().then(function(val){ // そうすると5番目の出力が"hello 5"となる console.log("hello " + val); }) function sleep(val) { return new Promise(function(resolve) { setTimeout(function() { console.log(val++); resolve(val); }, 1000); }); } async function init() { let val = await sleep(0); val = await sleep(val); val = await sleep(val); val = await sleep(val); val = await sleep(val); // ここでthrowを宣言すると throw new Error(); return val } init().then(function(val){ console.log("hello " + val); // catchに処理が移る }).catch(function(e){ // 5の個所でエラー表示がされる console.error(e); }) // 基本的には、Promiseと挙動が同じ 今日はここまで! 参考にさせて頂いた記事 【JS】ガチで学びたい人のためのJavaScriptメカニズム Let'sプログラミング JavaScript入門
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ESP32をJavascriptでLチカする:HTTP でJsonをPostする

前回の投稿「ESP32をJavascriptでLチカする」に引き続き、ESP32でJavascriptを動かします。 今回は、HTTPでJsonをPOSTします。 ESP32に以下の人感センサ PIR Hatを接続して、検出したらBeebotteにアップします。 PIR Hat Beebotte ※Beebotteは、HTTPやMQTTでデータを集約して、グラフ化してくれるサービスです。 ソースコードもろもろは、前回同様以下に上げてあります。 随時、修正、拡張しています。 poruruba/QuickJS_ESP32 HTTP通信 2つの通信方法を用意しました。 ①HTTPSで中継サーバを介してPost/Get接続 ESP32でHTTPS通信するためには少々骨が折れるため、Node.jsを使った中継サーバをイントラネットに配置し、中継サーバまではHTTP、中継サーバから先をHTTPS通信するようにします。 httpモジュールとして用意しています。 import * as http from "http"; 中継サーバは、GitHubからダウンロードしたファイルのnode.jsフォルダを立ち上げます。 また、main.cppの以下の部分を、立ち上げた中継サーバのURLに変更します。 const char *HTTPPROXY_URL = "http://【中継サーバのURL】/httpproxy-call"; ちなみに、中継サーバは以下のような実装になってます。 node.js/api/controllers/http-proxy/index.js 'use strict'; const HELPER_BASE = process.env.HELPER_BASE || '../../helpers/'; const Response = require(HELPER_BASE + 'response'); const TextResponse = require(HELPER_BASE + 'textresponse'); const BinResponse = require(HELPER_BASE + 'binresponse'); const { URLSearchParams } = require('url'); const fetch = require('node-fetch'); const Headers = fetch.Headers; exports.handler = async (event, context, callback) => { switch(event.path){ case '/httpproxy-call':{ var body = JSON.parse(event.body); console.log(body.method, body.url, body.params, body.headers, body.response_type, body.method); var result; switch(body.method){ case 'POST':{ result = await do_post(body.url, body.params, body.headers, body.response_type); break; } case 'GET':{ result = await do_get(body.url, body.params, body.headers, body.response_type); break; } default: throw "unknown method"; } if (body.response_type == 'TEXT') return new TextResponse("text/plain", result); else if (body.response_type == 'BINARY') return new BinResponse("application/octet-stream", Buffer.from(result)); else return new Response(result); } } }; function do_post(url, body, header, response_type = 'JSON') { const headers = new Headers(header); headers.append("Content-Type", "application/json"); console.log(headers); console.log(body); console.log(url); return fetch(url, { method: 'POST', body: JSON.stringify(body), headers: headers }) .then((response) => { if (!response.ok) throw 'status is not 200'; if (response_type == 'TEXT') return response.text(); else if (response_type == 'BINARY') return response.arrayBuffer(); else return response.json(); }); } function do_get(url, qs, header, response_type = 'JSON') { const params = new URLSearchParams(qs); const headers = new Headers(header); var append_params = params.toString(); if (append_params ){ url += (url.indexOf('?') >= 0) ? "&" : "?"; url += append_params; } console.log(url); return fetch(url, { method: 'GET', headers: headers }) .then((response) => { if (!response.ok) throw 'status is not 200'; if (response_type == 'TEXT') return response.text(); else if (response_type == 'BINARY') return response.arrayBuffer(); else return response.json(); }); } ②HTTPで直接Post/Get接続 接続先サーバが、HTTPであれば、直接接続できるようにしています。 utilsモジュールに用意しています。 import * as utils from "utils"; ①、②いずれも、パラメータの指定方法は同じです。 Post接続、JSON戻り http.postJson(url, params, headers); utils.httpPostJson(url, params, headers); ・url:接続先URL ・params(option):bodyにJSONとして渡す引数 ・headers(options):headerに指定する引数 例 var response = http.postJson("http://api.beebotte.com/v1/data/write/pir", { records: [{ "resource": "pir", data: (pir != 0) }]}, { "X-Auth-Token": X_AUTH_TOKEN } ) Post接続、TEXT戻り http.postText(url, params, headers); utils.httpPostText(url, params, headers); Get接続、JSON戻り http.getJson(url, params, headers); ・url:接続先URL ・params(option):queryStringに指定する引数 ・headers(options):headerに指定する引数 Get接続、TEXT戻り http.getText(url, params, headers); utils.httpGetText(url, params, headers); ほかに有用なAPIとして以下があります。 ・utils.urlencode(str) ・utils.base64Encode(str) ・utils.base64Decode(b64) また、当然以下もあります。 ・JSON.parse(str) ・JSON.stringify(obj) Javascriptソース PIR Hatは、GPIO_36につながれています。 import * as http from "http"; import * as input from "input"; import * as utils from "utils"; import * as gpio from "gpio"; const X_AUTH_TOKEN = "【Beabotteチャネルトークン】 "; async function setup() { var ipaddress = esp32.getIpAddress(); console.log("ipaddress=" + ((ipaddress >> 24) & 0xff) + "." + ((ipaddress >> 16) & 0xff) + "." + ((ipaddress >> 8) & 0xff) + "." + (ipaddress & 0xff)); gpio.pinMode(36, gpio.INPUT); } var pir = -1; async function loop() { esp32.update(); if( input.isPressed(input.BUTTON_B) ){ esp32.restart(); } var t = gpio.digitalRead(36); if( t != pir ){ pir = t; console.log("PIR=" + pir); // var result2 = http.postJson("http://api.beebotte.com/v1/data/write/pir", { records: [{ "resource": "pir", data: (pir != 0) }]}, { "X-Auth-Token": X_AUTH_TOKEN } ); var result2 = utils.httpPostJson("http://api.beebotte.com/v1/data/write/pir", { records: [{ "resource": "pir", data: (pir != 0) }]}, { "X-Auth-Token": X_AUTH_TOKEN } ); console.log(JSON.stringify(result2)); } } 終わりに かなり形になってきました。 異常系を整備しようかな。。。 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm-scripts でExcelファイルからJSONファイルを生成する

HTML のコーディングに Pug を利用するようになってから、JSONデータを HTML に反映できてとっても便利になりました。 Excelファイルにデータを入力 マクロでJSONファイル生成 という流れでJSONファイルを生成していましたが、VBA(Visual Basic for Applications)がよくわかりません… そこで、JavaScript でどうにかならんものかと試行錯誤してみたら… Node.js でできちゃいました というわけで、ExcelファイルからJSONファイルを生成する方法を紹介します。 仕様 特定のディレクトリ内のすべてのExcelファイルを対象とする Excelファイルの1シート目のデータがJSONファイルとして生成される Excelファイルの1行目の値が各アイテムの key となる Excelファイルの2行目以降が各アイテムとなり、セルが value となる Excelファイルの拡張子は xls xlsx xlsm が対象 生成されるJSONファイル名はExcelファイル名と同じ Excelファイルのサンプル A B C A1 B1 C1 A2 B2 C2 A3 B3 C3 生成されるJSONファイル JSON [ { "A": "A1", "B": "B1", "C": "C1" }, { "A": "A2", "B": "B2", "C": "C2" }, { "A": "A3", "B": "B3", "C": "C3" } ] ファイル構造 dataフォルダ内のExcelファイルがjsonフォルダに格納されます。 root ├── data/ │ ├── hoge.xls │ ├── fuga.xls │ └── piyo.xls ├── json/ │ ├── hoge.json │ ├── fuga.json │ └── piyo.json ├── npm-scripts/ │ └── excel-to-json.js └── package.json 準備 package.json の作成 package.json をルート直下に作成します。 scripts フィールドに json という名前で excel-to-json.js を実行するnpmスクリプトを登録。 package.json { "name": "excel-to-json", "version": "1.0.0", "private": true, "scripts": { "json": "node ./npm-scripts/excel-to-json.js" } } node_module のインストール 必要な node_module は以下となります。 モジュール名 役割 ansi-colors ログメッセージに色をつける fs ファイルを操作する glob パターンにマッチしたパスを見つける xlsx Excelファイルを操作する npm i ansi-colors fs glob xlsx -D JavaScript の作成 excel-to-json.js const c = require('ansi-colors') const fs = require('fs') const glob = require('glob') const XLSX = require('xlsx') class ExcelToJson { constructor() { this.pattern = 'data/!(~$)*.+(xls|xlsx|xlsm)' this.dest = 'json/' } init() { glob(this.pattern, (err, files) => { if (err) { console.error(err) return } for (const file of files) { const fileName = file.match(/([^/]*)\./)[1] const data = this.getExcelData(file, fileName) this.writeJsonFile(data, fileName) } }) } // エクセルファイルの1シート目のデータを取得 getExcelData(excelFilePath, excelFileName) { const workbook = XLSX.readFile(excelFilePath) const Sheet1 = workbook.Sheets[workbook.SheetNames[0]] return XLSX.utils.sheet_to_json(Sheet1) } // Jsonファイルの書き込み writeJsonFile(data, excelFileName) { const jsonStr = JSON.stringify(data) fs.mkdir(this.dest, { recursive: true }, (err) => { if (err) { console.error( c.red('ERR! ') + `Failed to create directory '${this.dest}' :${err}` ) return } fs.writeFile(`${this.dest}${excelFileName}.json`, jsonStr, (err) => { if (err) { console.error( c.red('ERR! ') + `Failed to create '${this.dest}${excelFileName}.json':${err}` ) return } console.log( `Created '` + c.green(`${this.dest}${excelFileName}.json`) + `'` ) }) }) } } const excelToJson = new ExcelToJson() excelToJson.init() npm-scripts の実行 以下コマンドを入力するとJSONファイルが生成されます npm run json
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript関連で出たエラーのメモ。

Vue.jsの勉強がてらのアプリ製作中に出たエラーを備忘録としてここにまとめます。エラー内容は多種多様。同じエラーが出た方がいれば参考にしてみてね(^_-)-☆ 随時更新します。 1. Chart.jsを使っていた時。 『 “export ‘default’ (imported as ‘Chart’) was not found in ‘chart.js’』というエラーが出た。 Chart.jsのバージョンが原因。最新のものだとうまく動かないとのこと。 ↓↓のこちら記事で解説あり。 なので、インストールし直せばOK。 $ npm uninstall vue-chartjs $ npm uninstall chart.js $ npm install chart.js@2.8 $ npm install vue-chartjs@3.4.2 2. forEachを使おうとした時。 『TypeError: _this.data.forEach is not a function』というエラーが出た。 これはajaxを使っている時だったが、戻ってくるデータが配列になっていなかったのが原因。 ↓↓のようなコントローラーで、(バックエンドLaravel使ってました、)『return[]』とするとオブジェクトで返ってきてしまうので、そこを『return response()->json($products);』と書き換えれば、配列で返ってきて問題解決した。 public function index() { $products = Product::orderByDesc('year')->get(); // return ['products' => $products]; return response()->json($products); }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsはシングルスレッド、排他は必要ない

※タイトルは釣りです。 この記事はNode.js初心者がasync/await実装をマルチスレッドと誤解して 混乱するのを解消するために書きました。 Node.jsのコードはシングルスレッドで動きます。 (厳密に言うとWorkerとかモジュール内部とかマルチスレッドな部分はありますが、ユーザーが普通に書くコードとしては) 結論 あなたがasyncと書いても、コードが2箇所で同時に実行されることはありません。 実行されるコードは常に1箇所のみです。 awaitと書くとコードの実行が止まって、他のコードに処理が移ることがあります。 awaitではないコードで、処理が他のコードに切り替わることはありません。 あなたが作りたいものによって、排他が必要になることがあります。 質問 外部から通信を受け付けるプログラムを書いています。 通信Aを処理している最中に、通信Bが届いたとき、何が起こりますか? 「通信Aの処理が終わるまで、通信Bの受信処理は実行されない」 「通信Aの処理の最中に、通信Bの受信処理が割り込む」 回答 (作り方によって)どっちもありうる awaitの処理が割り込まれない例 //時間がかかる処理 async function heavyOperation(){ console.log('heavyOperation'); let x = 0; for(let i=0;i<10000000000;i++){ x += i / 10000000000; } } async function callback(){ console.log('callback'); } async function exec(){ console.log('exec'); setTimeout(callback,1); const startTime = Date.now(); console.log('heavy operation started.'); await heavyOperation(); console.log('heavy operation finished.', Date.now() - startTime, 'ms'); } exec(); % node settimeout.js exec heavy operation started. heavyOperation heavy operation finished. 12254 ms callback heavyOperationの実行には12秒ほどかかっていますが、 setTimeout(1ミリ秒)によるcallback実行は後回しにされました。 関数callbackにasyncがついている/ついていないで違いはありません。 awaitの処理が割り込まれる例 function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function callback(){ console.log('callback'); } async function exec(){ console.log('exec'); setTimeout(callback,1); const startTime = Date.now(); console.log('sleep started.'); await sleep(3000); console.log('sleep finished.', Date.now() - startTime, 'ms'); } exec(); % node sleep.js exec sleep started. callback sleep finished. 3000 ms await sleep(1000ミリ秒)の最中にsetTimeoutによる関数callbackの呼び出しが行われています。 つまりawaitする関数の中身によって、他の処理に切り替わるかどうかが変わります。 処理が割り込むか、割り込まないか、どうやったらわかるの? const fs = require("fs"); async function readFile1(){ return (await fs.promises.readFile('./test.txt')).toString(); } async function readFile2(){ return fs.readFileSync('./test.txt').toString(); } 「よく考えればわかります。処理がイベントキューに積まれることを意識するべきだ!」 とか言っていると見落としがちです。わかりにくいんです。 この例では、readFile1は割り込みますが、readFile2は割り込みません。 readFile1ではawaitを書かないでPromiseのままreturnすることもできるので 中身でawaitを使っているかどうか、は判断として使えません。 fs.promises.readFileはわかりやすい名前ですが、関数名で見分けがつくとも限りません。 async/awaitで説明しましたが、コールバックでも似たような状況 (Concurrent safeではない)が作れます。 質問の例に戻ると 通信Aを処理している最中に、通信Bが届いたとき、何が起こりますか? 通信A = ファイルの書き込み 通信B = ファイルの送信 という処理をする場合 (そもそも仕様が悪いというのはスルーするとして) ファイルが存在しなくて送れない、ファイルが古いものを送ってしまう ということが起こってしまうということです。 関数が割り込みされるかどうか(他の処理が)、 つまりシングルスレッドでも、マルチスレッド程ではないとしても クリティカルセクション、排他を意識したコーディングが必要になるということです。 どうすればいいの? いろんなやり方があります。 通信Aでflag=true(完了時にflag=false)して、通信Bをflag===trueで失敗にする 通信Aでflag=true(完了時にflag=false)して、通信Bでflag===falseになるまで待つ 通信A、Bをキューイングして、キューから処理を実行する async-lockを使う 3について想像が難しい場合は、マルチスレッドパターンですが、Active Object、Proactor、Reactor、Worker threadとか調べるといい説明がでてくるかもしれません。 4のasync-lockはスター数253のライブラリで、Lockという名前がついていますがキューのような動作をしていて、lock.acquireはpromiseを返すので、async/awaitな実装と一緒に使いやすいです。 async-await const AsyncLock = require('async-lock'); const lock = new AsyncLock(); async function sleep(ms) { return lock.acquire('mylock', async ()=>{ await new Promise(resolve => setTimeout(()=>{ console.log('sleep resolved'); resolve(); }, ms)); }); } async function callback(){ return lock.acquire('mylock', async()=>{ console.log('callback'); }); } async function exec(){ console.log('exec'); setTimeout(callback,1); const startTime = Date.now(); console.log('sleep started.'); await sleep(3000); console.log('sleep finished.', Date.now() - startTime, 'ms'); } exec(); % node sleep2.js exec sleep started. sleep resolved callback sleep finished. 3001 ms 'callback'はおおよそ3秒後に表示されます。 sleep関数のlockを抜けた直後にcallback関数のlock内処理が実行されます。 sleep finishedより先にcallbackが表示されることに注意して下さい。 lock.acquireは、他のlock.acquireと同時に実行されないことが保証できる lock.acquireを呼んだ順に実行される 注意すること return lock.acquire('mylock', async()=>{ return lock.acquire('mylock', async()=>{ console.log("hi"); }); }); このように入れ子になると、デッドロックのように止まって何も実行されなくなります。 関数を跨いで入れ子になる場合でも同様なので注意が必要です。 async-lockにはreentrantなlockも使えますが、個人的には特殊なケース以外では使わないように 使い方で注意したほうがいいでしょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで"await"を使わずにsleep(sec);

背景 皆さんテレワークで、仕事中に、届け物が来て、わざわざインターホンの場所に向かって やり取りしないといけないことはないですか?  特に会議中にこのイベントが発生すると大変ですよね^^; ゴール switchbot を使って、インターホンを押されたら 通話/終話を押して、カメラをオンにする 解錠ボタンを押す 通話/終話を押して、カメラをオフにする これを自動化したいです。 直面した問題 switchbotは url = "https://api.switch-bot.com/v1.0/devices/${deviceid}/commands"; でAPI呼び出しできます。 インターホンをトリガーとして switchbot(下)に通話/終話を押させて、カメラをオンにする N秒待って、声をかける switchbot(上)に解錠ボタンを押させる N秒待つ switchbot(下)に通話/終話を押させて、カメラをオフにする 1-5をシーケンシャルに実行する、webアプリを作りたいです めんどくさいのでHTMLのアンカータグの中でこんな感じで呼び出したいです <a href="javascript:opengate();">Open Gate</a> つまりopengateはこんな感じで呼び出したいです。 const nsec = 10; fetch('switchbot(下)のAPI'); _sleep(nsec); fetch('switchbot(上)のAPI'); _sleep(nsec); fetch('switchbot(下)のAPI')) awaitとPromiseを使ったsleepの方法は見つけましたが アンカータグの中にawait書いたらasyncの中ではないので怒られてしましました 普通にasyncつかったり色々試せばいいのですが、めんどくさいので手っ取り早く下記で解決しました。 解決策 (ものすごく前置きが長くなってしまいましたが実際に作ったものは下記です) /** * * function _sleep * * @param {number} sec - number of secs to wait * @param {function} cb - function to be executed after waiting * */ const _sleep = (sec, cb) => { // setTimeout ID let id = 0; // wait function const wait = () => { // if sec is expired and cb is a "function" if (sec-- <= 0 && "function" === typeof cb) { cb(); return; } // clear current timer and set next timer if (id > 0) clearTimeout(id); id = setTimeout(wait, 1000); }; // set first timer id = setTimeout(wait, 0); }; 最終的なopengateはこんな感じで実装しました const opengate = () => { const nsec = 10; fetch('switchbot(下)のAPI'); _sleep(nsec, () => { fetch('switchbot(上)のAPI'); _sleep(nsec, () => fetch('switchbot(下)のAPI')) }); }; 一般的な使い方 console.log("start"); _sleep(2, () => { console.log("end") });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Web APIを使って、特定の音声に対して返答するWebアプリの作成

背景 ひとり暮らししていると 家に帰って「ただいま」 といっても返答がない  悲しい… そんなことありますよね。 この悲しさを少しでも緩和してくれるアプリを作成しました。 制作物 このCodepenを参考にしました。 こちらのCodePenでは音声の認識と出力が別になっています。 今回は、 Web APIを使用し、特定の入力音声に応じて音声が出力されるように作成しました。 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>あいさつ返答</title> </head> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script> <body> <h1>あいさつ返答アプリ</h1> <div id="app"> <p> 音声読み込み <button @click="listen">{{ recogButton }}</button> {{ result }} </p> <p> 発話させる(日本語)<br> <input v-model="message" /> <button @click="say">しゃべって!</button> </p> </div> <script> //音声認識 const app = new Vue({ el: '#app', data: { recogButton: 'スタート!', recog: null, result: '', speech: null, message: '', }, mounted() { // 音声認識の準備 const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.msSpeechRecognition || null; this.recog = new SpeechRecognition(); this.recog.lang = 'ja-JP'; this.recog.interimResults = false; this.recog.continuous = false; // 音声認識が開始されたら this.recog.onstart = () => { this.result = ''; this.recogButton = '聴き取り中…'; }; // 音声を認識できたら this.recog.onresult = (event) => { // 認識されしだい、this.resultにその文字をいれる // Vueなので、文字をいれただけで画面表示も更新される this.result = event.results[0][0].transcript; //修正箇所 if (this.result == 'おはよう' || this.result == 'おはようございます') this.speech.text = this.result; else if (this.result == 'いただきます') this.speech.text = '召し上がれ'; else if (this.result == 'ごちそうさま' || this.result == 'ごちそうさまでした') this.speech.text = 'お粗末さまでした'; else if (this.result == 'ただいま') this.speech.text = 'おかえり'; else if (this.result == 'ただいま帰りました') this.speech.text = 'おかえりなさいませ、ご主人様'; else if (this.result == 'ありがとう' || this.result == 'ありがとうございます') this.speech.text = 'どういたしまして'; else if (this.result == 'ごめんなさい' || this.result == 'すいません') this.speech.text = '気にしないでね'; else if (this.result == 'こんにちは') this.speech.text = this.result; else if (this.result == 'はじめまして') this.speech.text = this.result; else if (this.result == 'お疲れ様' || this.result == 'お疲れ様でした' || this.result == 'お疲れ') this.speech.text = this.result; else this.speech.text = "すみません、よくわかりません"; return new Promise((res) => { this.speech.onend = () => res(); speechSynthesis.speak(this.speech); }); }; // 音声認識が終了したら this.recog.onspeechend = () => { this.recog.stop(); this.recogButton = '再開'; }; // 認識できなかったら this.recog.onerror = () => { this.result = '(認識できませんでした)'; this.recog.stop(); this.recogButton = '再開'; }; // 発話の準備 this.speech = new window.SpeechSynthesisUtterance(); this.speech.lang = 'ja-JP'; // または、ブラウザが対応している言語のうちja-JPな最初のボイスを使う window.speechSynthesis.onvoiceschanged = () => { const voices = window.speechSynthesis.getVoices(); this.speech.voice = voices.find(voice => voice.lang == 'ja-JP'); console.log(this.speech.voice); }; }, methods: { // 認識(聞き取り) listen() { this.recog.start(); }, // なんかしゃべる(いちおう非同期対応) async say() { return new Promise((res) => { this.speech.text = this.message; this.speech.onend = () => res(); speechSynthesis.speak(this.speech); }); }, }, }); </script> </body> </html> 完成したコードペン 変更点とコード解説 this.recog.onresultメソッド内のthis.resultに入力された音声が格納されるため そこをif文で条件分岐させました。 返答させたい文章がthis.speech.text変数に格納されています。 もっといろんな音声に対応させたい場合はforkして編集してみてください。 Tips ■CodePenのプラグイン  各言語の右側に歯車マークがあります。  そこでプラグインの設定や確認ができます。  CodePen外で使用する際は、適宜挿入が必要でした。 ■Web API ・これいれるだけでバイブレーションが使えます。  何かに使用できそうです... window.navigator.vibrate(2000); ・公式ドキュメント > インターフェース  大量にありますが、いずれ全てに目を通したいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DBDシリアルコード取得BOT

内容 discord botを使用してDBD(Dead By Daylight)のシリアルコードを神ゲー攻略から取得する 使い方 成果物のbot追加リンク リンク踏むと下記画面が出るので、招待したいサーバを選んで認証する。 サーバにBOTが入れば放置するだけ。 開発環境 windows: 10 EXE Visual Studio Code: 1.62.3 エディタ Node.js: 16.13.0 パッケージ discord.js: 13.3.1 log4js: 6.3.0 ログ出力 node-cron: 3.0.0 定期実行 puppeteer: 11.0.0 データ取得 sqlite3: 5.0.2 ファイルツリー appディレクトリ下 appディレクトリ直下にnode_modulesもあるがツリーが見づらいため写してない パッケージインストール appディレクトリで下記コマンド実行 npm install puppeteer sqlite3 log4js node-cron discord.js ソース index index.js const {Client, Intents} = require('discord.js'); const client = new Client({intents: Object.keys(Intents.FLAGS)}); const {token, admin} = require('./Config/config.json'); const Log4js = require('log4js'); Log4js.configure('./Config/log-config.json'); const logger = Log4js.getLogger('system'); const common = require('./Config/common.json'); const name = 'Index'; const DataProcess = require('./Service/DataProcessService.js'); const dataprocess = new DataProcess(logger); client.on('ready', () => { logger.info(`login: user[${client.user.tag}]`); const cron = require('node-cron'); // 3時間ごとに実行 cron.schedule(common.scraping.interval, async() => { try{ logger.info(`call: ${name} ready`); const TimeProcess = require('./Service/TimeProcessService.js'); const timeprocess = new TimeProcess(logger); const lastTime = await timeprocess.getLastTime(); // 最終実行日時から1分が経過している場合 if (timeprocess.isTimehasPassend(lastTime.lastexetime)) { // 実行日時を記録 timeprocess.setLastExeTime(); const Scraping = require('./Service/ScrapingService.js'); const scraping = new Scraping(logger); // データ取得 const data = await scraping.getCode(lastTime.lastupdtime); // 記録と取得した更新日時が異なる場合 if (data.lastUpdTime) { // データ加工して送る timeprocess.setLastUpdTime(data.lastUpdTime); broadcastMsg(dataprocess.convertMsg(data.codeData)); } else { logger.info(`msg: ${name} 前回の取得情報と同じです`); } } else { logger.info(`msg: ${name} 時間を空けて実行してください`); } } catch(e) { logger.error(e); await broadcastMsg('問題が発生したため強制終了します'); logger.info(`logout: user[${client.user.tag}]`); process.exit(1); } }); }); client.on('message', async msg => { if (msg.author.id === admin) { // adminが送ったメッセージのみ記録 logger.info(`call: ${name} message[${msg}]`); if (msg.content.substr(0, 6) === '!noti ') { broadcastMsg(msg.content.substr(6)); }else if (msg.content === '!scnt') { msg.channel.send(`現在BOTが参加しているサーバ件数は[${client.guilds.cache.size}]です`); }else if (msg.content === '!gcid') { msg.channel.send(msg.channel.id); } } // オウム返し // if (msg.author !== client.user) { // msg.channel.send(msg.content); // } }); // 招待されたとき client.on('guildCreate', async guild => { try { const msgData = await dataprocess.getCodeMsg(); if (msgData.msg) { client.channels.cache.get(guild.systemChannelId).send(msgData.msg); } } catch(e) { logger.error(`call: guildCreate[${guild}]`); } }); client.login(token); function broadcastMsg(msg) { // BOTがいるテキストチャンネル[一般]にメッセージを送る const textChannels = client.channels.cache.filter(channel => { return channel.type == common.discord.sendChannelType && channel.name == common.discord.sendChannelName; }); Array.from(textChannels.keys()).forEach(channelId => { client.channels.cache.get(channelId).send(msg); }); } Service/ Service/ScrapingService.js class Scraping { constructor(logger) { this.logger = logger; this.common = require('../Config/common.json').scraping; } async getCode(lastTime) { this.logger.info(`call: ${this.constructor.name} getCode lastTime[${lastTime}]`); const url = this.common.targetUrl; const puppeteer = require('puppeteer'); const {options} = require('../Config/puppeteer.json'); const browser = await puppeteer.launch(options); const page = await browser.newPage(); // 対象のURLの情報のみ取得する await page.setRequestInterception(true); page.on('request', request => { if (url === request.url()) { request.continue(); } else { request.abort(); } }); const data = await this.getCodeFromWeb(page, url, lastTime); browser.close(); return data; } async getCodeFromWeb(page, url, lastTime){ this.logger.info(`call: ${this.constructor.name} getCodeFromWeb url[${url}] lastTime[${lastTime}]`); await page.goto(url); // データ取得 return await page.evaluate((common, lastTime) => { let data = { codeData : [] , lastUpdTime : "" }; let dataList = []; const updTime = document.querySelector('time').innerText; if (updTime === lastTime && common.timeFlg === 0) { // 記録と取得の更新日時が同じ場合 return data; } // 取得したいデータにidなどがなかったため長い記述 const storeCodeList = document.querySelector(common.targetTag).nextElementSibling.nextElementSibling.querySelectorAll('tr'); storeCodeList.forEach((elm, idx) => { let row = []; if (idx === 0) { elm.querySelectorAll('th').forEach(head => { row.push(head.innerText); }); dataList.push(row); row = []; } else { elm.querySelectorAll('td').forEach((body, itmIdx) => { if (itmIdx === 2) { row.push(body.querySelector('input').value); dataList.push(row); row = []; } else { row.push(body.innerText.replace(/\r?\n/g,"")); } }); } }); if (dataList.length === 0) { // データ0件の場合 throw new Error('Could not get the data'); } data.codeData = dataList; data.lastUpdTime = updTime; return data; // evaluteの引数 }, this.common, lastTime); } } module.exports = Scraping; Service/DataProcessService.js class DataProcess { constructor(logger) { this.logger = logger; this.common = require('../Config/common.json').dataProcess; this.Repository = require('../Repository/Repository.js'); this.repository = new this.Repository(logger); } convertMsg(data) { // データをメッセージ用にやや整える // コンソールで揃っていてもdiscordだとずれる this.logger.info(`call: ${this.constructor.name} convertMsg data[${data}]`); let msg = ""; const separater = " "; const newLine = '\n'; data.forEach((row, rowCnt) => { row.forEach((col, colCnt) => { if (rowCnt === 0) { msg += col + separater.repeat(40); } else { if (colCnt === 2) { msg += col; } else { msg += col + separater.repeat(((colCnt === 0 ? this.common.maxLimitLen : this.common.maxContentsLen) - this.getStrDataLen(col)) * 2); } msg += separater.repeat(6); } }); msg += newLine; }); this.setCodeMsg(msg); return msg; } getStrDataLen(str) { // 全角半角判定 let len = 0; [].forEach.call(str, txt => { (txt.match(/[ -~]/)) ? len += 1 : len += 2; }); return len; } setCodeMsg(msg) { // 作成したメッセージを記録 this.logger.info(`call: ${this.constructor.name} setCodeMsg msg[${msg}]`); this.repository.setCodeMsg(msg); } async getCodeMsg() { this.logger.info(`call: ${this.constructor.name} getCodeMsg`); return await this.repository.getCodeMsg(); } } module.exports = DataProcess; Service/TimeProcessService.js class TimeProcess { constructor(logger) { this.logger = logger; this.common = require('../Config/common.json').scraping; this.Repository = require('../Repository/Repository.js'); this.repository = new this.Repository(logger); } async getLastTime() { this.logger.info(`call: ${this.constructor.name} getLastTime`); return await this.repository.getLastTime(); } isTimehasPassend(time) { this.logger.info(`call: ${this.constructor.name} isTimehasPassend time[${time}]`); const now = new Date(); const lastTime = new Date(time); // 1分経過したら lastTime.setMinutes(lastTime.getMinutes() + this.common.progressMin); return now.getTime() > lastTime.getTime() ? true : false; } setLastExeTime() { this.logger.info(`call: ${this.constructor.name} setLastExeTime`); this.repository.setLastExeTime(this.convertToDate(new Date())); } setLastUpdTime(time) { this.logger.info(`call: ${this.constructor.name} setLastUpdTime time[${time}]`); this.repository.setLastUpdTime(time); } convertToDate(time) { this.logger.info(`call: ${this.constructor.name} convertToDate time[${time}]`); let datetime = ""; const timecut1 = '-'; const timecut2 = ':'; const space = ' '; datetime += time.getFullYear() + timecut1; datetime += time.getMonth() + 1 + timecut1; datetime += time.getDate() + space; datetime += time.getHours() + timecut2; datetime += time.getMinutes() + timecut2; datetime += time.getSeconds() + timecut2; datetime += time.getMilliseconds(); // console.log(datetime); return datetime; } now() { this.logger.info(`call: ${this.constructor.name} now`); return this.convertToDate(new Date()); } } module.exports = TimeProcess; Repository/ Repository/Repository.js class Repository { constructor(logger) { this.logger = logger; this.sqlite3 = require('sqlite3'); this.db = new this.sqlite3.Database('./dbd.sqlite'); // パスの場所はindexと同じらしい } getLastTime() { this.logger.info(`call: ${this.constructor.name} getLastTime`); return new Promise(resolve => { this.db.each('SELECT * FROM timeinfo', (err, row) => resolve(row)); }); } setLastExeTime(time) { this.logger.info(`call: ${this.constructor.name} setLastExeTime time[${time}]`); this.db.run('UPDATE timeinfo SET lastexetime = ?', time); } setLastUpdTime(time) { this.logger.info(`call: ${this.constructor.name} setLastUpdTime time[${time}]`); this.db.run('UPDATE timeinfo SET lastupdtime = ?', time); } // メッセージをそのまま記録 setCodeMsg(msg) { this.logger.info(`call: ${this.constructor.name} setCodeMsg msg[${msg}]`); this.db.run('UPDATE codemsg SET msg = ?', msg); } getCodeMsg() { this.logger.info(`call: ${this.constructor.name} getCodeMsg`); return new Promise(resolve => { this.db.each('SELECT msg FROM codemsg', (err, row) => resolve(row)); }); } } module.exports = Repository; Config/ Config/common.json { "scraping" : { "progressMin" : 1, 1分以内に2回以上のリクエストを無効 "targetUrl" : "https://kamigame.jp/dbd/page/116439381060878343.html", "targetTag" :"#引き換えコードの最新情報", "interval" : "0 0 */3 * * *", 指定時刻になると実行 3hおき 0, 3, 6, 9, 12...時 "timeFlg" : 0 0: 最終更新日時を確認, 他: 最終更新日時を無視 }, "discord" : { 一斉送信の対象 "sendChannelType" : "GUILD_TEXT", "sendChannelName" : "一般" }, "dataProcess" : { "maxLimitLen" : 6, "maxContentsLen" : 35 } } Config/config.json { "token" : "トークン" "admin" : "自分のdiscrod id" } Config/puppeteer.json { "args": [ puppeteer高速化 "--disable-gpu", "--disable-dev-shm-usage", "--disable-setuid-sandbox", "--no-first-run", "--no-sandbox", "--no-zygote", "--single-process" ] } Config/log-config.json ログの設定 { "appenders": { "console": { "type": "console" }, "system": { "type": "dateFile", "filename": "log/system.log", "pattern": "-yyyy-MM-dd" } }, "categories": { "default": { "appenders": [ "console", "system" ], "level": "all" } } } 最終実行日時の確認ですがnode-cronで3hおきに1回取得してくるので、1分以内に2回以上リクエストすることはないです。 最初はメッセージに反応してデータを取得するようにしていたのですが、メッセージを送るのが面倒だったため定期実行に変えました。node-cronの設定を間違えたとき用でそのまま置いてあります。 参考サイト js スクレイピング https://qiita.com/ledsun/items/0965a60f9bdff04f2fa0 pupptter高速化 https://qiita.com/markey/items/ebf2b48626b6ac61ee89 puppetter 引数 https://qiita.com/horikeso/items/f87d3e703828aa13e2ff id無しエレメント取得 https://gray-code.com/javascript/get-child-element-and-paranet-element-and-previous-element-and-next-element-of-specific-html-element/ 非同期処理 https://qiita.com/kerupani129/items/cf4048a7d4e3aad75881 sqlite https://qiita.com/zaburo/items/a155cbc02832b501a8dd ログファイル出力 https://webbibouroku.com/Blog/Article/log4js node 定期実行 https://qiita.com/n0bisuke/items/66abf6ca1c12f495aa04
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HTML、CSS、JavaScriptのファイルをリンクさせる方法

はじめに Webサイトの開発をやっているとHTML、CSS、JavaScriptのファイルを主に扱うのですが、その3つのファイルをリンクさせて、HTMLに適用する方法を紹介したいと思います。 環境 Mac OS Monterey MacBook Pro M1 VS Code 本編 主にHTMLのファイルにCSS、JavaScriptを適用させる感じです。 HTMLとCSS head上にこのように記載します。 <link rel="stylesheet" href="ファイル名.css"> HTMLとJavaScript bodyタグの一番最後にこのように付け足します。 <script type="text/javascript" src="ファイル名.js"></script> これで完了です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む