20210912のJavaScriptに関する記事は22件です。

Firebase Authenticationのユーザを一括削除する方法

Firebase Authenticationのユーザを一括削除できないか試してみました。 Firebase Authenticationのユーザ一覧が表示されている場所で、chromeのコンソールに下記のjsを入力すると順次削除してくれます。 const interval = setInterval(() => { if ($('.edit-account-button').length === 0) { clearInterval(interval) } else { $('.edit-account-button').first().click() $('button:contains("アカウントを削除")').click() $('.confirm-button').click() } }, 1000)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

var と let でfor文の中で違い

ブロックスコープだとか、関数スコープとかで、言うけど、なんとなく一緒だと思ってたけど、明確にふるまいが違うのが、発生した。 qiita.js for(let i = 0 ; i < 10 ; i++){ console.log(i); } と qiita.js for(var i = 0 ; i < 10 ; i++){ console.log(i); } は同じ。 1,2,3,4,5,6,7,8,9 これは、その時点でconsoleが発動されるので納得できる。 1ミリ秒でも後に実行されるもので確認してみる qiita.js for(let i = 0 ; i < 10 ; i++){ setTimeout(()=>console.log(i),1) } は同じ。 1,2,3,4,5,6,7,8,9 qiita.js for(var i = 0 ; i < 10 ; i++){ setTimeout(()=>console.log(i),1) } は違う 10,10,10,10,10,10,10,10,10,10 これは、for文が終わった後に呼ばれるので、10になってる。 jQueryで、例えば、 qiita.js for(var i = 0 ; i < 10 ; i++){ $(".btn").eq(i).click(()=>{ //i番目がクリックされた事にしたいとしても function(i); }) } ってしても、どれをクリックしてもfunction(10)が発動してしまう qiita.js for(let i = 0 ; i < 10 ; i++){ $(".btn").eq(i).click(()=>{ //i番目がクリックされた事にしたいとしても function(i); }) } ってすると、n番目をクリックしたらfunction(n)が発動する。 letの方が、自分としてもイメージ通りの動作をして、varがバグっぽい動作に感じるので、constやletを面倒でvarばっか使っていたのを、改善しなくちゃって思いました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JEST入門】テストコードを書いてみよう ~環境構築とBabelの設定~

はじめに  最近フロントエンドの開発が多くなり、Jestでテストコードを書く機会が多くなりました。これまで、テストコードは、他のコードを真似しながら書いていたこともあり、しっかり勉強したことがありませんでした。そこで、Jestを0から学び直したいと思います。この記事は学び直しの備忘録です。 Jestとは Jestとは、Facebook社が開発しているOSSのJavaScriptのテストフレームワークです。 Babel, TypeScript, Node, React, Angular, Vue などで利用されています。 JestのGitHubリポジトリ 公式ドキュメント 実行環境 OS: macOS Big Sur 11.5.2 Node.js: 14.17.6 npm: 6.14.15 jest: 27.1.1 Jestのインストール はじめにJestを動かすためのプロジェクト(jest-basic)を作成し、npmコマンドでjestをインストールします。 terminal $ mkdir jest-basic $ cd jest-basic $ npm init -y $ npm install --save-dev jest Testコマンドの追加 package.jsonのtestコマンドの箇所を以下のコマンドに変更します。 package.json { "scripts": { "test": "jest" } } デフォルトのTest対象 公式ドキュメントに書いてあるとおり、Jestにはtestregexというオプションがあり、この設定でどのファイルを設定テストするかを指定できます。 デフォルトのTest対象は、(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$ __tests__フォルダの配下にテストを格納するか、テストのファイル名に.spec.jsまたは.test.js拡張子のファイルがテスト対象になります。 公式ドキュメント testregex Test対象のファイルを作成 Test対象のファイルをsrcフォルダを作成し、sum.jsというファイル名で足し算を行う関数を作成します。 src/sum.js const sum = (a,b)=> a + b; module.exports= sum; Testファイルを作成 sum.test.jsというファイル名でtestコードを書きます。 src/sum.test.js const sum = require("./sum"); test(`add 10 + 20 to equal 30`, () => { expect(sum(10, 20)).toBe(30); }); Testを実行する 先程追加したtestコマンドを実行します。 terminal $ npm test > jest-basic@1.0.0 test /Users/jest-basic > jest PASS src/sum.test.js ✓ add 10 + 20 to equal 30 (1 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 0.462 s Ran all test suites. 作成したsum関数が正常に動作していることが確認できました! Babelを使ってTestコードを書こう Babelとは、JavaScriptのコンパイラです。 Babelを使うとよりコードをシンプルに書くことができ、ES6や7の機能を利用することができます。 babeljsの公式サイト ES6の機能import関数とexport関数を利用する Babelを利用して、ES6の機能のimport関数とexport関数を利用していきます。 先程書いたコードを下記のように修正します。 src/sum.js export const sum = (a,b)=> a + b; src/sum.test.js import { sum } from "./sum"; test(`add 10 + 20 to equal 30`, () => { expect(sum(10, 20)).toBe(30); }); 再度テストコマンドを実行すると、Babelを設定していないのでエラーが発生してしまいます。 terminal $ npm test > jest-basic@1.0.0 test /Users/jest-basic > jest FAIL src/sum.test.js ● Test suite failed to run Jest encountered an unexpected token Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax. Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration. Babelの導入 公式ドキュメント Babel を使用する 以下のパッケージをインストールします。 terminal $ npm install --save-dev babel-jest @babel/core @babel/preset-env Babelのコンフィグファイルbabel.config.jsをプロジェクト直下に作成します。 terminal $ touch babel.config.js babel.config.js // babel.config.js module.exports = { presets: [["@babel/preset-env", { targets: { node: "current" } }]], }; ES6の機能を利用したコードのテストを実行する Babelの設定ができたので、再度テストコマンドを実行すると、コードがコンパイルされ、正常に実行できたことが確認ができます。 terminal $ npm test > jest-basic@1.0.0 test /Users/jest-basic > jest PASS src/sum.test.js ✓ add 10 + 20 to equal 30 (1 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 0.657 s Ran all test suites.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向け】JEST入門 テストコードを書いてみよう ~環境構築とBabelの設定~

はじめに  最近フロントエンドの開発が多くなり、Jestでテストコードを書く機会が多くなりました。これまで、テストコードは、他のコードを真似しながら書いていたこともあり、しっかり勉強したことがありませんでした。そこで、Jestを0から学び直したいと思います。この記事は学び直しの備忘録です。 Jestとは Jestとは、Facebook社が開発しているOSSのJavaScriptのテストフレームワークです。 Babel, TypeScript, Node, React, Angular, Vue などで利用されています。 JestのGitHubリポジトリ 公式ドキュメント 実行環境 OS: macOS Big Sur 11.5.2 Node.js: 14.17.6 npm: 6.14.15 jest: 27.1.1 Jestのインストール はじめにJestを動かすためのプロジェクト(jest-basic)を作成し、npmコマンドでjestをインストールします。 terminal $ mkdir jest-basic $ cd jest-basic $ npm init -y $ npm install --save-dev jest Testコマンドの追加 package.jsonのtestコマンドの箇所を以下のコマンドに変更します。 package.json { "scripts": { "test": "jest" } } デフォルトのTest対象 公式ドキュメントに書いてあるとおり、Jestにはtestregexというオプションがあり、この設定でどのファイルを設定テストするかを指定できます。 デフォルトのTest対象は、(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$ __tests__フォルダの配下にテストを格納するか、テストのファイル名に.spec.jsまたは.test.js拡張子のファイルがテスト対象になります。 公式ドキュメント testregex Test対象のファイルを作成 Test対象のファイルをsrcフォルダを作成し、sum.jsというファイル名で足し算を行う関数を作成します。 src/sum.js const sum = (a,b)=> a + b; module.exports= sum; Testファイルを作成 sum.test.jsというファイル名でtestコードを書きます。 src/sum.test.js const sum = require("./sum"); test(`add 10 + 20 to equal 30`, () => { expect(sum(10, 20)).toBe(30); }); Testを実行する 先程追加したtestコマンドを実行します。 terminal $ npm test > jest-basic@1.0.0 test /Users/jest-basic > jest PASS src/sum.test.js ✓ add 10 + 20 to equal 30 (1 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 0.462 s Ran all test suites. 作成したsum関数が正常に動作していることが確認できました! Babelを使ってTestコードを書こう Babelとは、JavaScriptのコンパイラです。 Babelを使うとよりコードをシンプルに書くことができ、ES6や7の機能を利用することができます。 babeljsの公式サイト ES6の機能import関数とexport関数を利用する Babelを利用して、ES6の機能のimport関数とexport関数を利用していきます。 先程書いたコードを下記のように修正します。 src/sum.js export const sum = (a,b)=> a + b; src/sum.test.js import { sum } from "./sum"; test(`add 10 + 20 to equal 30`, () => { expect(sum(10, 20)).toBe(30); }); 再度テストコマンドを実行すると、Babelを設定していないのでエラーが発生してしまいます。 terminal $ npm test > jest-basic@1.0.0 test /Users/jest-basic > jest FAIL src/sum.test.js ● Test suite failed to run Jest encountered an unexpected token Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax. Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration. Babelの導入 公式ドキュメント Babel を使用する 以下のパッケージをインストールします。 terminal $ npm install --save-dev babel-jest @babel/core @babel/preset-env Babelのコンフィグファイルbabel.config.jsをプロジェクト直下に作成します。 terminal $ touch babel.config.js babel.config.js // babel.config.js module.exports = { presets: [["@babel/preset-env", { targets: { node: "current" } }]], }; ES6の機能を利用したコードのテストを実行する Babelの設定ができたので、再度テストコマンドを実行すると、コードがコンパイルされ、正常に実行できたことが確認ができます。 terminal $ npm test > jest-basic@1.0.0 test /Users/jest-basic > jest PASS src/sum.test.js ✓ add 10 + 20 to equal 30 (1 ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 0.657 s Ran all test suites.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初学者向け】JavaScriptの配列

はじめに 今回はJS勉強し始めた方向けにmapやfillterの解説をしていきます。(※Mapオブジェクトの方ではないです) 後半の【レベルUP】は少し難しく感じるかもしれないので飛ばしてもらって構いません mapとfilterとは 2つとも配列に関して使用できる関数です。 配列処理でfor文を使わなくて済みます。 ぞれぞれのイメージを記号を使って表すと map: 配列に同じ処理をして新しい配列を生成 [ ? , ? , ? ].map(○ → □) → [ ? , ? , ? ] filter: 条件を満たす要素のみ取り出し新しい配列を生成 [ ? , ? , ? , ? ].filter(□) → [ ? , ? ] mapの使い方 今回はある配列にmapを使って『2倍にする』という処理をしていきます。 //まずは配列arryを用意 const arry = [1, 2, 3, 4]; //配列arryにmapを使用して新しい配列arry2として定義 const arry2 = arry.map((num) => { return num * 2; }) console.log(arry2); // [2, 4, 6, 8] 上の流れを見ていきましょう。 ①引数に配列の要素が順番に入ってくる(引数のnumに配列arryの要素が入っていきます ※引数の名前は任意です。) ②順番に同じ処理をする(今回はreturn num * 2なので値を2倍して返す) ③新しい配列が生成される(今回はarryからarry2が生成)  filterの使い方 今回はある配列にfilterを使って『偶数だけ取り出す』処理をしていきます。 //配列を用意 const arry = [1, 2, 3, 4, 5]; //新しい配列arry2を用意。右辺でarryにfilterを使用。 const arry2 = arry.filter((num) => { //2で割ってあまりが0となるものだけ返す return num % 2 === 0; }); console.log(arry2); //[2, 4] ①引数に配列の要素が順番に入ってくる ②return文で条件式を書く ③条件式を満たすものだけ取り出して新しく配列を生成 【レベルUP編】 ①引数を複数使用 mapもfilterも引数を3つ持つことができます。(第2引数はindex。第3引数は配列自体を表します) 第二引数とテンプレートリテラルを使用すると const nameArry = ["近本", "中野", "マルテ"]; nameArry.map((name, index) => console.log(`${index + 1}番は${name}です`)); //1番は近本です //2番は中野です //3番はマルテです と表すことができます。 ②map + if文 mapの中でif文を使用し、条件分岐(※処理を行う要素、行わない要素を分けること)ができます。 const nameArry = ["近本", "中野", "マルテ"]; const newNameArr = nameArr.map((name) => { if(name === "マルテ") { return name } else { return `${name}君` } }) console.log(newNameArr); //近本君 //中野君 //マルテ ③map + filter 今回はある配列にfilterで『奇数のみ取り出す』処理を行い、その後mapで『+10する』処理を行います。 const arry = [1, 2, 3, 4, 5, 6]; const answer = arry.filter(num => num % 2 === 1).map(num => num + 10); console.log(answer); // 11 13 15 ちなみにreduceを使用して同様の処理も可能。 const answer = arry.reduce((acc, cur) => { if (cur % 2 === 1) { const plusNum = cur + 10; acc.push(plusNum); } return acc; }, []); console.log(answer); ※reduceの初期値として空配列を使用しています。詳しくは参考記事をご覧ください。 参考記事 reduceの初期値について
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初学者向け】JavaScriptのmapやfilterについて

はじめに 今回はJS勉強し始めた方向けにmapやfillterの解説をしていきます。(※Mapオブジェクトの方ではないです) 後半の【レベルUP】は少し難しく感じるかもしれないので飛ばしてもらって構いません mapとfilterとは 2つとも配列に関して使用できる関数です。 配列処理でfor文を使わなくて済みます。 ぞれぞれのイメージを記号を使って表すと map: 配列に同じ処理をして新しい配列を生成 [?, ?, ?].map(○ → □) → [?, ?, ?] filter: 条件を満たす要素のみ取り出し新しい配列を生成 [?, ?, ?, ?].filter(□) → [?, ?] mapの使い方 今回はある配列にmapを使って『2倍にする』という処理をしていきます。 //まずは配列arryを用意 const arry = [1, 2, 3, 4]; //配列arryにmapを使用して新しい配列arry2として定義 const arry2 = arry.map((num) => { return num * 2; }) console.log(arry2); // [2, 4, 6, 8] 上の流れを見ていきましょう。 ①引数に配列の要素が順番に入ってくる(引数のnumに配列arryの要素が入っていきます ※引数の名前は任意です。) ②順番に同じ処理をする(今回はreturn num * 2なので値を2倍して返す) ③新しい配列が生成される(今回はarryからarry2が生成)  filterの使い方 今回はある配列にfilterを使って『偶数だけ取り出す』処理をしていきます。 //配列を用意 const arry = [1, 2, 3, 4, 5]; //新しい配列arry2を用意。右辺でarryにfilterを使用。 const arry2 = arry.filter((num) => { //2で割ってあまりが0となるものだけ返す return num % 2 === 0; }); console.log(arry2); //[2, 4] ①引数に配列の要素が順番に入ってくる ②return文で条件式を書く ③条件式を満たすものだけ取り出して新しく配列を生成 【レベルUP編】 ①引数を複数使用 mapもfilterも引数を3つ持つことができます。(第2引数はindex。第3引数は配列自体を表します) 第二引数とテンプレートリテラルを使用すると const nameArry = ["近本", "中野", "マルテ"]; nameArry.map((name, index) => console.log(`${index + 1}番は${name}です`)); //1番は近本です //2番は中野です //3番はマルテです と表すことができます。 ②map + if文 mapの中でif文を使用し、条件分岐(※処理を行う要素、行わない要素を分けること)ができます。 const nameArry = ["近本", "中野", "マルテ"]; const newNameArr = nameArr.map((name) => { if(name === "マルテ") { return name } else { return `${name}君` } }) console.log(newNameArr); //近本君 //中野君 //マルテ ③map + filter 今回はある配列にfilterで『奇数のみ取り出す』処理を行い、その後mapで『+10する』処理を行います。 const arry = [1, 2, 3, 4, 5, 6]; const answer = arry.filter(num => num % 2 === 1).map(num => num + 10); console.log(answer); // 11 13 15 ちなみにreduceを使用して同様の処理も可能。 const answer = arry.reduce((acc, cur) => { if (cur % 2 === 1) { const plusNum = cur + 10; acc.push(plusNum); } return acc; }, []); console.log(answer); ※reduceの初期値として空配列を使用しています。詳しくは参考記事をご覧ください。 参考記事 reduceの初期値について
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Chrome Extensionでオブジェクトの配列をCSVとして保存する方法

background.ts // オブジェクトの配列(例) const data = [ { title: 'aaa', published_at: '2021-09-12T06:24:12.175882' }, { title: 'bbb', published_at: '2021-09-11T07:12:14.174731' }, ]; const convertJsonToCsv = <T>(json: T[]): string => { const header = `${Object.keys(json[0]).join(',')}\n`; const body = json.map((d) => Object.values(d).join(',')).join('\n'); return header + body; }; // ダウンロードを実行 const downloadCsv = () => { const csv = convertJsonToCsv(data); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); chrome.downloads.download({ url, filename: 'filename.csv', }); }; manifest.jsonのpermissionに 'downloads' を追加するのをお忘れなく!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

絶頂度の計算方法についてそれなりに真剣に考えた

少し前にバズっていた以下の記事があまりに面白かったので この絶頂度計算なるものを誰か編み出してくれないかなーと思っていたのですが誰もやってくれないので自分で考えてみました。 結論としてあまり良い結果は得られませんでしたが、firebaseで公開しましたのでよかったら遊んでみてください。 記事内容からの仕様読み取り 「あえぎエディタ.xls」と題されたエクセルファイルは、前任者が残したあえぎ声専用のマクロ Excelのマクロ(VBA)で作成されている。この場合、分かち書きなどはできないはずなので、特定の発言をしていればポイントを高くするなどの文章解析処理にあたるものは行なっていない可能性が高い。 縦に並んだセルにセリフを一つずつ入力していくと、各セリフに含まれる母音・子音等の音声的要素が自動で数値化される。 さらに、その数値を足し引き計算することで、「絶頂度」と呼ばれる値が算出される仕組み 母音・子音に分解し、数値を足し引き計算しているだけで算出している。 あえぎ声を入力するセルには、理論上どんな文章も入れることができる。 つまり、あえぎ声以外のテキストでも、その「絶頂度」を算出できるのだ。 この絶頂度計算の仕組みは本来あえぎ声を計算することだけを想定したものであり、作者は通常の文章までカバーすることまでは想定していなかったはず。 さらに、テキストの長さを調整することも重要だ。 短すぎると気持ちいい感じが伝わらないのだが、かといって長すぎるとボイスが冗長になる。 文章の長さが重要。短すぎるのも長すぎるのもダメ。 「私はその人を常に先生と呼んでいた。」は12、「親譲りの無鉄砲で小供の時から損ばかりしている。」は30という具合だ。 とくに顕著なのは芥川で、「下人の行方は、誰も知らない。」など絶頂度367だ。 薬漬けになった人妻が白目を剥いて失神するシーンでもせいぜい330程度 大学名によっても異なるが、概して「卒業」よりも「中退」のほうが絶頂度は高くなる。 計算結果の大小は上記を満たしているべき。 ↑この一文の絶頂度は290だ。 かなり高まってきているので、この辺りで文章を終わりにしたい。 んほぉぉぉぉ! あああぁぁん!! 最後の一文「んほぉぉぉぉ! あああぁぁん!!」の絶頂度は290よりも高いはず。 ここまでの話でそれが出ている例は「下人の行方は、誰も知らない。」以外には"薬漬けになった人妻が白目を剥いて失神するシーン"のあえぎ声。 「んほぉぉぉぉ! あああぁぁん!!」="薬漬けになった人妻が白目を剥いて失神するシーン"のあえぎ声、か?  その場合「んほぉぉぉぉ! あああぁぁん!!」の絶頂度はかなり高いが「下人の行方は、誰も知らない。」よりは低いものと推察される。 読み取った内容からの考察と実装内容 文章の各文字の音声要素(母音・子音)ごとに「恍惚ポイント」というものを設定し、その四則演算から値を算出した。 恍惚ポイントはその発音をするために「口元にどれだけ複雑な操作が必要か」「全身が緊張状態の時に発音しやすいか」という2つの基準から、主観的に判断して決定。なお、口元の操作が複雑であるほど恍惚ポイントは低く、緊張状態のときに発音しやすいほど恍惚ポイントは高い。 母音の恍惚ポイント×子音の恍惚ポイントを、文章中でその文字が持つ恍惚ポイントと定義。その文字ごとの恍惚ポイントおよび前の文字からの恍惚の変化量(※1)を合算した値を基本の値とした。 ※1:つまり、恍惚ポイントが高い発音から低い発音への移行が多い場合は全体としての恍惚ポイントは低めになる補正がかかり、低い恍惚ポイントから高い恍惚ポイントへ移行する発音が多い場合は高めになる補正がかかる 読点や感嘆符など、実際に発声する際には息継ぎが発声すると思われる文字が現れた場合、そこで息継ぎが発声したものとみなし、そこまでの文章を「単呼吸内の発声」として扱う。 恍惚ポイントの移行計算は息継ぎが発生した時点でリセットする 単呼吸内で発声した文字数が適切であるほど絶頂度が高いとみなし、その基準を8文字と設定した。8文字ちょうどの場合にもっとも評価が高く、8文字から乖離するほど評価が低くなる。 読点や感嘆符も込みで1つの文章(あえぎ声)全体の長さが16文字以内であることを制限とした。制限文字数内では文字数が多いほどエロさが高いとして絶頂度が高くなる。いっぽう制限文字数を超えたものは冗長であるとしてとたんに評価が下がる。 なお、「音声的要素」と言っているので、前の文字による発音の変化も考慮に入れたが、音素ごとのポイント評価が途中で面倒くさくなって似たような発音をするものには結局同じポイントを振っていったので、あまり意味はなかったかもしれない 音韻についてはwikipediaを参考にした https://ja.wikipedia.org/wiki/日本語の音韻 https://ja.wikibooks.org/wiki/日本語/非母語話者むけ/五十音図 計算結果として記事に記載されている絶頂度の値そのものを目指すのは困難であることが予想されたため、例にあげられている文章と相対的な高低関係がある程度あっていればよいものとした。 漢字込みの文章がコピペで入力された場合に、その読み方を正確に特定できる機能を実装することは難しかったため、漢字込みの文章は対応せず、ひらがなもしくはカタカナのみの入力を許容することとした。 Excelの場合は、Excel上で入力した文章であれば読み仮名を特定してくれる機能があるらしい。(参考: https://getnavi.jp/business/64775/ ) そしてできたものがこれ URL 画面 絶頂度算出のコード const pronunciateVariationN = (prevPronunciation, nextPronunciation) => { // 語末では通常は口蓋垂鼻音 [ɴ]、たまに軟口蓋鼻音 [ŋ] になる。歌手の多くは両唇鼻音 [m]を語末の「ん」として使う。 if (nextPronunciation == null) return "ɴ"; if (nextPronunciation["consonant"].length == 0) { const firstConsonant = nextPronunciation["consonant"].slice(0, 1); // [n]・[t]・[d] の前では歯茎鼻音 [n] になる。 if (["n", "t", "d"].includes(firstConsonant)) return "n"; // [m]・[p]・[b] の前では両唇鼻音 [m] になる。 if (["m", "p", "b"].includes(firstConsonant)) return "m"; // [k]・[g] の前では軟口蓋鼻音 [ŋ] になる。 if (["k", "g"].includes(firstConsonant)) return "ŋ"; // 母音、半母音、摩擦音または、はじき音の前のときは鼻母音になる。 if (["s", "h"].includes(firstConsonant)) return " ̃"; // 撥音 /N/ は、後ろが子音が続くときはその子音と同じ調音位置になる。 return "ɴ"; } // 母音、半母音、摩擦音または、はじき音の前のときは鼻母音になる。 return " ̃"; }; const pronunciateVariationQ = (prevPronunciation, nextPronunciation) => { /** * 語末では声門閉鎖音 [ʔ] になる */ if (nextPronunciation == null) return "ʔ"; if (nextPronunciation["consonant"].length > 0) { const firstConsonant = nextPronunciation["consonant"].slice(0, 1); /** * 破裂音の前ではその破裂音の内破音である。 /p/ の前では [p̚] になる。 例 葉っぱ [hap̚pa] /t/ の前では [t̚] になる。 例 打った [ut̚ta] /k/ の前では [k̚] になる。 例 作家 [sak̚ka] */ if (["p"].includes(firstConsonant)) return "p̚"; if (["t"].includes(firstConsonant) && nextPronunciation["vowel"] != "a") return "t̚"; if (["k"].includes(firstConsonant)) return "k̚"; /** * 破擦音の前では内破音の[t]になる。 例 一致 [it̚tɕi] 例 ブリッジ [buɽit̚dʑi] 例 グッズ [gut̚dzu] 例 三つ [mit̚tsu] */ if (["t", "d", "z"].includes(firstConsonant)) return "t̚"; /** * 摩擦音の前ではその摩擦音を伸ばす。 例 あっさり [assaɾʲi] 例 一緒 [iɕɕo] 例 バッハ [bahha] 例 ビュッフェ [bjuɸɸe] 例 ワッフル [waɸɸuɽu] */ if (["s", "h"].includes(firstConsonant)) return firstConsonant; if (firstConsonant in ["s", "h"]) return firstConsonant; } return ""; } const pronunciateVariationR = (prevPronunciation, nextPronunciation) => { if (prevPronunciation["vowel"].length > 0) { return prevPronunciation["vowel"]; } return ""; } const pronunciations = { "あ": {"full": "a", "consonant": "", "vowel": "a"}, "ア": {"full": "a", "consonant": "", "vowel": "a"}, "い": {"full": "i", "consonant": "", "vowel": "i"}, "イ": {"full": "i", "consonant": "", "vowel": "i"}, "う": {"full": "u", "consonant": "", "vowel": "u"}, "ウ": {"full": "ɯ", "consonant": "", "vowel": "ɯ"}, "え": {"full": "e", "consonant": "", "vowel": "e"}, "エ": {"full": "e", "consonant": "", "vowel": "e"}, "お": {"full": "o", "consonant": "", "vowel": "o"}, "オ": {"full": "o", "consonant": "", "vowel": "o"}, "か": {"full": "ka", "consonant": "k", "vowel": "a"}, "カ": {"full": "ka", "consonant": "k", "vowel": "a"}, "が": {"full": "ga", "consonant": "g", "vowel": "a"}, "ガ": {"full": "ɡa", "consonant": "ɡ", "vowel": "a"}, "き": {"full": "ki", "consonant": "k", "vowel": "i"}, "キ": {"full": "kʲi", "consonant": "kʲ", "vowel": "i"}, "ぎ": {"full": "gi", "consonant": "g", "vowel": "i"}, "ギ": {"full": "ɡʲi", "consonant": "ɡʲ", "vowel": "i"}, "きゃ": {"full": "kya", "consonant": "ky", "vowel": "a"}, "キャ": {"full": "kʲa", "consonant": "kʲ", "vowel": "a"}, "ぎゃ": {"full": "gya", "consonant": "gy", "vowel": "a"}, "ギャ": {"full": "ɡʲa", "consonant": "ɡʲ", "vowel": "a"}, "きゅ": {"full": "kyu", "consonant": "ky", "vowel": "u"}, "キュ": {"full": "kʲɯ", "consonant": "kʲ", "vowel": "ɯ"}, "ぎゅ": {"full": "gyu", "consonant": "gy", "vowel": "u"}, "ギュ": {"full": "ɡʲɯ", "consonant": "ɡʲ", "vowel": "ɯ"}, "きょ": {"full": "kyo", "consonant": "ky", "vowel": "o"}, "キョ": {"full": "kʲo", "consonant": "kʲ", "vowel": "o"}, "ぎょ": {"full": "gyo", "consonant": "gy", "vowel": "o"}, "ギョ": {"full": "ɡʲo", "consonant": "ɡʲ", "vowel": "o"}, "く": {"full": "ku", "consonant": "k", "vowel": "u"}, "ク": {"full": "kɯ", "consonant": "k", "vowel": "ɯ"}, "ぐ": {"full": "gu", "consonant": "g", "vowel": "u"}, "グ": {"full": "ɡɯ", "consonant": "ɡ", "vowel": "ɯ"}, "け": {"full": "ke", "consonant": "k", "vowel": "e"}, "ケ": {"full": "ke", "consonant": "k", "vowel": "e"}, "げ": {"full": "ge", "consonant": "g", "vowel": "e"}, "ゲ": {"full": "ɡe", "consonant": "ɡ", "vowel": "e"}, "こ": {"full": "ko", "consonant": "k", "vowel": "o"}, "コ": {"full": "ko", "consonant": "k", "vowel": "o"}, "ご": {"full": "go", "consonant": "g", "vowel": "o"}, "ゴ": {"full": "ɡo", "consonant": "ɡ", "vowel": "o"}, "さ": {"full": "sa", "consonant": "s", "vowel": "a"}, "サ": {"full": "sa", "consonant": "s", "vowel": "a"}, "ざ": {"full": "za", "consonant": "z", "vowel": "a"}, "ザ": {"full": "dza", "consonant": "dz", "vowel": "a"}, "し": {"full": "si", "consonant": "s", "vowel": "i"}, "シ": {"full": "ʃi", "consonant": "ʃ", "vowel": "i"}, "じ": {"full": "zi", "consonant": "z", "vowel": "i"}, "ジ": {"full": "ʤi", "consonant": "ʤ", "vowel": "i"}, "しゃ": {"full": "sya", "consonant": "sy", "vowel": "a"}, "シャ": {"full": "ʃa", "consonant": "ʃ", "vowel": "a"}, "じゃ": {"full": "zya", "consonant": "zy", "vowel": "a"}, "ジャ": {"full": "ʤa", "consonant": "ʤ", "vowel": "a"}, "しゅ": {"full": "syu", "consonant": "sy", "vowel": "u"}, "シュ": {"full": "ʃɯ", "consonant": "ʃ", "vowel": "ɯ"}, "じゅ": {"full": "zyu", "consonant": "zy", "vowel": "u"}, "ジュ": {"full": "ʤɯ", "consonant": "ʤ", "vowel": "ɯ"}, "しょ": {"full": "syo", "consonant": "sy", "vowel": "o"}, "ショ": {"full": "ʃo", "consonant": "ʃ", "vowel": "o"}, "じょ": {"full": "zyo", "consonant": "zy", "vowel": "o"}, "ジョ": {"full": "ʤo", "consonant": "ʤ", "vowel": "o"}, "す": {"full": "su", "consonant": "s", "vowel": "u"}, "ス": {"full": "sɯ", "consonant": "s", "vowel": "ɯ"}, "ず": {"full": "zu", "consonant": "z", "vowel": "u"}, "ズ": {"full": "dzɯ", "consonant": "dz", "vowel": "ɯ"}, "せ": {"full": "se", "consonant": "s", "vowel": "e"}, "セ": {"full": "se", "consonant": "s", "vowel": "e"}, "ぜ": {"full": "ze", "consonant": "z", "vowel": "e"}, "ゼ": {"full": "dze", "consonant": "dz", "vowel": "e"}, "そ": {"full": "so", "consonant": "s", "vowel": "o"}, "ソ": {"full": "so", "consonant": "s", "vowel": "o"}, "ぞ": {"full": "zo", "consonant": "z", "vowel": "o"}, "ゾ": {"full": "dzo", "consonant": "dz", "vowel": "o"}, "た": {"full": "ta", "consonant": "t", "vowel": "a"}, "タ": {"full": "ta", "consonant": "t", "vowel": "a"}, "だ": {"full": "da", "consonant": "d", "vowel": "a"}, "ダ": {"full": "da", "consonant": "d", "vowel": "a"}, "ち": {"full": "ti", "consonant": "t", "vowel": "i"}, "チ": {"full": "tʃi", "consonant": "tʃ", "vowel": "i"}, "ぢ": {"full": "di", "consonant": "d", "vowel": "i"}, "ヂ": {"full": "ʤi", "consonant": "ʤ", "vowel": "i"}, "ちゃ": {"full": "tya", "consonant": "ty", "vowel": "a"}, "チャ": {"full": "tʃa", "consonant": "tʃ", "vowel": "a"}, "ぢゃ": {"full": "dya", "consonant": "dy", "vowel": "a"}, "ヂャ": {"full": "ʤa", "consonant": "ʤ", "vowel": "a"}, "ちゅ": {"full": "tyu", "consonant": "ty", "vowel": "u"}, "チュ": {"full": "tʃɯ", "consonant": "tʃ", "vowel": "ɯ"}, "ぢゅ": {"full": "dyu", "consonant": "dy", "vowel": "u"}, "ヂュ": {"full": "ʤɯ", "consonant": "ʤ", "vowel": "ɯ"}, "ちょ": {"full": "tyo", "consonant": "ty", "vowel": "o"}, "チョ": {"full": "tʃo", "consonant": "tʃ", "vowel": "o"}, "ぢょ": {"full": "dyo", "consonant": "dy", "vowel": "o"}, "ヂョ": {"full": "ʤo", "consonant": "ʤ", "vowel": "o"}, "つ": {"full": "tu", "consonant": "t", "vowel": "u"}, "ツ": {"full": "tsɯ", "consonant": "ts", "vowel": "ɯ"}, "づ": {"full": "du", "consonant": "d", "vowel": "u"}, "ヅ": {"full": "dzɯ", "consonant": "dz", "vowel": "ɯ"}, "て": {"full": "te", "consonant": "t", "vowel": "e"}, "テ": {"full": "te", "consonant": "t", "vowel": "e"}, "で": {"full": "de", "consonant": "d", "vowel": "e"}, "デ": {"full": "de", "consonant": "d", "vowel": "e"}, "と": {"full": "to", "consonant": "t", "vowel": "o"}, "ト": {"full": "to", "consonant": "t", "vowel": "o"}, "ど": {"full": "do", "consonant": "d", "vowel": "o"}, "ド": {"full": "do", "consonant": "d", "vowel": "o"}, "な": {"full": "na", "consonant": "n", "vowel": "a"}, "ナ": {"full": "na", "consonant": "n", "vowel": "a"}, "に": {"full": "ni", "consonant": "n", "vowel": "i"}, "ニ": {"full": "ɲi", "consonant": "ɲ", "vowel": "i"}, "にゃ": {"full": "nya", "consonant": "ny", "vowel": "a"}, "ニャ": {"full": "ɲa", "consonant": "ɲ", "vowel": "a"}, "にゅ": {"full": "nyu", "consonant": "ny", "vowel": "u"}, "ニュ": {"full": "ɲɯ", "consonant": "ɲ", "vowel": "ɯ"}, "にょ": {"full": "nyo", "consonant": "ny", "vowel": "o"}, "ニョ": {"full": "ɲo", "consonant": "ɲ", "vowel": "o"}, "ぬ": {"full": "nu", "consonant": "n", "vowel": "u"}, "ヌ": {"full": "nɯ", "consonant": "n", "vowel": "ɯ"}, "ね": {"full": "ne", "consonant": "n", "vowel": "e"}, "ネ": {"full": "ne", "consonant": "n", "vowel": "e"}, "の": {"full": "no", "consonant": "n", "vowel": "o"}, "ノ": {"full": "no", "consonant": "n", "vowel": "o"}, "は": {"full": "ha", "consonant": "h", "vowel": "a"}, "ハ": {"full": "ha", "consonant": "h", "vowel": "a"}, "ば": {"full": "ba", "consonant": "b", "vowel": "a"}, "バ": {"full": "ba", "consonant": "b", "vowel": "a"}, "ぱ": {"full": "pa", "consonant": "p", "vowel": "a"}, "パ": {"full": "pa", "consonant": "p", "vowel": "a"}, "ひ": {"full": "hi", "consonant": "h", "vowel": "i"}, "ヒ": {"full": "çʲi", "consonant": "çʲ", "vowel": "i"}, "び": {"full": "bi", "consonant": "b", "vowel": "i"}, "ビ": {"full": "bʲi", "consonant": "bʲ", "vowel": "i"}, "ぴ": {"full": "pi", "consonant": "p", "vowel": "i"}, "ピ": {"full": "pʲi", "consonant": "pʲ", "vowel": "i"}, "ひゃ": {"full": "hya", "consonant": "hy", "vowel": "a"}, "ヒャ": {"full": "ça", "consonant": "ç", "vowel": "a"}, "びゃ": {"full": "bya", "consonant": "by", "vowel": "a"}, "ビャ": {"full": "bʲa", "consonant": "bʲ", "vowel": "a"}, "ぴゃ": {"full": "pya", "consonant": "py", "vowel": "a"}, "ピャ": {"full": "pʲa", "consonant": "pʲ", "vowel": "a"}, "ひゅ": {"full": "hyu", "consonant": "hy", "vowel": "u"}, "ヒュ": {"full": "çɯ", "consonant": "ç", "vowel": "ɯ"}, "びゅ": {"full": "byu", "consonant": "by", "vowel": "u"}, "ビュ": {"full": "bʲɯ", "consonant": "bʲ", "vowel": "ɯ"}, "ぴゅ": {"full": "pyu", "consonant": "py", "vowel": "u"}, "ピュ": {"full": "pʲɯ", "consonant": "pʲ", "vowel": "ɯ"}, "ひょ": {"full": "hyo", "consonant": "hy", "vowel": "o"}, "ヒョ": {"full": "ço", "consonant": "ç", "vowel": "o"}, "びょ": {"full": "byo", "consonant": "by", "vowel": "o"}, "ビョ": {"full": "bʲo", "consonant": "bʲ", "vowel": "o"}, "ぴょ": {"full": "pyo", "consonant": "py", "vowel": "o"}, "ピョ": {"full": "pʲo", "consonant": "pʲ", "vowel": "o"}, "ふ": {"full": "hu", "consonant": "h", "vowel": "u"}, "フ": {"full": "ɸɯ", "consonant": "ɸ", "vowel": "ɯ"}, "ぶ": {"full": "bu", "consonant": "b", "vowel": "u"}, "ブ": {"full": "bɯ", "consonant": "b", "vowel": "ɯ"}, "ぷ": {"full": "pu", "consonant": "p", "vowel": "u"}, "プ": {"full": "pɯ", "consonant": "p", "vowel": "ɯ"}, "へ": {"full": "he", "consonant": "h", "vowel": "e"}, "ヘ": {"full": "he", "consonant": "h", "vowel": "e"}, "べ": {"full": "be", "consonant": "b", "vowel": "e"}, "ベ": {"full": "be", "consonant": "b", "vowel": "e"}, "ぺ": {"full": "pe", "consonant": "p", "vowel": "e"}, "ペ": {"full": "pe", "consonant": "p", "vowel": "e"}, "ほ": {"full": "ho", "consonant": "h", "vowel": "o"}, "ホ": {"full": "ho", "consonant": "h", "vowel": "o"}, "ぼ": {"full": "bo", "consonant": "b", "vowel": "o"}, "ボ": {"full": "bo", "consonant": "b", "vowel": "o"}, "ぽ": {"full": "po", "consonant": "p", "vowel": "o"}, "ポ": {"full": "po", "consonant": "p", "vowel": "o"}, "ま": {"full": "ma", "consonant": "m", "vowel": "a"}, "マ": {"full": "ma", "consonant": "m", "vowel": "a"}, "み": {"full": "mi", "consonant": "m", "vowel": "i"}, "ミ": {"full": "mʲi", "consonant": "mʲ", "vowel": "i"}, "みゃ": {"full": "mya", "consonant": "my", "vowel": "a"}, "ミャ": {"full": "mʲa", "consonant": "mʲ", "vowel": "a"}, "みゅ": {"full": "myu", "consonant": "my", "vowel": "u"}, "ミュ": {"full": "mʲɯ", "consonant": "mʲ", "vowel": "ɯ"}, "みょ": {"full": "myo", "consonant": "my", "vowel": "o"}, "ミョ": {"full": "mʲo", "consonant": "mʲ", "vowel": "o"}, "む": {"full": "mu", "consonant": "m", "vowel": "u"}, "ム": {"full": "mɯ", "consonant": "m", "vowel": "ɯ"}, "め": {"full": "me", "consonant": "m", "vowel": "e"}, "メ": {"full": "me", "consonant": "m", "vowel": "e"}, "も": {"full": "mo", "consonant": "m", "vowel": "o"}, "モ": {"full": "mo", "consonant": "m", "vowel": "o"}, "ゃ": {"full": "ya", "consonant": "y", "vowel": "a"}, "ャ": {"full": "ja", "consonant": "j", "vowel": "a"}, "や": {"full": "ya", "consonant": "y", "vowel": "a"}, "ヤ": {"full": "ja", "consonant": "j", "vowel": "a"}, "ゅ": {"full": "yu", "consonant": "y", "vowel": "u"}, "ュ": {"full": "jɯ", "consonant": "j", "vowel": "ɯ"}, "ゆ": {"full": "yu", "consonant": "y", "vowel": "u"}, "ユ": {"full": "jɯ", "consonant": "j", "vowel": "ɯ"}, "ょ": {"full": "yo", "consonant": "y", "vowel": "o"}, "ョ": {"full": "jo", "consonant": "j", "vowel": "o"}, "よ": {"full": "yo", "consonant": "y", "vowel": "o"}, "ヨ": {"full": "jo", "consonant": "j", "vowel": "o"}, "ら": {"full": "ra", "consonant": "r", "vowel": "a"}, "ラ": {"full": "ɾa", "consonant": "ɾ", "vowel": "a"}, "り": {"full": "ri", "consonant": "r", "vowel": "i"}, "リ": {"full": "ɾʲi", "consonant": "ɾʲ", "vowel": "i"}, "りゃ": {"full": "rya", "consonant": "ry", "vowel": "a"}, "リャ": {"full": "ɾʲa", "consonant": "ɾʲ", "vowel": "a"}, "りゅ": {"full": "ryu", "consonant": "ry", "vowel": "u"}, "リュ": {"full": "ɾʲɯ", "consonant": "ɾʲ", "vowel": "ɯ"}, "りょ": {"full": "ryo", "consonant": "ry", "vowel": "o"}, "リョ": {"full": "ɾʲo", "consonant": "ɾʲ", "vowel": "o"}, "る": {"full": "ru", "consonant": "r", "vowel": "u"}, "ル": {"full": "ɾɯ", "consonant": "ɾ", "vowel": "ɯ"}, "れ": {"full": "re", "consonant": "r", "vowel": "e"}, "レ": {"full": "ɾe", "consonant": "ɾ", "vowel": "e"}, "ろ": {"full": "ro", "consonant": "r", "vowel": "o"}, "ロ": {"full": "ɾo", "consonant": "ɾ", "vowel": "o"}, "わ": {"full": "wa", "consonant": "w", "vowel": "a"}, "ワ": {"full": "ɰa", "consonant": "ɰ", "vowel": "a"}, "ゐ": {"full": "i", "consonant": "", "vowel": "i"}, "ヰ": {"full": "i", "consonant": "", "vowel": "i"}, "ゑ": {"full": "e", "consonant": "", "vowel": "e"}, "ヱ": {"full": "e", "consonant": "", "vowel": "e"}, "を": {"full": "o", "consonant": "", "vowel": "o"}, "ヲ": {"full": "o", "consonant": "", "vowel": "o"}, "ん": {"full": "n", "consonant": "", "vowel": "n"}, "ン": { "full": "/N/", "consonant": "", "vowel": pronunciateVariationN}, "っ": {"full": "/Q/", "consonant": "", "vowel": pronunciateVariationQ}, "ー": {"full": "/R/", "consonant": "", "vowel": pronunciateVariationR}, "ぁ": {"full": "a", "consonant": "", "vowel": "a"}, "ぃ": {"full": "i", "consonant": "", "vowel": "i"}, "ぅ": {"full": "u", "consonant": "", "vowel": "u"}, "ぇ": {"full": "e", "consonant": "", "vowel": "e"}, "ぉ": { "full": "o", "consonant": "", "vowel": "o" }, "ァ": {"full": "a", "consonant": "", "vowel": "a"}, "ィ": {"full": "i", "consonant": "", "vowel": "i"}, "ゥ": {"full": "u", "consonant": "", "vowel": "u"}, "ェ": {"full": "e", "consonant": "", "vowel": "e"}, "ォ": { "full": "o", "consonant": "", "vowel": "o" }, }; const vowelPointMapping = { "": 0, "n": 1, "m": 2, "ɴ": 1, "ŋ": 1, " ̃": 1, "p̚": 2, "t̚": 2, "k̚": 2, "s": 2, "h": 2, "ʔ": 10, "i": 5, "u": 5, "ɯ": 5, "e": 4, "o": 8, "a": 10, }; const consonantPointMapping = { "": 10, "k": 1, "g": 7, "ɡ": 7, "kʲ": 1, "ɡʲ": 7, "ky": 1, "gy": 7, "s": 1, "z": 7, "dz": 7, "ʃ": 4, "ʤ": 7, "sy": 2, "zy": 7, "t": 1, "d": 4, "tʃ": 1, "ty": 2, "dy": 4, "ts": 1, "n": 3, "ɲ": 3, "ny": 4, "h": 1, "b": 6, "p": 6, "çʲ": 7, "bʲ": 7, "pʲ": 8, "hy": 3, "ç": 2, "by": 7, "py": 7, "ɸ": 3, "m": 4, "mʲ": 4, "my": 3, "y": 2, "j": 7, "r": 3, "ɾ": 3, "ɾʲ": 3, "ry": 2, "w": 2, "ɰ": 2, }; const stopLetterMappings = { "、": 1, "!": 2, "?": 1, "…": 1, " ": 1, }; const BREATHING_BEST_LENGTH = 8; const TEXT_BEST_LENGTH = 16; const calcurate = (texts) => { const textArray = texts.split(/[\n|。]/).reverse(); const ret = []; for (const text of textArray) { if (text.length == 0) continue; let point = null; try { point = _calcurate(text); ret.push([text, point]); } catch (error) { console.log(error); ret.push([text, null]); } } return ret; } const _getBestLengthCorrectionValue = (val, best) => { const tmpVal = Math.abs(best - val); return ((tmpVal > best ? best : tmpVal) + 1) } const _evalSentenceCrimaxPoint = (breathingCrimaxPoint, breathingLength) => { breathingCrimaxPoint /= _getBestLengthCorrectionValue(breathingLength, BREATHING_BEST_LENGTH); return breathingCrimaxPoint; } const _calcurate = (text) => { const splited = text.split(""); const textArray = []; for (const letter of splited) { if (["ゃ", "ゅ", "ょ"].includes(letter)) { textArray[textArray.length - 1] += letter; } else { textArray.push(letter); } } const extacyDegreeArray = []; for (let i = 0; i < textArray.length; i++) { const letter = textArray[i]; let extacyPoint = null; if (!Object.keys(stopLetterMappings).includes(letter)) { const pronunciation = pronunciations[letter]; const prevPronunciation = i == 0 ? null : pronunciations[textArray[i - 1]]; const nextPronunciation = i + 1 == textArray.length ? null : pronunciations[textArray[i + 1]]; const consonantPoint = consonantPointMapping[pronunciation["consonant"]]; const vowel = typeof pronunciation["vowel"] == "function" ? pronunciation["vowel"](prevPronunciation, nextPronunciation) : pronunciation["vowel"]; const vowelPoint = vowelPointMapping[vowel]; extacyPoint = consonantPoint * vowelPoint; } extacyDegreeArray.push({ "letter": letter, "point": extacyPoint }); } let preExtacyDegree = null; let crimaxPoint = 0; let breathingCrimaxPoint = 0; let breathingLength = 0; for (let i = 0; i < extacyDegreeArray.length; i++) { const extacyDegree = extacyDegreeArray[i]; if (preExtacyDegree == null) { preExtacyDegree = extacyDegree; breathingCrimaxPoint += extacyDegree["point"]; continue; } if (Object.keys(stopLetterMappings).includes(extacyDegree["letter"])) { if (preExtacyDegree != null) { breathingCrimaxPoint += preExtacyDegree["point"] * stopLetterMappings[extacyDegree["letter"]]; } crimaxPoint += _evalSentenceCrimaxPoint(breathingCrimaxPoint, breathingLength) preExtacyDegree = null; breathingCrimaxPoint = 0 breathingLength = 0 } else { breathingCrimaxPoint += (extacyDegree["point"] - preExtacyDegree["point"]) + + ((extacyDegree["point"]) * (i + 1) / extacyDegreeArray.length); preExtacyDegree = extacyDegree; breathingLength += 1; } } if (breathingLength > 0) { crimaxPoint += _evalSentenceCrimaxPoint(breathingCrimaxPoint, BREATHING_BEST_LENGTH) } if (text.length <= TEXT_BEST_LENGTH) { crimaxPoint /= TEXT_BEST_LENGTH - text.length + 1 } else { crimaxPoint /= TEXT_BEST_LENGTH } return Math.round(crimaxPoint); }; 反省および所感 すでに書いたが、発音の変化をできる限り実装したり、五十音の発音を単純なローマ字表記の母音子音よりも細かく分けた割には使いこなせなかった。 「下人の行方は、誰も知らない」のポイントが非常に高いが、そうなるようにルールを定めていったのだから当然の結果である。それ以外はまあまあ微妙な数値になった。 お気づきの方もいるかもしれないが、元記事ではかなり高スコアであるとされている「さて、お気づきの方もいるかもしれないが、この文章もここまで少しずつ絶頂度を高めるように書いてきた」の値が、実はめちゃくちゃ低く出てしまう。(もう見なかったことにした) とはいえ、ルールとして定めたように、適度な長さに区切られていて発音しやすい音韻で組まれた文章が、人を心地よくさせる、というのは、実際ない話ではなさそう、と思った。 んほぉぉぉぉ! あああぁぁん!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript メソッド

JavaScriptの学習の備忘録と振り返りの記事です。 何かの参考になれば幸いです。 付け足しや訂正などある場合ご教授いただけると 大変嬉しく思います!! pushメソッド 配列の最後に新しい要素を追加するメソッド pushメソッドの後の()の中に追加したい要素を入力する 定数.push(追加したい要素); //const numbers = [1,2,3]; //console.log(numbers); numbers.push(4); //console.log(numbers); 出力結果:[1,2,3,4] forEachメソッド 配列の中の要素を1つずつ取り出し、全ての要素に繰り返し同じ処理を行うメソッド 配列名.forEach((引数) => {処理内容}); //const numbers = [1,2,3]; //配列名の要素が1つずつ順番に引数に代入される numbers.forEach((number) => { //処理内に書いてある内容が繰り返し実行される。 //処理内容 console.log(number); }); 出力結果:1 2 3 findメソッド 条件式に合う1つ目の要素を配列の中から取り出すメソッド コールバック関数の中は { return 条件 } と書くことで、条件に合う要素が戻り値となる const found定数名 = 配列名.find((引数名) => {return 条件} //const numbers = [1,2,3,5,7]; //配列名の要素が1つずつ引数に代入されて処理される const foundNumber = numbers.find((number) => { return number > 3; }); //console.log(foundNumber); 出力結果:5 配列の要素がオブジェクトの場合もfindメソッドを使うことができる filterメソッド 条件に合う要素のみを取り出して新しい配列を作成するメソッド const filtered定数名 = 配列名.filter((引数名) => {return 条件} //const numbers = [1,2,3,5,7]; //配列の要素が1つずつ引数numberに代入される const filteredNumber = numbers.filter((number) => { //filterメソッド内で「3より大きい数字」かどうかを判定し、条件に合う要素が定数filteredNumbersに配列として代入される return number > 3; }); //console.log(filteredNumber); 出力結果:[5,7] mapメソッド 配列内のすべての要素に処理を行い、新しい配列を作成するメソッド 配列名の要素が1つずつ引数に代入されmapメソッド内の処理をした配列が新しく作られ、定数dに配列として代入される const 定数名 = 配列名.map((引数名) => {return 条件}); //const numbers = [1, 2, 3, 4]; // 配列numbersの要素が1つずつ引数numberに代入される const doubledNumbers = numbers.map((number) => { //↓↓↓配列numbersの全ての要素を2倍した要素を持つ、新しい配列を作成する条件 return number * 2; }); //console.log(doubledNumbers); 出力結果:[2,4,6]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript 配列のメソッド

JavaScriptの学習の備忘録と振り返りの記事です。 何かの参考になれば幸いです。 付け足しや訂正などある場合ご教授いただけると 大変嬉しく思います!! pushメソッド 配列の最後に新しい要素を追加するメソッド pushメソッドの後の()の中に追加したい要素を入力する 定数.push(追加したい要素); //const numbers = [1,2,3]; //console.log(numbers); numbers.push(4); //console.log(numbers); 出力結果:[1,2,3,4] forEachメソッド 配列の中の要素を1つずつ取り出し、全ての要素に繰り返し同じ処理を行うメソッド 配列名.forEach((引数) => {処理内容}); //const numbers = [1,2,3]; //配列名の要素が1つずつ順番に引数に代入される numbers.forEach((number) => { //処理内に書いてある内容が繰り返し実行される。 //処理内容 console.log(number); }); 出力結果:1 2 3 findメソッド 条件式に合う1つ目の要素を配列の中から取り出すメソッド コールバック関数の中は { return 条件 } と書くことで、条件に合う要素が戻り値となる const found定数名 = 配列名.find((引数名) => {return 条件} //const numbers = [1,2,3,5,7]; //配列名の要素が1つずつ引数に代入されて処理される const foundNumber = numbers.find((number) => { return number > 3; }); //console.log(foundNumber); 出力結果:5 配列の要素がオブジェクトの場合もfindメソッドを使うことができる filterメソッド 条件に合う要素のみを取り出して新しい配列を作成するメソッド const filtered定数名 = 配列名.filter((引数名) => {return 条件} //const numbers = [1,2,3,5,7]; //配列の要素が1つずつ引数numberに代入される const filteredNumber = numbers.filter((number) => { //filterメソッド内で「3より大きい数字」かどうかを判定し、条件に合う要素が定数filteredNumbersに配列として代入される return number > 3; }); //console.log(filteredNumber); 出力結果:[5,7] mapメソッド 配列内のすべての要素に処理を行い、新しい配列を作成するメソッド 配列名の要素が1つずつ引数に代入されmapメソッド内の処理をした配列が新しく作られ、定数dに配列として代入される const 定数名 = 配列名.map((引数名) => {return 条件}); //const numbers = [1, 2, 3, 4]; // 配列numbersの要素が1つずつ引数numberに代入される const doubledNumbers = numbers.map((number) => { //↓↓↓配列numbersの全ての要素を2倍した要素を持つ、新しい配列を作成する条件 return number * 2; }); //console.log(doubledNumbers); 出力結果:[2,4,6]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【簡単3 steps】Vue.jsサンプルをLaravelアプリに実装する方法

今回の記事は下記の方におすすめです! Laravelの基礎はOK Vue.jsの基礎はOK でもLaravelでVue.jsサンプルを使う方法が不明 自分でゴリゴリコードを書くのもありですが、最初のうちは「Vue.js」のサンプルをLaravelのサイトに埋め込むところから始めるのはありかもしれません。 今回は、「Vue.js」のサンプルが多数紹介されている海外のサイトを紹介しつつ、サンプルを実際の自分のプロジェクトに埋め込む方法について共有いたします! 今回の記事が参考になれば幸いです なお、間違いやご指摘ありましたら、コメント下さると幸いです。 実行環境 PHP 7.4 Laravel 6.2 Vue.js 2.5 ゴール Laravelのアプリ内でvue.jsのサンプルを実装する ちゅうい!! なお、Vue.jsがコンパイルできる環境が構築されている前提で話を進めていきます。 まだの方は、下記の記事の1)~4)を完了させ、「npm run dev」でコンパイルできる状態にしてください。 コンパイルまでの参考記事:【初心者向】Laravel 6系でVue.jsを使用する方法 laravel/uiパッケージをインストール 「composer require laravel/ui:^1.0 --dev」 vue.jsのファイル生成 「php artisan ui vue」 依存パッケージのインストール 「npm install」 js/sassファイルのコンパイル実行 「npm run dev」 ココまで↑は最低限実行しておいてください。詳細は上記記事にて。 Vue.jsサンプルサイト ちなみに下記のサイトではVue.jsを使ったサンプルがまとめられています 「スクロール」や「ポップアップ」など便利な部品が多数まとめられているので、是非一度目を通してみてください。 サンプルまとめサイト:Vue.js Examples ↑こんな感じで、サンプルによっては、Gif動画形式で、挙動を確認できるので、イメージしやすいかと思います。 今回埋め込むサンプル「scroll」 今回は、「スクロール」をLaravelサイトに埋め込んでいこうと思います。画面をスクロールすると、画面上部緑の進捗バーが進捗します。 今回Laravelで使用するサンプル:scroll progress bar ↓緑のバーが進捗 手順 Vue.jsサンプルをインストール Laravelと紐付ける インストールしたVue.jsサンプルをコンパイル 1)Vue.jsサンプルをインストール npm i vue-scroll-progress --save まず、パッケージをインストールします。上記コマンドで「node_modules」ディレクトリ以下に「vue-scroll-progress」がインストールされるかと思います。 この時点でエラーが出た方は、ご自分の開発環境のバージョンとサンプルのバージョンが競合している可能性があるので、競合しなさそうな(エラーにならない)ものをサンプルサイトから探してみてください。 2)Laravelと紐付ける ただインストールしただけでは使用できません。 Laravelのアプリ側からは、見えていないからですね。 そこでLaravelのアプリ側が認識してくれる場所に 「この機能を使いますよー」宣言をします。 具体的に言うと、「resources/js/app.js」ファイルへの追記ですね。 import VueScrollProgress from 'vue-scroll-progress'; Vue.use(VueScrollProgress); 「resources/js/app.js」ファイルに上記の記述を追加してください。 resources/js/app.js // (省略) require('./bootstrap'); window.Vue = require('vue'); // 以下2行追加 import VueScrollProgress from 'vue-scroll-progress'; Vue.use(VueScrollProgress); // (省略) Vue.component('example-component', require('./components/ExampleComponent.vue').default); 「import」の意味がわからない方はこちらの記事を参考にしてみてください。 参考:jsのimportとrequireの違い 参考:JavaScriptのimportとは? 機能ごとに分割したjsファイルを、読み込んで利用する=インポートするイメージです。 3)インストールしたVue.jsサンプルをコンパイル npm run dev 上記コマンドでコンパイルを実行すると... 進捗バーが表示されました! (ちょっとわかりにくいけど、一番上の緑のボーダーですね) 応用事例 これを応用すれば、ユーザーが入力するフォーム画面で 進捗を表示することで、途中での離脱を防ぐ効果なんかも得られそうですね 今回は、「app.js」ファイルに記述しましたが、読み込みたい部分で 部品として使うこともできるようですよ。 <template> <VueScrollProgress></VueScrollProgress> </template> 今回の記事は以上になります お役に立てたなら幸いです!! 参考 たった3ステップ!Laravelでnpmパッケージを使う方法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Slickでslick-slideのheightを100%にする方法

通常は下のような感じ(オプションなし) $('.slider').slick(); ここでslick-slideのheightを100%にする場合は $('.slider').slick() .on('setPosition', function(event, slick) { slick.$slides.css('height', slick.$slideTrack.height() + 'px'); }); これでOK
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

分割代入を利用したstate[ ]の更新

const [state, setState] = useState({id:1, firstName:"taro", lastName:"sato"}); const obj = {firstName:"hanako", lastName:"sato"}; const pushFunction = (state) => { setState( [...state, {id:2, ...obj}] ) } // ...state => {id:1, firstName:"taro", lastName:"sato"} // ...arr => firstName:"hanako", lastName:"sato" // id:2, ...arr => id:2, firstName:"hanako", lastName:"sato" // [...state, {id:2, ...obj}] => [{id:1, firstName:"taro", lastName:"sato"}, {id:2, firstName:"hanako", lastName:"sato"}]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】ハンバーガーメニューが閉じているのに中のリンクを押せる不具合の解消方法

対象者 ハンバーガーメニュー実装を考えている方 メニューを閉じているのに中身をクリックできる不具合が出ている方 目的 ハンバーガーメニューの実装 不具合の解消 実際の手順と実例 1.前提とエラー 下記の記事を参考にハンバーガーメニューを実装しました。 しかし、閉じている状態でも中身のリンクをクリックできる不具合に遭遇したので、これを解消していきたいと思います。 2.結論(解決策) CSSが今回の原因でした。 before application.scss /* ナビが開いた状態 */ nav.globalMenuSp { position: fixed; z-index : 2; top : 0; left : 0; color: #fff; background: rgba(0,0,0,0.7); text-align: center; width: 100%; opacity: 0; transition: opacity .6s ease, visibility .6s ease; } /* このクラスを、jQueryで付与・削除する */ nav.globalMenuSp.active { opacity: 100; } after application.scss /* ハンバーガーメニューの中身 */ nav.globalMenuSp { position: fixed; z-index : 2; top : 0; left : 0; color: #fff; background: rgba(0,0,0,0.7); text-align: center; width: 100%; opacity: 0; transition: opacity .6s ease, visibility .6s ease;     visibility: hidden;  #ここを追加 } /* ハンバーガーメニューが動いたとき */ nav.globalMenuSp.active { opacity: 100; visibility: visible; #ここを追加 } 3.原因 opacityを0にして透明になっていても、onclickイベントを拾ってしまうでした opacityとは透明度を調整するCSSです。 また、visibilityは要素を非表示にするCSSです。 visibility は CSS のプロパティで、文書のレイアウトを変更することなく要素を表示したり非表示にしたりします。 ここではvisibility: hidden;を指定してハンバーガーメニューの中身が隠れるようにしています。 その後visibility: visible;でハンバーガーメニューが動いた際に要素が可視化されるようになっています。 参照 【JS】ハンバーガーメニューの実装方法【CSS】 ハンバーガーメニューが閉じている状態なのに、中のリンクが押せてしまう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

node.js実践編(Markdown導入編)

準備 以下のコマンドで必要なパッケージをインストールします。 npm install markdown-it 1から作る場合は以下のパッケージをインストールします。 npm install express-session npm install express-validator npm install sqlite3 npm install knex npm install bookshelf npm install markdown-it モデル作成 以下のコマンドでモデルを作成します。 npx sequelize-cli model:generate --name Markdata --attributes userId:integer,title:string,content:text モデルファイルを修正、追記します。 models/markdata.js 'use strict'; module.exports = (sequelize, DataTypes) => { const Markdata = sequelize.define('Markdata', { userId: { type: DataTypes.INTEGER, validate: { notEmpty: { msg: "利用者は必須です。" } } }, title: { type: DataTypes.STRING, validate: { notEmpty: { msg: "タイトルは必須です。" } } }, content: { // 長さの制限無し type: DataTypes.TEXT, validate: { notEmpty: { msg: "コンテンツは必須です。" } } } }, {}); Markdata.associate = function(models) { Markdata.belongsTo(models.User); }; return Markdata; }; models/users.js User.associate = function(models) { // 主モデル User.hasMany(models.Board); // 以下を追記 User.hasMany(models.Markdata); }; 以下のコマンドでマイグレーションを行います。 npx sequelize-cli db:migrate ルーティング作成 以下のファイルを作成します。 routes/marks.js const express = require('express'); const router = express.Router(); const db = require('../models/index'); const { Op } = require("sequelize"); const MarkdownIt = require('markdown-it'); const markdown = new MarkdownIt(); const pnum = 10; // ログインのチェック function check(req, res) { if (req.session.login == null) { // ログイン後に戻る値 req.session.back = '/md'; res.redirect('/users/login'); return true; } else { return false; } } // トップページ router.get('/', (req, res, next) => { if (check(req, res)){ return }; db.Markdata.findAll({ where:{userId: req.session.login.id}, limit:pnum, order: [ ['createdAt', 'DESC'] ] }).then(mds => { var data = { title: 'Markdown Search', login: req.session.login, message: '※最近の投稿データ', form: {find:''}, content: mds }; res.render('md/index', data); }); }); // 検索フォームの送信処理 router.post('/', (req, res, next) => { if (check(req, res)){ return }; db.Markdata.findAll({ where:{ userId: req.session.login.id, content: {[Op.like]:'%' + req.body.find + '%'}, }, order: [ ['createdAt', 'DESC'] ] }).then( mds => { var data = { title: 'Markdown Search', login: req.session.login, message: '※"' + req.body.find + '" で検索された最近の投稿データ', form: req.body, content:mds }; res.render('md/index', data); }); }); // 新規作成ページの表示 router.get('/add', (req, res, next) => { if (check(req, res)){ return }; res.render('md/add', { title: 'Markdown/Add' }); }); // 新規フォームの送信処理 router.post('/add', (req, res, next) => { if (check(req, res)){ return }; db.sequelize.sync() .then(() => db.Markdata.create({ userId: req.session.login.id, title: req.body.title, content: req.body.content, }) .then(model => { res.redirect('/md'); }) ); }); // '/mark'へアクセスした際のリダイレクト router.get('/mark', (req, res, next) => { res.redirect('/md'); return; }); // 指定IDのMarkdata表示 router.get('/mark/:id', (req,res, next) => { if (check(req, res)){ return }; db.Markdata.findOne({ where: { id: req.params.id, userId: req.session.login.id }, }) .then((model) => { makepage(req, res, model, true); }); }); // Markdataの更新処理 router.post('/mark/:id', (req, res, next) => { if (check(req, res)){ return }; db.Markdata.findByPk(req.params.id) .then(md => { md.content = req.body.source; md.save().then((model) => { makepage(req, res, model, false); }); }) }); // 指定IDのMarkdaraの表示ページ作成 function makepage(req, res, model, flg) { var footer; if (flg){ var d1 = new Date(model.createdAt); var dstr1 = d1.getFullYear() + '-' + (d1.getMonth() + 1) + '-' + d1.getDate(); var d2 = new Date(model.updatedAt); var dstr2 = d2.getFullYear() + '-' + (d2.getMonth() + 1) + '-' + d2.getDate(); footer = '(created: ' + dstr1 + ', updated: ' + dstr2 + ')'; } else { footer = 'UPdating date and time information...' } var data = { title: 'Markdown', id: req.params.id, head: model.title, footer: footer, content: markdown.render(model.content), source: model.content }; res.render('md/mark', data); } module.exports = router; app.jsに登録します。 app.js var marksRouter = require('./routes/marks'); app.use('/md', marksRouter); テンプレート作成 以下のファイルを作成します。 views/md/index.ejs <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="content-type" content="text/html"> <title><%= title %></title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous"> <link rel="stylesheet" href="/stylesheets/style.css" /> </head> <body class="container"> <header> <h1 class="display-4"> <%= title %> </h1> </header> <div role="main"> <p class="h5 my-4">Hi, <span><%= login.name %></span>!<br> Welcome to <%= title %>. </p> <form action="/md" method="POST"> <div class="form-group"> <label for="find">FIND</label> <input type="text" name="find" id="find" value="<%= form.find %>" class="form-control"> </div> <input type="submit" value="検索" class="btn btn-primary"> </form> <p class="my-4 h5"><%= message %></p> <table class="table"> <% for (var i in content) { %> <% var ob = content[i]; %> <tr> <td> <a href="/md/mark/<%= ob.id %>" class="text-dark"> <%= ob.title %> </a> </td> </tr> <% } %> </table> <p> </p> <p><a href="/md/add">※データを登録</a></p> </div> </body> </html> views/md/add.ejs <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="content-type" content="text/html"> <title><%= title %></title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous"> <link rel="stylesheet" href="/stylesheets/style.css" /> </head> <body class="container"> <header> <h1 class="display-4 text-primary"> <%= title %> </h1> </header> <div role="main"> <form action="/md/add" method="post"> <div class="form-group"> <label>TITLE</label> <input type="text" name="title" id="title" class="form-control"> </div> <div class="form-group"> <label>CONTENT</label> <textarea name="content" id="content" rows="10" class="form-control"></textarea> </div> <input type="submit" value="送信" class="btn btn-primary"> </form> <p class="mt-4"><a href="/md">&lt;&lt; Top へ戻る</a></p> </div> </body> </html> views/md/mark.ejs <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="content-type" content="text/html"> <title><%= title %></title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous"> <link rel="stylesheet" href="/stylesheets/style.css" /> </head> <body class="container"> <header> <h1 class="display-4"> <%= title %> </h1> </header> <div role="main"> <p class="h5"><%= head %></p> <form action="/md/mark/<%=id %>" method="post"> <div class="form-group"> <label for="source">SOURCE</label> <textarea name="source" id="source" rows="5" class="form-control"><%= source %></textarea> </div> <input type="submit" value="更新" class="btn btn-primary"> </form> <div class="card mt-4"> <div class="card-header text-center h5"> Preview </div> <div class="card-body"> <%- content %> </div> <div class="card-footer text-muted text-right"> <%= footer %> </div> </div> <p class="mt-4"><a href="/md">&lt;&lt; Top へ戻る</a></p> </div> </body> </html> 結果 ログイン画面 Markdown一覧 Markdown追加 Markdown更新
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【追記あり】ES2022 Array#at がちょっとおかしい

【2021.9.13 追記】 既に Stage 4 になっているので諦めていたんですが、流石に見逃せないかなと思ったので TC39 の Discource にトピックをたててみました。意見がある方はこちらにお願いします。 【追記ここまで】 これについて解説します。ちなみに答えは 1 です。 配列のプロパティアクセスについて JavaScript においてオブジェクトのプロパティにアクセスするキーは文字列(とシンボル)だけとなっています。その他の値をキーに入れた場合、文字列に暗黙的型変換されます1。ちなみに配列はオブジェクトの一種です。 const arr = [1, 2, 3]; console.log(arr["0"]); // => 1 console.log(arr["1"]); // => 2 console.log(arr["10"]); // => undefined // 以下キーが "0" に変換されて 1 を返す console.log(arr[0]); console.log(arr[-0]); console.log(arr[0n]); console.log(arr[{ toString() { return "0"; } }]); // プロパティが存在しないため undefined を返す console.log(arr["foo"]); console.log(arr[NaN]); // arr["NaN"] console.log(arr[1.5]); // arr["1.5"] 配列の要素を後ろからアクセスしたい場合は Array#length を使います。 const arr = [1, 2, 3]; console.log(arr[arr.length - 1]); // => 3 console.log(arr[arr.length - 2]); // => 2 ES2022 Array#at について 配列を後ろからアクセスするのにわざわざ Array#length を使わないといけないのが煩わしいと長い間言われてきました。そこで導入されたのが ES2022 Array#at です2。引数に負の数を入れると後ろからアクセスできます。 const arr = [1, 2, 3]; console.log(arr.at(0)); // => 1 console.log(arr.at(1)); // => 2 console.log(arr.at(10)); // => undefined console.log(arr.at(-1)); // => 3 console.log(arr.at(-2)); // => 2 これでわざわざ長ったらしく記述する必要がなくなりました。 奇妙な動作 ところで Array#at では通常のプロパティアクセスとは異なり、以下のような奇妙な事が起きます。 const arr = [1, 2, 3]; console.log(arr.at("foo")); // => 1 console.log(arr.at(NaN)); // => 1 console.log(arr.at(1.5)); // => 2 どうしてこのような事が起きるのでしょうか。 通常のプロパティアクセスでは文字列に変換されるところですが、Array#at では負の数の対応をしないといけないためその前に引数を数値に変換する必要があります。仕様を見てみましょう。 Array.prototype.at ( index ) 1. Let O be ? ToObject(this value). 2. Let len be ? LengthOfArrayLike(O). 3. Let relativeIndex be ? ToIntegerOrInfinity(index). 4. If relativeIndex ≥ 0, then a. Let k be relativeIndex. 5. Else, a. Let k be len + relativeIndex. 6. If k < 0 or k ≥ len, return undefined. 7. Return ? Get(O, ! ToString(?(k))). ここで引数の index はその型によらずに必ず ToIntegerOrInfinity を通ることがわかります。これを使って数値に変換しているようです。その仕様が以下の通りです。 ToIntegerOrInfinity ( argument ) The abstract operation ToIntegerOrInfinity takes argument argument. It converts argument to an integer, +∞, or -∞. It performs the following steps when called: 1. Let number be ? ToNumber(argument). 2. If number is NaN, +0?, or -0?, return 0. 3. If number is +∞?, return +∞. 4. If number is -∞?, return -∞. 5. Let integer be floor(abs(ℝ(number))). 6. If number < +0?, set integer to -integer. 7. Return integer. ToNumber で数値に変換し3、NaN は 0 にして返し、小数点以下のある数値ではそれを取り除いて整数値にして返すという処理を行うことがわかります。これが奇妙な動作の原因です。 ……いやいやいや。単に ToNumber のみを使って変換し、その後で整数値以外をはじくような処理を行えばこのような変なことにならない気がします。どうしてこのような仕様になってしまったんでしょう。 実はこれに関連して数値にならない文字列("foo" など)や NaN を入れた場合について指摘した issue があります。 TC39 メンバーの ljharb さんの返答は以下の通り。 I see the argument that .at(NaN) should perhaps always return undefined - however, given that the proposal is stage 3 and shipping in multiple browsers, it's unlikely we'd be able to make such a change. ……というわけで誰も気づかないままブラウザに実装されてしまったのでもう手遅れというのがオチみたいです。 というか小数点以下のある数値を無理矢理整数値にすることについては正しい動作なんですかね……。 結び Array#at は引数が整数値かどうかチェックしてくれません。その上勝手に整数値に変換します。何らかの計算結果を Array#at に入れる場合には注意しましょう4。 ところで現在 Stage 2 Change Array by Copy という提案があります。 この提案には Array#withAt というものが含まれており、指定したプロパティの値のみを変更した新しい配列を作ることが出来ます。 const arr1 = [1, 2, 3]; const arr2 = arr1.withAt(1, 4); // メソッド名についてはまだ議論中です console.log(arr1); // => Array(3) [1, 2, 3] console.log(arr2); // => Array(3) [1, 4, 3] 現状このメソッドでは Array#at の反省を活かしてか、キーに相当する引数が整数値でない場合は RangeError を投げる仕様になっています。 JavaScript の言語仕様には一貫性がないことがよくあります。ES5 からある Array#indexOf は NaN を検知できませんが ES2015 Array#includes では検知できるなどは結構有名なのではないでしょうか。 const arr = [1, 2, 3, NaN]; console.log(arr.indexOf(NaN)); // => -1 console.log(arr.includes(NaN)); // => true こういった話は JavaScript の面白いところでもあり嫌われるところでもあるのかなと思います。ECMAScript の提案を追うのは割と楽しいので皆さんもよかったらどうぞ。 仕様の上ではそうですが、実装では同じ結果さえ返せばいいのでどうなっているかわかりません。普通配列に対して数値でアクセスすることが多いため、実装ではそれを前提とした最適化を行っているかもしれません。無駄に文字列で配列のプロパティにアクセスするコードを書くのは辞めたほうがいいです。 ↩ TypedArray や String に対しても同様に at メソッドが定義されています。中身はほとんど同じです。 ↩ ToNumber に bigint の値を入れると TypeError を投げます。よって Array#at に bigint は入れられません。 ↩ 正直なところ、こんな罠を警戒するくらいなら多少記述が長くなっても Array#length を使ったほうがマシだと個人的に思います。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ES2022 Array#at がちょっとおかしい

【2021.9.13 追記】 既に Stage 4 になっているので諦めていたんですが、流石に見逃せないかなと思ったので TC39 の Discource にトピックをたててみました。意見がある方はこちらにお願いします。 【追記ここまで】 これについて解説します。ちなみに答えは 1 です。 配列のプロパティアクセスについて JavaScript においてオブジェクトのプロパティにアクセスするキーは文字列(とシンボル)だけとなっています。その他の値をキーに入れた場合、文字列に暗黙的型変換されます1。ちなみに配列はオブジェクトの一種です。 const arr = [1, 2, 3]; console.log(arr["0"]); // => 1 console.log(arr["1"]); // => 2 console.log(arr["10"]); // => undefined // 以下キーが "0" に変換されて 1 を返す console.log(arr[0]); console.log(arr[-0]); console.log(arr[0n]); console.log(arr[{ toString() { return "0"; } }]); // プロパティが存在しないため undefined を返す console.log(arr["foo"]); console.log(arr[NaN]); // arr["NaN"] console.log(arr[1.5]); // arr["1.5"] 配列の要素を後ろからアクセスしたい場合は Array#length を使います。 const arr = [1, 2, 3]; console.log(arr[arr.length - 1]); // => 3 console.log(arr[arr.length - 2]); // => 2 ES2022 Array#at について 配列を後ろからアクセスするのにわざわざ Array#length を使わないといけないのが煩わしいと長い間言われてきました。そこで導入されたのが ES2022 Array#at です2。引数に負の数を入れると後ろからアクセスできます。 const arr = [1, 2, 3]; console.log(arr.at(0)); // => 1 console.log(arr.at(1)); // => 2 console.log(arr.at(10)); // => undefined console.log(arr.at(-1)); // => 3 console.log(arr.at(-2)); // => 2 これでわざわざ長ったらしく記述する必要がなくなりました。 奇妙な動作 ところで Array#at では通常のプロパティアクセスとは異なり、以下のような奇妙な事が起きます。 const arr = [1, 2, 3]; console.log(arr.at("foo")); // => 1 console.log(arr.at(NaN)); // => 1 console.log(arr.at(1.5)); // => 2 どうしてこのような事が起きるのでしょうか。 通常のプロパティアクセスでは文字列に変換されるところですが、Array#at では負の数の対応をしないといけないためその前に引数を数値に変換する必要があります。仕様を見てみましょう。 Array.prototype.at ( index ) 1. Let O be ? ToObject(this value). 2. Let len be ? LengthOfArrayLike(O). 3. Let relativeIndex be ? ToIntegerOrInfinity(index). 4. If relativeIndex ≥ 0, then a. Let k be relativeIndex. 5. Else, a. Let k be len + relativeIndex. 6. If k < 0 or k ≥ len, return undefined. 7. Return ? Get(O, ! ToString(?(k))). ここで引数の index はその型によらずに必ず ToIntegerOrInfinity を通ることがわかります。これを使って数値に変換しているようです。その仕様が以下の通りです。 ToIntegerOrInfinity ( argument ) The abstract operation ToIntegerOrInfinity takes argument argument. It converts argument to an integer, +∞, or -∞. It performs the following steps when called: 1. Let number be ? ToNumber(argument). 2. If number is NaN, +0?, or -0?, return 0. 3. If number is +∞?, return +∞. 4. If number is -∞?, return -∞. 5. Let integer be floor(abs(ℝ(number))). 6. If number < +0?, set integer to -integer. 7. Return integer. ToNumber で数値に変換し3、NaN は 0 にして返し、小数点以下のある数値ではそれを取り除いて整数値にして返すという処理を行うことがわかります。これが奇妙な動作の原因です。 ……いやいやいや。単に ToNumber のみを使って変換し、その後で整数値以外をはじくような処理を行えばこのような変なことにならない気がします。どうしてこのような仕様になってしまったんでしょう。 実はこれに関連して数値にならない文字列("foo" など)や NaN を入れた場合について指摘した issue があります。 TC39 メンバーの ljharb さんの返答は以下の通り。 I see the argument that .at(NaN) should perhaps always return undefined - however, given that the proposal is stage 3 and shipping in multiple browsers, it's unlikely we'd be able to make such a change. ……というわけで誰も気づかないままブラウザに実装されてしまったのでもう手遅れというのがオチみたいです。 というか小数点以下のある数値を無理矢理整数値にすることについては正しい動作なんですかね……。 結び Array#at は引数が整数値かどうかチェックしてくれません。その上勝手に整数値に変換します。何らかの計算結果を Array#at に入れる場合には注意しましょう4。 ところで現在 Stage 2 Change Array by Copy という提案があります。 この提案には Array#withAt というものが含まれており、指定したプロパティの値のみを変更した新しい配列を作ることが出来ます。 const arr1 = [1, 2, 3]; const arr2 = arr1.withAt(1, 4); // メソッド名についてはまだ議論中です console.log(arr1); // => Array(3) [1, 2, 3] console.log(arr2); // => Array(3) [1, 4, 3] 現状このメソッドでは Array#at の反省を活かしてか、キーに相当する引数が整数値でない場合は RangeError を投げる仕様になっています。 JavaScript の言語仕様には一貫性がないことがよくあります。ES5 からある Array#indexOf は NaN を検知できませんが ES2015 Array#includes では検知できるなどは結構有名なのではないでしょうか。 const arr = [1, 2, 3, NaN]; console.log(arr.indexOf(NaN)); // => -1 console.log(arr.includes(NaN)); // => true こういった話は JavaScript の面白いところでもあり嫌われるところでもあるのかなと思います。ECMAScript の提案を追うのは割と楽しいので皆さんもよかったらどうぞ。 仕様の上ではそうですが、実装では同じ結果さえ返せばいいのでどうなっているかわかりません。普通配列に対して数値でアクセスすることが多いため、実装ではそれを前提とした最適化を行っているかもしれません。無駄に文字列で配列のプロパティにアクセスするコードを書くのは辞めたほうがいいです。 ↩ TypedArray や String に対しても同様に at メソッドが定義されています。中身はほとんど同じです。 ↩ ToNumber に bigint の値を入れると TypeError を投げます。よって Array#at に bigint は入れられません。 ↩ 正直なところ、こんな罠を警戒するくらいなら多少記述が長くなっても Array#length を使ったほうがマシだと個人的に思います。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【追記あり】ES2022 Array#at がちょっとおかしい #fix_ecmascript_at

【2021.9.13 追記】 既に Stage 4 になっているので諦めていたんですが、流石に見逃せないかなと思ったので TC39 の Discource にトピックをたててみました。意見がある方はこちらにお願いします。 議論に伴って私が実際に欲しかったものをモジュールにして公開してみました。 それといまいちユーザーからの声が伝わっていない感じがしたのでハッシュタグ #fix_ecmascript_at を用意してみました。協力をよろしくおねがいします。 【追記ここまで】 これについて解説します。ちなみに答えは 1 です。 配列のプロパティアクセスについて JavaScript においてオブジェクトのプロパティにアクセスするキーは文字列(とシンボル)だけとなっています。その他の値をキーに入れた場合、文字列に暗黙的型変換されます1。ちなみに配列はオブジェクトの一種です。 const arr = [1, 2, 3]; console.log(arr["0"]); // => 1 console.log(arr["1"]); // => 2 console.log(arr["10"]); // => undefined // 以下キーが "0" に変換されて 1 を返す console.log(arr[0]); console.log(arr[-0]); console.log(arr[0n]); console.log(arr[{ toString() { return "0"; } }]); // プロパティが存在しないため undefined を返す console.log(arr["foo"]); console.log(arr[NaN]); // arr["NaN"] console.log(arr[1.5]); // arr["1.5"] 配列の要素を後ろからアクセスしたい場合は Array#length を使います。 const arr = [1, 2, 3]; console.log(arr[arr.length - 1]); // => 3 console.log(arr[arr.length - 2]); // => 2 ES2022 Array#at について 配列を後ろからアクセスするのにわざわざ Array#length を使わないといけないのが煩わしいと長い間言われてきました。そこで導入されたのが ES2022 Array#at です2。引数に負の数を入れると後ろからアクセスできます。 const arr = [1, 2, 3]; console.log(arr.at(0)); // => 1 console.log(arr.at(1)); // => 2 console.log(arr.at(10)); // => undefined console.log(arr.at(-1)); // => 3 console.log(arr.at(-2)); // => 2 これでわざわざ長ったらしく記述する必要がなくなりました。 奇妙な動作 ところで Array#at では通常のプロパティアクセスとは異なり、以下のような奇妙な事が起きます。 const arr = [1, 2, 3]; console.log(arr.at("foo")); // => 1 console.log(arr.at(NaN)); // => 1 console.log(arr.at(1.5)); // => 2 どうしてこのような事が起きるのでしょうか。 通常のプロパティアクセスでは文字列に変換されるところですが、Array#at では負の数の対応をしないといけないためその前に引数を数値に変換する必要があります。仕様を見てみましょう。 Array.prototype.at ( index ) 1. Let O be ? ToObject(this value). 2. Let len be ? LengthOfArrayLike(O). 3. Let relativeIndex be ? ToIntegerOrInfinity(index). 4. If relativeIndex ≥ 0, then a. Let k be relativeIndex. 5. Else, a. Let k be len + relativeIndex. 6. If k < 0 or k ≥ len, return undefined. 7. Return ? Get(O, ! ToString(?(k))). ここで引数の index はその型によらずに必ず ToIntegerOrInfinity を通ることがわかります。これを使って数値に変換しているようです。その仕様が以下の通りです。 ToIntegerOrInfinity ( argument ) The abstract operation ToIntegerOrInfinity takes argument argument. It converts argument to an integer, +∞, or -∞. It performs the following steps when called: 1. Let number be ? ToNumber(argument). 2. If number is NaN, +0?, or -0?, return 0. 3. If number is +∞?, return +∞. 4. If number is -∞?, return -∞. 5. Let integer be floor(abs(ℝ(number))). 6. If number < +0?, set integer to -integer. 7. Return integer. ToNumber で数値に変換し3、NaN は 0 にして返し、小数点以下のある数値ではそれを取り除いて整数値にして返すという処理を行うことがわかります。これが奇妙な動作の原因です。 ……いやいやいや。単に ToNumber のみを使って変換し、その後で整数値以外をはじくような処理を行えばこのような変なことにならない気がします。どうしてこのような仕様になってしまったんでしょう。 実はこれに関連して数値にならない文字列("foo" など)や NaN を入れた場合について指摘した issue があります。 TC39 メンバーの ljharb さんの返答は以下の通り。 I see the argument that .at(NaN) should perhaps always return undefined - however, given that the proposal is stage 3 and shipping in multiple browsers, it's unlikely we'd be able to make such a change. ……というわけで誰も指摘がされなかったままブラウザに実装されてしまったのでもう手遅れというのがオチみたいです。 【2021.9.13 追記】 どうやらこれは Array#slice などのインデックスを受け取るメソッドで既に広く使われているやり方なので、Array#at もそれに合わせたということみたいです。 どう考えても使い勝手が悪そうなんですが、何故そのような決定になったのでしょう。既に Stage 4 になっている仕様に対してこうやって意見を言うのもおかしい話ではあるんですが……。 【追記ここまで】 結び Array#at は引数が整数値かどうかチェックしてくれません。その上勝手に整数値に変換します。何らかの計算結果を Array#at に入れる場合には注意しましょう4。 ところで現在 Stage 2 Change Array by Copy という提案があります。 この提案には Array#withAt というものが含まれており、指定したプロパティの値のみを変更した新しい配列を作ることが出来ます。 const arr1 = [1, 2, 3]; const arr2 = arr1.withAt(1, 4); // メソッド名についてはまだ議論中です console.log(arr1); // => Array(3) [1, 2, 3] console.log(arr2); // => Array(3) [1, 4, 3] 現状このメソッドでは Array#at の反省を活かしてか、キーに相当する引数が整数値でない場合は RangeError を投げる仕様になっています。 JavaScript の言語仕様には一貫性がないことがよくあります。ES5 からある Array#indexOf は NaN を検知できませんが ES2015 Array#includes では検知できるなどは結構有名なのではないでしょうか。 const arr = [1, 2, 3, NaN]; console.log(arr.indexOf(NaN)); // => -1 console.log(arr.includes(NaN)); // => true こういった話は JavaScript の面白いところでもあり嫌われるところでもあるのかなと思います。ECMAScript の提案を追うのは割と楽しいので皆さんもよかったらどうぞ。 仕様の上ではそうですが、実装では同じ結果さえ返せばいいのでどうなっているかわかりません。普通配列に対して数値でアクセスすることが多いため、実装ではそれを前提とした最適化を行っているかもしれません。無駄に文字列で配列のプロパティにアクセスするコードを書くのは辞めたほうがいいです。 ↩ TypedArray や String に対しても同様に at メソッドが定義されています。中身はほとんど同じです。 ↩ ToNumber に bigint の値を入れると TypeError を投げます。よって Array#at に bigint は入れられません。 ↩ 正直なところ、こんな罠を警戒するくらいなら多少記述が長くなっても Array#length を使ったほうがマシだと個人的に思います。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ほんやくコンニャクお味噌味を食べて論文を読もう!

Abstract ブラウザ上で論文を"Read online"で読む際に、DeepLを用いたページ翻訳もどきを行うツールを紹介する。このツールはChromeさえ入っていれば、会社などのインストールに制限がある環境でも実行できる利点がある。 Github「TranslationGummyOmiso」に導入方法と使用方法を示してあるため、別途こちらも参考にしていただきたい。この記事は半分お遊びで執筆したため、技術的な内容に興味がない場合や、使用のみを目的とする場合はGithubだけを読むことを強く推奨する。 Introduction ほんやくコンニャクお味噌味は「ドラえもん のび太の日本誕生」にて使用された、食べると未知の言語間でも会話が可能となるひみつ道具である。 ......という話はここではおいておいて、このツールは2020年12月に岩崎 修登 氏が「「ほん訳コンニャク」 を食べて 論文を読もう」内で紹介したTranslation-Gummyをオマージュして作成したツールである。岩崎氏は論文ページのURLやPDFファイルを投げることでDeepLやGoogle翻訳によって日本語訳したPDFファイルを出力するツールTranslation-Gummyを公開した。ツールはPythonで記述されており、WindowsであればPythonの実行環境の作成が必要で、DeepL/Google翻訳の操作やPDFの編集にいくつかのソフトウェアのインストールを必要とする代わりに、論文調査の効率向上に貢献している。 企業研究者における大きな壁として、ソフトウェアの任意のインストールが制限される問題がある。多くの場合はインストールのために業務内で使用する旨を申請する必要があり、セキュリティが厳しい場合ではそもそもソフトウェアのインストールが認められない場合がある。制限環境においてはWindowsに標準搭載されているコマンドプロンプト、PowerShell、VBS、C#コンパイラ(csc.exe)であったり、ほとんどの場合にインストールされているOfficeのExcelやVBAを用いたりする代替手法でプログラミングする必要がある。また、近年ではインターネットでの情報収集を促進するために、ブラウザとしてChromeのインストールが行われている例も報告されている。 慣れない分野の論文探索や日常的な情報収集においては、翻訳機能の利用が効率向上に重要であるが、企業研究者はその制限環境の影響から、原文のままで読んだり、Chromeに搭載されたGoogleページ翻訳機能を利用したりして情報収集に励んでいる。しかし、Google翻訳は論文文章の翻訳において必ずしも高い精度を発揮することができず、コピーアンドペースト(コピペ)によって、翻訳精度に定評のあるDeepL翻訳を利用することも多い。そこで、私が作成した「ほんやくコンニャクお味噌味」(Omiso)は、オンライン論文のページ翻訳のみ、Chromeを必須、という制限を設けることで、Windowsに標準搭載されているソフトウェアだけを使用したDeepLによるページ翻訳を実現した。 Material and Method 使用するOmisoの導入方法はGithubに示した。また、Windows標準ツールとして、コマンドライン、PowerShell、メモ帳などのテキストエディタを使用し、別途Chromeのインストールを必要としている。論文ページを開くブラウザは、Firefox、Chrome、Microsoft Edgeで動作確認している。 Results Omisoの使用には事前にOmisoServerの起動が必要である。OmisoServer実行時にはコマンドプロンプトに"Translation Gummy Omiso server is running..."の文字が表示される(Fig. 1)。 Fig. 1 OmisoServerの実行画面 1行目はStartOmisoServer.batによるOmisoServer.ps1の呼び出し。2行目はサーバーの実行状態。エラー発生時にはこの画面が表示されない場合がある。 OmisoServerが起動した状態のままブラウザのブックマークレットからOmisoBookmarklet.jsの内容を実行すると、10 s後にページ上部から段落ごとに翻訳が進行していく。DeepLへの負担を減らすため、ページのスクロールに応じて翻訳は適宜行われる。翻訳が終了した文章も右上の?をクリックすることで、翻訳前の文章と翻訳後の文章をクリックによって切り替えることができる。 まれにページに段落(HTMLのpタグ)が設定されていない場合においては、翻訳ができない場合がある。翻訳できない文章は?が表示されないようになっている。また、OmisoServerが起動していない場合は30 s後にエラーメッセージが表示される。 Discussion OmisoServerはPowerShell Scriptを介した.NetのHttpListnerによって実現されており、PowerShellのHttpListnerによる簡易サーバーで複数リクエスト処理にて紹介したローカルサーバーを用いている。このサーバーはChromeの標準機能であるHeadless Chrome(画面に表示されないままウェブページの実行が可能)を介してDeepLを開き、翻訳内容をJSONP形式で返却する。 DeepLのページはURLの末尾にUTF-8 文字エンコーディングした原文を添えて開くことで、入力内容をその場で翻訳する機能を有している。翻訳はページを開いた後、ブラウザ上で実行されるため、httpリクエストでは内容を取得できない。そこで、前述のHeadless Chromeでページを開いた後、10 s待ち、ブラウザ上に描画された翻訳後の文章を取得している。例えば、"This is a pen."を翻訳したい場合、https://www.deepl.com/translator#en/ja/This%20is%20a%20pen.を開くことで、DeepLによる翻訳を実行することができる。 ブラウザ側ではブックマークレットから、OmisoServerに翻訳したい内容を送る。この際、DeepLの負担を減らすため、下記のようなルールを設けている。 最初は上部5段落のみを翻訳 マウスカーソルが段落に載せられた場合、その段落と次の段落を翻訳する 200文字以下の段落は段落とみなさない また、論文のページ上でブックマークレットを実行しているため、論文のドメインとlocalhostが異なり、一般的な方法では情報の受け渡しができない。そのため、OmisoServerからのレスポンスはJSONP形式で受け取り、scriptタグをページに埋め込むことでcallbacck関数を実行している。この際、JSONP injectionの危険性があるため、サーバーからのレスポンスには特定の文字の変換処理を行っている。 最後に、OmisoではPowerShellを用いたサーバーからHeadless Chromeを呼び出し、DeepLのページに翻訳をさせ、JSONPでブラウザに返す手法で論文ページのリアルタイム翻訳を実現した。論文を英語で読める技術は重要ではあるが、より多くより手軽に読めるようにすることで、本業である研究に費やす時間を増えると期待される。 Acknowledgement このツールはTranslation-Gummyに触発されて作成した。素晴らしいツールを考案した岩崎 修登 氏には感謝の意を表する。翻訳にはDeepL Translator (free)を使用しており、素晴らしい翻訳技術を提供しているTechCrunch社にも感謝を述べたい。また、本ツールは210912現在、利用規約に沿った形でDeepLを使用しているが、TechCrunch社からの要請や利用規約の変更があれば、事前告知なく公開を停止したり、仕様を変更したりする場合があることを承知いただきたい。DeepLのページのレイアウト変更によって、私の意図なくHeadless Chromeでの翻訳後文章の取得ができなくなる可能性もここに示す。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザにROSイメージの表示 (ROS/Javascript)

概要 ブラウザ上にROSのイメージトピックを配信 インストール web-video-serverをインストール sensor_msgs/Imageをブラウザへ配信してくれる sudo apt install ros-noetic-web-video-server 実行 roscore rosrun web_video_server web_video_server 別端末で好きなイメージを引数に与えて,image_publisherの実行(静止画をROSメッセージとして送信してくれる) rosrun image_publisher image_publisher {イメージファイル} ex. rosrun image_publisher image_publisher temp.png 結果 ブラウザからhttp://localhost:8080 へアクセス すると以下の写真のようにトピックが選択でき,クリックするとそのストリームを見ることができる! 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザにsensor_msgs/Imageの表示 (ROS/Javascript)

概要 ブラウザ上にROSのイメージトピックを配信 インストール web-video-serverをインストール sensor_msgs/Imageをブラウザへ配信してくれる sudo apt install ros-noetic-web-video-server 実行 roscore rosrun web_video_server web_video_server 別端末で好きなイメージを引数に与えて,image_publisherの実行(静止画をROSメッセージとして送信してくれる) rosrun image_publisher image_publisher {イメージファイル} ex. rosrun image_publisher image_publisher temp.png 結果 ブラウザからhttp://localhost:8080 へアクセス すると以下の写真のようにトピックが選択でき,クリックするとそのストリームを見ることができる! ※自分で立ち上げたサーバに表示 以下、自分のjavasciptプログラムでイメージを取得する場合についてです。 上記プログラムを実行しながら,次のようなプログラムを作成します。 ※image_topic変数内は,現在配信されているトピックから確認して書き換える(rostopic listで確認) webpages/video.html <!doctype html> <html lang="ja"> <head> <title>ROS Image Web Viewer</title> <style> *{ margin: 0px; padding: 0px; border: 0px; } .full_picture{ width: 100vw; height: 100vh; object-fit: contain; } </style> </head> <body> <script> var image_topic = "/image_publisher_1631381620176566818/image_raw"; document.write("<img class='full_picture' src='http://"+location.hostname +":8080/stream?topic=" + image_topic + "&type=ros_compressed'></img>"); </script> </body> </html> 実行します。 cd webpages python3 -m http.server http://0.0.0.0:8000/ にアクセスするとブラウザに画像が表示されます。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザにROS画像の送信 ROS/Javascript

概要 ブラウザ上にROSのイメージトピックを配信 インストール web-video-serverをインストール sensor_msgs/Imageをブラウザへ配信してくれる sudo apt install ros-noetic-web-video-server 実行 roscore rosrun web_video_server web_video_server 別端末で好きなイメージを引数に与えて,image_publisherの実行(静止画をROSメッセージとして送信してくれる) rosrun image_publisher image_publisher {イメージファイル} ex. rosrun image_publisher image_publisher temp.png 結果 ブラウザからhttp://localhost:8080 へアクセス すると以下の写真のようにトピックが選択でき,クリックするとそのストリームを見ることができる! ※自分で立ち上げたサーバに表示 以下、自分のjavasciptプログラムでイメージを取得する場合についてです。 上記プログラムを実行しながら,次のようなプログラムを作成します。 ※image_topic変数内は,現在配信されているトピックから確認して書き換える(rostopic listで確認) webpages/video.html <!doctype html> <html lang="ja"> <head> <title>ROS Image Web Viewer</title> <style> *{ margin: 0px; padding: 0px; border: 0px; } .full_picture{ width: 100vw; height: 100vh; object-fit: contain; } </style> </head> <body> <script> var image_topic = "/image_publisher_1631381620176566818/image_raw"; document.write("<img class='full_picture' src='http://"+location.hostname +":8080/stream?topic=" + image_topic + "&type=ros_compressed'></img>"); </script> </body> </html> 実行します。 cd webpages python3 -m http.server http://0.0.0.0:8000/ にアクセスするとブラウザに画像が表示されます。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む