- 投稿日:2022-01-13T22:42:39+09:00
Googleさんの zx をローカルインストールして「nodeコマンドで実行」/「ファイルを直接指定して実行」
この記事の内容は、昨年末のアドベントカレンダー用の記事でも扱った、Googleさんの zx に関するものです。 ●【Backlog 2021】 Googleさんの zx で Backlog API を扱う(JavaScript で課題を追加する) - Qiita https://qiita.com/youtoy/items/36ea84e09332d4e6815a 何をやるか 今回の記事で扱う内容は、以下のとおりです。 npm i zx で zx をローカルにインストールして使う node ●●.mjs というような nodeコマンドを使った処理の実行 ./●●.mjs というようなファイルを直接実行する形での処理 下準備 冒頭の記事でも書いている、今回のセットアップの話をざっくり書きます。 なお、自分が試している環境は Mac で、Node.js のバージョンは 14.18.2 です。 Node.js のバージョンについては、公式ページに以下の指定がありますので、その点だけご注意ください。 zx をローカルインストール まずは以下のコマンドを任意の場所で実行します。 $ npm i zx これで、コマンドを実行したフォルダに node_modulesフォルダがや package.json などができると思います。 テスト用のプログラムを用意して zx コマンドで実行する 次に、上記の手順で npmコマンドを実行したフォルダに、JavaScript のプログラムのファイルを作ります。 ファイル名は適当に test.mjs にしました。 拡張子を mjs にしているのは、zx の公式ページの以下にも書いてある「top-level await」をシンプルに使うためです。 test.js という名前にした場合、上記のとおり全体を async function でラップしてやる必要が出てきます。 テスト用のプログラム ここで、シンプルなプログラムを用意してみます(補足は、プログラムの内容と実行結果の後に書きます)。 以下の処理内容は「lsコマンド」・「1秒間のスリープ」・「chalk を使った色付きの文字出力」を順番に実行するという適当なものにしました。 test.mjs await $`ls` await sleep(1000); console.log(chalk.blue("Hello world!")); プログラムを zxコマンドで実行する まずは、zxコマンドで実行します。 グローバルインストールではなくローカルインストールを行ったために、直接 zxコマンドが使えません。それに関する対処として npxコマンドを使い zxコマンドを実行します。 $ npx zx test.mjs 実行結果は以下の通りで、想定どおりの結果になりました。 (1行目の右端のあたりが実行コマンドで、2行目移行が実行結果の出力です) プログラムの補足 1行目は、公式ドキュメントの以下に書いてある方法で lsコマンドを実行しています。書き方は「$`【コマンド】`」という形です。 2行目は、公式ドキュメントの以下に書いてある関数を利用しています。 これは公式ドキュメントの「Functions」という項目で書かれた内容のうちの 1つです。 他にも、cd()、fetch()、question()、nothrow()などが、Functions という項目の部分に書かれています。 3行目は、公式ドキュメントの以下に書いてある関数を利用したものです。 chalkパッケージをインポートすることなく利用できる状態にしているようです。 テスト用のプログラムを少し書きかえて nodeコマンドで実行する 次に、上で用いていたプログラムを少し書きかえて、nodeコマンドで実行できるようにしてみます。 ちなみに、上記のプログラムをそのまま実行すると $ is not defined というエラーになります。 それを避けるために、以下に書かれた対応(import 'zx/globals'という行を冒頭に追加)をします。 その対応を行った後のプログラムは、以下のようになります。 test.mjs import 'zx/globals' await $`ls` await sleep(1000); console.log(chalk.blue("Hello world!")); 再度、nodeコマンドで実行してみると、今度は無事に処理が完了しました。 プログラムをまた少し書きかえてからファイルを直接指定する形で実行 今度は、ファイルを直接指定して実行できる形にして、プログラムを実行してみます。 この後のプログラムのファイル名は、上記と区別するために「test2.mjs」に変えました(前のままでも問題はないのですが...)。 ファイルを直接指定して実行できるようにするため、mjsファイルに実行権限を付与します。 chmod +x test2.mjs この後、 ./test2.mjs という形で、ファイルを直接指定して実行することができますが、今は実行後にエラーが発生します。 そこで、以下の記事などにも書かれている「shebang(hashbang)」を、プログラムの冒頭に追加します。 ●JavaScriptと #! https://zenn.dev/qnighy/articles/d2712de8e4585f749b30 これで、test2.mjs を何を使って実行するか(つまり、nodeコマンドで実行すること)を指定できます。 test2.mjs #!/usr/bin/env node import 'zx/globals' await $`ls` await sleep(1000); console.log(chalk.blue("Hello world!")); 再度、ファイルを直接指定して実行してみると、無事に動作させることができました。 グローバルインストールして使う場合の話 今回の記事では、zx をローカルインストールして利用しました。 グローバルインストールをして使う場合は、公式ページや以下のページなどをご参照ください。 ●Google発のJavaScriptで書けるシェル 「zx」 | DevelopersIO https://dev.classmethod.jp/articles/shell-zx/ ●zxを使ってみた│FORCIA CUBE│フォルシア株式会社 https://www.forcia.com/blog/002378.html
- 投稿日:2022-01-13T21:24:56+09:00
初めてのDiscord.js
使い方・初めてではまったことを書いていきます。 初心者なのでミスがあるかもしれないです。 条件 OS: windows 参考 https://discord.com/developers/ https://qiita.com/yuto0214w/items/1ecee25efca6b5b7445b Botの作成方法 以前こちらの記事の前半に書きました。 https://qiita.com/kigawa/items/b5eee4bb00809e5a4931 Node.jsのインストール インストーラーでインストールします インストールは割愛 https://nodejs.org/ ※v16以降じゃないといけないらしい botの設定 左のメニューからbotを選択します Privileged Gateway Intentsの中のPRESENCE INTENTとSERVER MEMBERS INTENTをオンにします コードを書く index.js const {Client, Intents} = require('discord.js') const client = new Client({intents: Object.keys(Intents.FLAGS)}) client.on('ready', () => { console.log(`log in ${client.user.tag}`) }) client.on('messageCreate', async msg => { if (msg.content === 'cmd.command') { msg.channel.send('on command') } }) client.login('token') 最後に 先日jdaでボットを作ったばかりですがjsで作ることになってしまったので書いてみました。 それも含めてdiscord.jsを始めるにあたって必要なサイトを集めただけになってしまった気がします。
- 投稿日:2022-01-13T21:23:27+09:00
【配列操作】オブジェクトの配列から共通項を探しまとめる
はじめに フロントエンドエンジニアにとって配列操作は必須の技術だと思うが、ちょっと詰まったので共有する。 以下のようなオブジェクトの配列がある際に、name キーのa が共通であり、aを一つのキーにまとめ、ageの配列を作成する。 ちなみにAPIの関係でこのような処理を強いられました。 const arr = [ { name: 'a', age: 21 }, { name: 'a', age: 27, }, { name: 'a', age: 43, }, { name: 'b', age: 45, }, { name: 'c', age: 13, } ]; やりたいこと const arr = [ { name: 'a', age: [21, 27, 43] }, { name: 'b', age: 45, }, { name: 'c', age: 13, } ]; 実装方法 ①まず配列の中から、共通しているnameをまとめる処理をする (Setオブジェクトで共通項をはじき、Array.fromで配列に格納する) const names = [...new Set(arr.map(v => v.name))]; ②作成したnamesをmapで回し、nameキーにはnameを代入する const commonArr = names.map(name => ({ name, age: arr.filter(elem => elem.name === name).map(({age}) => age), })); console.log(commonArr); ③最初に定義したarrにfilterをかけ、nameが一致する値のageをageに代入 age: arr.filter(elem => elem.name === name).map(({age}) => age), 最終的に以下のような配列になる!! [ { name: 'a', age: [21, 27, 43] }, { name: 'b', age: 45, }, { name: 'c', age: 13, } ] さいごに 今回はAPIのレスポンスの構成上このような操作が必須だった。 配列操作はフロントエンドエンジニアとしては必須だと思うので配列操作マスターになれるように頑張りまふ。
- 投稿日:2022-01-13T18:32:51+09:00
正多面体のデータを作る[改] (HTML+JavaScript版)
前記事での反省を踏まえつつ JavaScript を使って計算します。 生成する正多面体 原点を中心として、頂点が半径1の球上にある正多面体のデータを求めます。 算出される座標は単位ベクトルになります。 正多面体の面と頂点 正多面体の面数 M と、頂点数 V と面の角数 N と頂点に接する面の数 F の対応は以下のとおり。 M 正多面体 N F 対 V 4 正四面体 3 3 正四面体 4 6 正六面体 4 3 正八面体 8 8 正八面体 3 4 正六面体 6 12 正十二面体 5 3 正二十面体 20 20 正二十面体 3 5 正十二面体 12 V 対 F N 正多面体 M 正多面体の面の法線(単位ベクトル)は「対となる正多面体」の頂点(単位ベクトル)にすることができます。 また、正多面体と「対となる正多面体」は 面数 M と頂点数 V 面の角数 N と頂点に接する面の数 F を入れ替えたものになっています。 面数に対する序数 面数に対する序数を 面数 4 4 6 8 12 20 序数 0 1 2 3 4 5 とします。正四面体は対も正四面体なので重複させると、対の序数は $\left( M \oplus 1 \right)$ とできます。 全ての頂点を求める 最初の3頂点を求める 最初の3頂点を \begin{eqnarray} \phi & = & \frac{2\pi}{F} \\ \cos{\theta} & = & 2 \cos^2{ \frac{\pi}{N} } \csc^2{ \frac{\pi}{F} } - 1 \\ \\ P_1 & = & \left( 0,\ 0,\ 1 \right) \\ P_2 & = & \left( \sin{\theta},\ 0,\ \cos{\theta} \right) \\ P_3 & = & \left( \sin{\theta} \cos{\phi},\ \sin{\theta} \sin{\phi},\ \cos{\theta} \right) \\ \end{eqnarray} とします。これは対となる正多面体の法線にもなります。 新たな頂点を求める この3頂点から隣の頂点を求めるため \begin{eqnarray} v_x & = & P_1 \\ v_y & = & \frac{ v_x \times P_2 }{ \left| v_x \times P_2 \right| } \\ v_z & = & \frac{ v_x \times v_y }{ \left| v_x \times v_y \right| } \\ X_{cos} & = & v_x \cdot P_3 \\ Y_{cos} & = & v_y \cdot P_3 \\ Z_{cos} & = & v_z \cdot P_3 \\ \end{eqnarray} および、隣接する2頂点の判定情報 P_{cos} = P_1 \cdot P_2 を用意します。 これで隣接する2頂点 $P_1,P_2$ から $v_x,v_y,v_z$ を求め、これと $X_{cos},Y_{cos},Z_{cos}$ から $P_3$ が求まります。 P_3 = X_{cos} v_x + Y_{cos} v_y + Z_{cos} v_z 同様に、順番を逆にした $P_2,P_1$ と $X_{cos},Y_{cos},Z_{cos}$ からは新たな頂点 $P_4$ を求めることができます。あとは、隣接する2頂点(内積が $P_{cos}$ と一致するもの)の総当たりで新たな頂点を重複排除しつつ追加していき、増えなくなるまで繰り返します。 全ての法線を求める 最初の3法線を求める 最初の3法線は、最初の3頂点を使って \begin{eqnarray} Rot_{xy} & = & \begin{pmatrix} \cos{\theta} & -\sin{\theta} & 0 \\ \sin{\theta} & \cos{\theta} & 0 \\ 0 & 0 & 1 \\ \end{pmatrix} \\ N_1 & = & \frac{ \left( P_1 - P_3 \right) \times \left( P_2 - P_1 \right) }{ \left| \left( P_1 - P_3 \right) \times \left( P_2 - P_1 \right) \right| } \\ N_2 & = & Rot_{xy}\ N_1 \\ N_3 & = & Rot_{xy}\ N_2 \\ \end{eqnarray} とします。 あとは、$P_1,P_2,P_3$ を $N_1,N_2,N_3$ に置換えて頂点と同じ方法で、全ての法線を求めます。 多角形のデータを作る 一つの法線が、一つの多角形の面向きを表していて、多角形の頂点と法線の角度は一定なので PN_{cos} = P_1 \cdot N_1 が判定情報になります。 多角形の頂点表 一つの多角形の頂点表は、正多面体の頂点の中で多角形の法線との内積が $PN_{cos}$ と一致するものになります。 多角形を描画するには、頂点の並びも重要です。正多面体の頂点の中から必要な頂点を抽出しただけでは順番がバラバラなので、並び替える必要があります。並び替えるには、多角形の頂点に隣接する頂点(頂点間の内積が $P_{cos}$ と一致)は2つしかないという性質を使います。 頂点 $p_0$ に対して、$p_1,p_2$ が頂点表から見つかったとすると p_1 \to p_0 \to p_2 または p_2 \to p_0 \to p_1 の並びしかありません。判定は、次の外積を求めて n = \frac{ \left( p_0 - p_1 \right) \times \left( p_2 - p_0 \right) }{ \left| \left( p_0 - p_1 \right) \times \left( p_2 - p_0 \right) \right| } $n$ が多角形の法線と一致すれば $\left( p_1 \to p_0 \to p_2 \right)$ で、一致しないと逆向き $\left( p_1 \to p_0 \to p_2 \right)$ になります。これで頂点 $p_0$ の次の頂点が分かり、その次も同様なので、一周するように並び替えることができます。 これで、多角形描画用の 3D データが出来上がります。 プログラム(HTML+JavaScript) 正多面体の計算に加えて、GIF による 3D 動画も作成しています。 動作確認は以下のブラウザで行ってます。 Google Chrome (Mac版) バージョン: 97.0.4692.71(Official Build) (x86_64) プログラムは短くないので折りたたみ RegularPolyhedron.html <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > <title>正多面体の生成プログラム</title> <style> body { margin-left: auto; margin-right: auto; width: 750px; } table { border: solid 1px gray; border-collaspe: collaspe; border-spacing: 0; } th, td { border: solid 1px gray; padding: 6px; } </style> </head> <body> <hr><h2>正4面体 (対も正4面体)</h2><hr> <blockquote id="RegularTetrahedron"> </blockquote> <hr><h2>正6面体 (対は正8面体)</h2><hr> <blockquote id="RegularHexahedron"> </blockquote> <hr><h2>正8面体 (対は正6面体)</h2><hr> <blockquote id="RegularOctahedron"> </blockquote> <hr><h2>正12面体 (対は正20面体)</h2><hr> <blockquote id="RegularDodecahedron"> </blockquote> <hr><h2>正20面体 (対は正12面体)</h2><hr> <blockquote id="RegularIcosahedron"> </blockquote> <script type="text/javascript"> <!-- /* ********************************** */ /* * 正多面体のデータを作る */ class MyRegularPolyhedron { /* ベクトルのソート用(逆順比較) */ static vecCmpR(a, b) { const va = a.vector; const vb = b.vector; const zc = va[2] - vb[2]; if (zc < 0) return +1; if (zc > 0) return -1; const yc = va[1] - vb[1]; if (yc < 0) return +1; if (yc > 0) return -1; const xc = va[0] - vb[0]; if (xc < 0) return +1; if (xc > 0) return -1; return 0; }; /* ベクトルの一致を調査: 演算誤差を考慮して、1.0 との一致比較はしない */ static vecEq = ((a, b) => (Math.abs(1.0 - a.dot(b)) < (1 / (1 << 20)))); /* ベクトルの内積と比較値の一致を調査: 演算誤差を考慮して、1.0 との一致比較はしない */ static vecDotEq = ((a, b, c) => (Math.abs(c - a.dot(b)) < (1 / (1 << 20)))); /* 重複を排除しながら追加する */ static vecAppend(l, v) { for (let q of l) if (MyRegularPolyhedron.vecEq(v, q)) return false; l.push(v); return true; }; /* 3ベクトルから残りのベクトルを求める */ static vecFrom(p1, p2, p3) { const S = MyRegularPolyhedron; const vecDotEq = S.vecDotEq; const vecs = [p1, p2, p3]; // 最初の2ベクトルから新しいベクトルを求めるための準備. const v1 = p1.normalize(); const v2 = p1.cross(p2).normalize(); const v3 = v1.cross(v2).normalize(); const x_cos = v1.dot(p3); const y_cos = v2.dot(p3); const z_cos = v3.dot(p3); const p_cos = p1.dot(p2); // 2ベクトルに隣接するベクトルの追加関数. const vecAdd = function(q1, q2) { const x = q1.normalize(); const y = q1.cross(q2).normalize(); const z = x.cross(y).normalize(); const v = x.mul(x_cos).add(y.mul(y_cos)).add(z.mul(z_cos)); S.vecAppend(vecs, v.normalize()); }; // 総当たりで求める. let last_veclen = 0; let new_veclen = vecs.length; do { for (let i1 = last_veclen; i1 < new_veclen; i1++) { const q1 = vecs[i1]; for (let i2 = 0; i2 < new_veclen; i2++) { if (i1 == i2) continue; const q2 = vecs[i2]; if (!vecDotEq(q1, q2, p_cos)) continue; vecAdd(q1, q2); vecAdd(q2, q1); }; }; last_veclen = new_veclen; new_veclen = vecs.length; } while (last_veclen != new_veclen); vecs.sort(S.vecCmpR); return vecs; }; // 面情報の一覧を作成. static polyFrom(vecs, norms, pp_cos, np_cos) { const vecEq = MyRegularPolyhedron.vecEq; const vecDotEq = MyRegularPolyhedron.vecDotEq; const nl = new Array(); for (const n of norms) { // 面に属する頂点を抽出(とインデックス) const plist = vecs.filter(v => vecDotEq(v, n, np_cos)); const pindex = [...Array(plist.length)].map((_, i) => vecs.indexOf(plist[i])); // 頂点の接続表を作成する. const plink = new Array(); for (let i1 = 0; i1 < pindex.length; i1++) { const vc = plist[i1]; let [vp, vn] = plist.filter(v => vecDotEq(v, vc, pp_cos)); if (!vecEq(n, (vc.sub(vp)).cross(vn.sub(vc)).normalize())) [vp, vn] = [vn, vp]; const pi = plist.indexOf(vp); const ni = plist.indexOf(vn); plink.push([pi, ni]); }; // 接続表から順に整列したインデックス表に変換. const ilist = new Array(); let p = 0; do { ilist.push(pindex[p]); p = plink[p][1]; } while (p != 0); nl.push(ilist); }; return nl; }; // 辺情報の一覧を作成. static edgeFrom(poly) { const edge = new Array(); for (let l of poly) { let p = l[l.length - 1]; for (let q of l) { const r = (Math.min(p, q) << 8) + Math.max(p, q); if (edge.indexOf(r) < 0) edge.push(r); p = q; }; }; return [...Array(edge.length)].map( (_, i) => [(edge[i] >> 8), (edge[i] & 255)]); }; // コンストラクタ. constructor(M) { const S = MyRegularPolyhedron; const vecEq = S.vecEq; const vecAppend = S.vecAppend; const vecFrom = S.vecFrom; this.S = S; this.vecEq = vecEq; this.vecAppend = vecAppend; this.vecFrom = vecFrom; const mIndex = { 4:1, 6:2, 8:3, 12:4, 20:5 }[M]; const V = [4, 4, 6, 8, 12, 20][mIndex^1]; const F = [3, 3, 3, 4, 3, 5][mIndex]; const N = [3, 3, 4, 3, 5, 3][mIndex]; const PiDivF = Math.PI / F; // π÷F const PiDivN = Math.PI / N; // π÷N // const D = 1 / Math.tan(PiDivF) / Math.tan(PiDivN); // 中心から面までの距離. // 最初の3頂点を求める. const rotXY = 2 * PiDivF; const cosXY = Math.cos(rotXY); const sinXY = Math.sin(rotXY); const cosZX = 2 * Math.pow(Math.cos(PiDivN) / Math.sin(PiDivF), 2) - 1; const sinZX = Math.sqrt(1 - Math.pow(cosZX, 2)); const p1 = new MyVector([0, 0, 1]); const p2 = new MyVector([sinZX, 0, cosZX]); const p3 = new MyVector([sinZX*cosXY, sinZX*sinXY, cosZX]); // 頂点の一覧を作成する. this.vertex = vecFrom(p1, p2, p3); // 最初の3法線を求める. const rm = new MyMatrix().rotateZ(rotXY); const n1 = p1.sub(p3).cross(p2.sub(p1), 1).normalize(); const n2 = rm.mul(n1).normalize(); const n3 = rm.mul(n2).normalize(); // 法線の一覧を作成する. this.normal = vecFrom(n1, n2, n3); const pp_cos = p1.dot(p2); // 頂点間の角度情報. const np_cos = p1.dot(n1); // 頂点・法線間の角度情報. const nn_cos = n1.dot(n2); // 法線間の角度情報. // 正多面体と対の両方の多角形データを作成する. this.polygon1 = S.polyFrom(this.vertex, this.normal, pp_cos, np_cos); this.polygon2 = S.polyFrom(this.normal, this.vertex, nn_cos, np_cos); // 正多面体と対の両方の辺データを作成する. this.edge1 = S.edgeFrom(this.polygon1); this.edge2 = S.edgeFrom(this.polygon2); }; }; /* ********************************** */ /* * ベクトル */ class MyVector { // コンストラクタ. constructor() { this.S = MyVector; this.M = MyMatrix; const args = [...Array(arguments.length)].map((_, i) => arguments[i]); let vector; if (args.length == 0) vector = Array(4).fill(0); else if (args.length == 1) { vector = args[0]; if (vector.length == 3) vector.push(1); while (vector.length < 4) vector.push(0); vector = vector.slice(0, 4); } else if (args.length == 3) { vector = args; vector.push(1); } else { vector = args; while (vector.length < 4) vector.push(0); vector = vector.slice(0, 4); } this.vector = vector; this.isMatrix = false; this.isVector = true; }; // ベクトルの大きさ (w=false で 3 要素) abs(w=false) { return Math.sqrt(this.dot(this, w)); } // 逆 (w=false で[3]はそのまま) neg(rhs, w=false) { const vl = this.vector; return new this.S([ -vl[0], -vl[1], -vl[2], (w ? -vl[3] : vl[3]), ]); }; // 和 (w=false で[3]はそのまま) add(rhs, w=false) { const vl = this.vector; const vr = rhs.vector; return new this.S([ vl[0] + vr[0], vl[1] + vr[1], vl[2] + vr[2], (w ? (vl[3] + vr[3]) : vl[3]), ]); }; // 差 (w=false で[3]はそのまま) sub(rhs, w=false) { const vl = this.vector; const vr = rhs.vector; return new this.S([ vl[0] - vr[0], vl[1] - vr[1], vl[2] - vr[2], (w ? (vl[3] - vr[3]) : vl[3]), ]); }; // 乗算 (w=false で 3 要素) mul(rhs, w=false) { return ((typeof(rhs) == "number") ? this.nMul(rhs, w) : this.mMul(rhs)); }; // 実数倍 (w=false で[3]はそのまま) nMul(n, w=false) { const vl = this.vector; return new this.S([ vl[0] * n, vl[1] * n, vl[2] * n, (w ? (vl[3] * n): vl[3]), ]); }; // 行列との積 (w=false で[3]はそのまま) mMul(rhs, w=false) { const vl = this.vector; const mr = rhs.matrix; return this.M([ vl[0] * mr[0] + vl[1] * mr[4] + vl[2] * mr[ 8] + vl[3] * mr[12], vl[0] * mr[1] + vl[1] * mr[5] + vl[2] * mr[ 9] + vl[3] * mr[13], vl[0] * mr[2] + vl[1] * mr[6] + vl[2] * mr[10] + vl[3] * mr[14], (w ? vl[0] * mr[3] + vl[1] * mr[7] + vl[2] * mr[11] + vl[3] * mr[15] : vl[3]), ]); }; // 内積 (w=false で 3 要素). dot(rhs, w=false) { const vl = this.vector; const vr = rhs.vector; return (vl[0] * vr[0] + vl[1] * vr[1] + vl[2] * vr[2] + (w ? vl[3] * vr[3] : 0)); }; // 外積 (3要素のみ) cross(rhs, w=0) { const vl = this.vector; const vr = rhs.vector; return new this.S([ vl[1] * vr[2] - vl[2] * vr[1], vl[2] * vr[0] - vl[0] * vr[2], vl[0] * vr[1] - vl[1] * vr[0], w, ]); }; // 実数除算 (w=false で[3]はそのまま). div(n, w=false) { const vl = this.vector; if (n != 0) return new this.S([ vl[0] / n, vl[1] / n, vl[2] / n, (w ? (vl[3] / n): vl[3]), ]); throw 'division by zero'; }; // 単位ベクトルを得る (w=false で 3 要素) normalize(w=false) { return this.div(this.abs(w), w); }; // 整数に近い数値を整数にする. fixInt() { const thr = 1 / (1 << 20); const fix = function(x) { const xi = Math.trunc(x); return ((Math.abs(x - xi) < thr) ? xi : x); }; const vector = this.vector; return new this.S([ fix(vector[0]), fix(vector[1]), fix(vector[2]), fix(vector[3]), ]); }; }; /* * 行列 */ class MyMatrix { // コンストラクタ. constructor() { const S = MyMatrix; this.S = S; this.V = MyVector; const args = [...Array(arguments.length)].map((_, i) => arguments[i]); let matrix; if (args.length == 0) matrix = S.identity(); else if (args.length == 1) matrix = args[0]; else if (args.length == 4) { matrix = [ args[0], args[1], 0, 0, args[2], args[3], 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, ]; } else if (args.length == 9) { matrix = [ args[0], args[1], args[2], 0, args[3], args[4], args[5], 0, args[6], args[7], args[8], 0, 0, 0, 0, 1, ]; } else { matrix = args; while (matrix.length < 16) matrix.push(0); matrix = matrix.slice(0, 16); } this.matrix = matrix; this.isMatrix = true; this.isVector = false; }; // 単位行列. static identity() { return [...Array(16)].map((_, i) => (((i % 5) == 0) ? 1 : 0)); }; // 転置. transpose() { const ml = this.matrix; return new this.S([ ml[0], ml[4], ml[ 8], ml[12], ml[1], ml[5], ml[ 9], ml[13], ml[2], ml[6], ml[10], ml[14], ml[3], ml[7], ml[11], ml[15], ]); }; // 和. add(rhs) { const mr = rhs.matrix; return new this.S(this.matrix.map((l, i) => l + mr[i])); }; // 差. sub(rhs) { const mr = rhs.matrix; return new this.S(this.matrix.map((l, i) => l - mr[i])); }; // 配列[16]との積で配列[16]を返す. aMulI(mr) { const ml = this.matrix; return [ ml[ 0] * mr[0] + ml[ 1] * mr[4] + ml[ 2] * mr[ 8] + ml[ 3] * mr[12], ml[ 0] * mr[1] + ml[ 1] * mr[5] + ml[ 2] * mr[ 9] + ml[ 3] * mr[13], ml[ 0] * mr[2] + ml[ 1] * mr[6] + ml[ 2] * mr[10] + ml[ 3] * mr[14], ml[ 0] * mr[3] + ml[ 1] * mr[7] + ml[ 2] * mr[11] + ml[ 3] * mr[15], ml[ 4] * mr[0] + ml[ 5] * mr[4] + ml[ 6] * mr[ 8] + ml[ 7] * mr[12], ml[ 4] * mr[1] + ml[ 5] * mr[5] + ml[ 6] * mr[ 9] + ml[ 7] * mr[13], ml[ 4] * mr[2] + ml[ 5] * mr[6] + ml[ 6] * mr[10] + ml[ 7] * mr[14], ml[ 4] * mr[3] + ml[ 5] * mr[7] + ml[ 6] * mr[11] + ml[ 7] * mr[15], ml[ 8] * mr[0] + ml[ 9] * mr[4] + ml[10] * mr[ 8] + ml[11] * mr[12], ml[ 8] * mr[1] + ml[ 9] * mr[5] + ml[10] * mr[ 9] + ml[11] * mr[13], ml[ 8] * mr[2] + ml[ 9] * mr[6] + ml[10] * mr[10] + ml[11] * mr[14], ml[ 8] * mr[3] + ml[ 9] * mr[7] + ml[10] * mr[11] + ml[11] * mr[15], ml[12] * mr[0] + ml[13] * mr[4] + ml[14] * mr[ 8] + ml[15] * mr[12], ml[12] * mr[1] + ml[13] * mr[5] + ml[14] * mr[ 9] + ml[15] * mr[13], ml[12] * mr[2] + ml[13] * mr[6] + ml[14] * mr[10] + ml[15] * mr[14], ml[12] * mr[3] + ml[13] * mr[7] + ml[14] * mr[11] + ml[15] * mr[15], ]; }; // 乗算で更新. aMulUpdate(mr) { this.matrix = this.aMulI(mr); }; // 配列[4]との積で配列[4]を返す. vMulI(vr) { const ml = this.matrix; return [ ml[ 0] * vr[0] + ml[ 1] * vr[1] + ml[ 2] * vr[ 2] + ml[ 3] * vr[3], ml[ 4] * vr[0] + ml[ 5] * vr[1] + ml[ 6] * vr[ 2] + ml[ 7] * vr[3], ml[ 8] * vr[0] + ml[ 9] * vr[1] + ml[10] * vr[ 2] + ml[11] * vr[3], ml[12] * vr[0] + ml[13] * vr[1] + ml[14] * vr[ 2] + ml[15] * vr[3], ]; }; // 実数倍の配列を返す. nMulI(r) { return [ ml[ 0] * r, ml[ 1] * r, ml[ 2] * r, ml[ 3] * r, ml[ 4] * r, ml[ 5] * r, ml[ 6] * r, ml[ 7] * r, ml[ 8] * r, ml[ 9] * r, ml[10] * r, ml[11] * r, ml[12] * r, ml[13] * r, ml[14] * r, ml[15] * r, ]; }; // 積. mul(rhs) { if (rhs.isMatrix) return new this.S(this.aMulI(rhs.matrix)); if (rhs.isVector) return new this.V(this.vMulI(rhs.vector)); if (typeof(rhs) == 'number') return new this.S(this.nMulI(rhs)); throw 'unknown parameter.'; }; // 行列式を得る. det() { const rval = 0; const [m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33, ] = this.matrix; rval += m00 * m11 * m22 * m33; rval -= m00 * m11 * m23 * m32; rval += m00 * m12 * m23 * m31; rval -= m00 * m12 * m21 * m33; rval += m00 * m13 * m21 * m32; rval -= m00 * m13 * m22 * m31; rval += m01 * m12 * m20 * m33; rval -= m01 * m12 * m23 * m30; rval += m01 * m13 * m22 * m30; rval -= m01 * m13 * m20 * m32; rval += m01 * m10 * m23 * m32; rval -= m01 * m10 * m22 * m33; rval += m02 * m13 * m20 * m31; rval -= m02 * m13 * m21 * m30; rval += m02 * m10 * m21 * m33; rval -= m02 * m10 * m23 * m31; rval += m02 * m11 * m23 * m30; rval -= m02 * m11 * m20 * m33; rval += m03 * m10 * m22 * m31; rval -= m03 * m10 * m21 * m32; rval += m03 * m11 * m20 * m32; rval -= m03 * m11 * m22 * m30; rval += m03 * m12 * m21 * m30; rval -= m03 * m12 * m20 * m31; return rval; }; // 逆行列を得る. inverse() { const det = this.det(); return ((det != 0) ? this.mul(1 / det) : undefined); }; // 単位行列を設定する. loadIdentity() { this.matrix = this.S.identity(); return this; }; // X 軸回転を乗算. rotateX(rad) { const rc = Math.cos(rad); const rs = Math.sin(rad); this.aMulUpdate([ 1, 0, 0, 0, 0, rc, -rs, 0, 0, rs, +rc, 0, 0, 0, 0, 1, ]); return this; }; degRotateX(deg) { return this.rotateX(deg * Math.PI / 180); }; // Y 軸回転を乗算. rotateY(rad) { const rc = Math.cos(rad); const rs = Math.sin(rad); this.aMulUpdate([ +rc, 0, +rs, 0, + 0, 1, 0, 0, -rs, 0, +rc, 0, + 0, 0, 0, 1, ]); return this; }; degRotateY(deg) { return this.rotateY(deg * Math.PI / 180); }; // Z 軸回転を乗算. rotateZ(rad) { const rc = Math.cos(rad); const rs = Math.sin(rad); this.aMulUpdate([ rc, -rs, 0, 0, rs, +rc, 0, 0, +0, 0, 1, 0, +0, 0, 0, 1, ]); return this; }; degRotateZ(deg) { return this.rotateZ(deg * Math.PI / 180); }; // 任意軸回転を乗算. rotate(rad, vec) { const [x, y, z, w] = vec.normalize().vector; const rc = Math.cos(rad); const rs = Math.sin(rad); const xs = x * sin; const ys = y * sin; const zs = z * sin; const nc1 = 1 - rc; const x1c = x * nc1; const y1c = y * nc1; const z1c = z * nc1; this.aMulUpdate([ x * x1c + rc, x * y1c - zs, x * z1c + ys, 0, y * x1c + zs, y * y1c + rc, y * z1c - xs, 0, z * x1c - ys, z * y1c + xs, z * z1c + rc, 0, 0, 0, 0, 1, ]); return this; }; degRotate(deg, vec) { return this.rotate(deg * Math.PI / 180, vec); }; // 平行移動を乗算. translate(x, y, z) { this.aMulUpdate([ 1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1, ]); return this; }; // 各軸に係数を乗算. scale(x, y, z, w=1) { this.aMulUpdate([ x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, w, ]); return this; }; }; /* * 3D オブジェクト用行列 */ class MyObject3D extends MyMatrix { // コンストラクタ. constructor(...args) { super(...args); this.S = MyObject3D; this.matrix_stack = []; this.loadIdentity(); }; // 行列を複写してスタックに積み上げる. pushMatrix(m) { this.matrix_stack.push(this.matrix.map((d) => d)); if (m != undefined) this.aMulUpdate(m.matrix); return this; }; // スタックから行列を取り出す. popMatrix() { this.matrix = this.matrix_stack.pop(); return this; }; }; /* * 透視変換用行列 */ class MyProjection3D extends MyObject3D { // コンストラクタ. constructor(...args) { super(...args); }; // 拡大・縮小表示設定を乗算 setZoom(zoom) { this.aMulUpdate([ zoom, 0, 0, 0, + 0, zoom, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, ]); return this; }; // 奥行範囲設定を乗算. setOrtho(near, far) { const fmn = far - near; const m33 = 2 / fmn; const m34 = - (far + near) / fmn; this.aMulUpdate([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, m33, m34, 0, 0, 0, 1, ]); return this; }; // 透視図設定を乗算. setPerspective(znear, zfar, zoom, aspect) { const zfmn = zfar - znear; const m11 = zoom * aspect; const m22 = zoom; const m33 = (zfar + znear) / zfmn; const m34 = - (2 * zfar * znear) / zfmn; this.aMulUpdate([ m11, 0, 0, 0, + 0, m22, 0, 0, + 0, 0, m33, m34, + 0, 0, 1, 0, ]); return this; }; }; /* * ビューポート変換用行列 */ class MyViewport { // コンストラクタ. constructor(width, height, depth) { this.width = width; this.height = height; this.depth = depth; this.half_width = width / 2; this.half_height = height / 2; this.half_depth = depth / 2; }; getScreen(vertex) { const hw = this.half_width; const hh = this.half_height; const hd = this.half_depth; const vector = vertex.vector; const w = vector[3]; return new MyVector( Math.round(hw + vector[0] / w * hw), Math.round(hh - vector[1] / w * hh), Math.round(hd + vector[2] / w * hd), ); }; }; /* * 頂点処理用 */ class MyVertex3D extends MyVector { // コンストラクタ. constructor(...args) { super(...args); const [x, y, z, w] = this.vector; const wpx = w + x; const wmx = w - x; const wpy = w + y; const wmy = w - y; const wpz = w + z; const wmz = w - z; this.wclip = [wpz, wpx, wpy, wmy, wmx, wmz]; this.fclip = (((wpz < 0) ? 0x01 : 0) | ((wpx < 0) ? 0x02 : 0) | ((wpy < 0) ? 0x04 : 0) | ((wmy < 0) ? 0x08 : 0) | ((wmx < 0) ? 0x10 : 0) | ((wmz < 0) ? 0x20 : 0)); }; }; /* ********************************** */ /* * GIF 形式データを生成するクラス */ class MyGIF { static signature = [0x47, 0x49, 0x46]; static version87a = [0x38, 0x37, 0x61]; static version89a = [0x38, 0x39, 0x61]; // コンストラクタ. constructor(width, height, resolution, background, global_color, aspect) { const S = MyGIF; this.S = S; this.signature = S.signature; this.version = S.version89a; this.logicalScreen = S.createLogicalScreenDescriptor( width, height, resolution, background, global_color, aspect); this.descriptor = []; }; // GIF形式のバイナリを取得する. get_binary() { return [ this.signature, this.version, this.logicalScreen.get_binary(), this.descriptor.map((d, _) => d.get_binary()).flat(), ].flat(); }; // 任意の Descriptor を追加する. appendDescriptor(descriptor) { this.descriptor.push(descriptor); }; // Image Descriptor を追加する. appendImageDescriptor(x, y, w, h, data, color, interlace) { this.descriptor.push(this.S.createImageDescriptor(x, y, w, h, data, color, interlace)); }; // Graphic Control Extension を追加する. appendGraphicControlExtension(delay, method, transparent, input) { this.descriptor.push(this.S.createGraphicControlExtension(delay, method, transparent, input)); }; // Comment Extension を追加する. appendCommentExtension(text) { this.descriptor.push(this.S.createCommentExtension(text)); }; // Plain Text Extension を追加する. appendPlainTextExtension(x, y, w, h, cw, ch, fc, bc, text) { this.descriptor.push(this.S.createPlainTextExtension(x, y, w, h, cw, ch, fc, bc, text)); }; // Application Extension を追加する. appendApplicationExtension(id, code, data) { this.descriptor.push(this.S.createApplicationExtension(id, code, data)); }; // アニメーションのループ回数を設定する. appendApplicationExtensionForLoop(count) { this.appendApplicationExtension('NETSCAPE', '2.0', [1, (count & 0xff), ((count >> 8) & 0xff)]); }; // Trailer を追加する. appendTrailer() { this.descriptor.push(this.S.createTrailer()); }; // 8 ビット形式の取得. static uint8el(x) { return ((!x ? 0 : x) & 0xff); }; // 16 ビット・リトル・エンディアン形式の取得. static uint16el(x) { const y = (!x ? 0 : x); return [(y & 0xff), ((y >> 8) & 0xff)]; }; // 色情報の過不足を調整. static fixColorTable(depth, color) { const colen = (1 << (depth + 1)) * 3; let table = color.flat(); return ((table.length > colen) ? table.slice(0, colen) : table.concat(Array(colen - table.length).fill(0))); }; // 文字の UTF-8 バイナリ化. static charToUTF8(c) { if (c < 0x0080) return c; if (c < 0x0800) return [ (0xc0 | (c >> 6)), (0x80 | (c & 0x3f)), ]; return [ (0xe0 | (c >> 12)), (0x80 | ((c >> 6) & 0x3f)), (0x80 | (c & 0x3f)), ]; }; static stringToUTF8(s) { return [...Array(s.length)].map((_, i) => MyGIF.charToUTF8(s.charCodeAt(i))).flat(); }; // Data Sub-blocks の生成. static createDataSubBlocks(data) { let block = []; let pos = 0; let length = data.length; while (length) { let slen = (length < 256) ? length : 255; block.push(slen); block.push(data.slice(pos, pos + slen)); pos += slen; length -= slen; }; block.push(0); return block.flat(); }; // Logical Screen Descriptor の生成. static createLogicalScreenDescriptor(width, height, resolution, background, color, sort, aspect) { const S = MyGIF; const D = { logical_screen_width: width, logical_screen_height: height, size_of_global_color_table: 0, sort_flag: sort, color_resolution: resolution, global_color_table_flag: false, background_color_index: background, pixel_aspect_ratio: ((aspect == undefined) ? 49 : aspect), global_color_table: undefined, set_color: (function(table) { const flag = (table != undefined); D.size_of_global_color_table = (!flag ? 0 : (31 - Math.clz32(table.length-1))); D.global_color_table_flag = flag; D.global_color_table = table; }), get_binary: (function() { const sgct = (!D.size_of_global_color_table ? 0 : (7 & D.size_of_global_color_table)); const sort = (!D.sort_flag ? 0 : 1); const res = (!D.color_resolution ? 0 : (7 & D.color_resolution)); const gcf = (!D.global_color_table_flag ? 0 : 1); const bgc = (!D.background_color_index ? 0 : D.background_color_index); const aspect = ((D.pixel_aspect_ratio == undefined) ? 49 : D.pixel_aspect_ratio); const color = (!gcf ? [] : S.fixColorTable(sgct, D.global_color_table)); return [ S.uint16el(D.logical_screen_width), S.uint16el(D.logical_screen_height), (sgct | (sort << 3) | (res << 4) | (gcf << 7)), S.uint8el(bgc), S.uint8el(aspect), color.map((v, _) => S.uint8el(v)), ].flat(); }), }; D.set_color(color); return D; }; // Image Descriptor の生成. static createImageDescriptor(x, y, w, h, data, color, interlace, sort) { const S = MyGIF; const D = { descriptor: 0x2c, image_left_position: x, image_top_position: y, image_width: w, image_height: h, size_of_local_color_table: 0, sort_flag: sort, interlace_flag: interlace, local_color_table_flag: false, local_color_table: undefined, table_based_image_data: S.createTableBasedImageData(data), set_color_table: (function(table) { const flag = (table != undefined); D.size_of_local_color_table = (!flag ? 0 : (31 - Math.clz32(table.length-1))); D.local_color_table_flag = flag; D.local_color_table = table; }), get_binary: (function() { const slct = (!D.size_of_local_color_table ? 0 : (7 & D.size_of_local_color_table)); const sort = (!D.sort_flag ? 0 : 1); const interlace = (!D.interlace ? 0 : 1); const lcf = (!D.local_color_table_flag ? 0 : 1); const color = (!lcf ? [] : S.fixColorTable(slct, D.local_color_table)); return [ D.descriptor, S.uint16el(D.image_left_position), S.uint16el(D.image_top_position), S.uint16el(D.image_width), S.uint16el(D.image_height), (slct | (sort << 5) | (interlace << 6) | (lcf << 7)), color.map((v, _) => S.uint8el(v)), D.table_based_image_data.lzw_minimum_code_size, D.table_based_image_data.data, ].flat(); }), }; D.set_color_table(color); return D; }; // Table Based Image の生成 (LZW圧縮) static createTableBasedImageData(source) { const S = MyGIF; const source_size = source.length; // const src_max = Math.max.apply(null, source); // 'Maximum call stack size exceeded' がでる. const src_max = function() { let m = 0; for (const d of source) if (m < d) m = d; return m; }(); const src_max_clz = (32 - Math.clz32(src_max)); const bits_init = ((src_max_clz < 2) ? 2 : src_max_clz); let bits_curr = (bits_init + 1); const code_base = (1 << bits_init); const code_clear = code_base; const code_end = code_base + 1; const code_max = ((1 << 12) - 1); let code_curr = code_end; let code_step = (1 << bits_curr); const tree = [...Array(code_max + 2)].map((_, i) => ({ code: i, next: -1, down: -1, data: 0 })); const buffer = []; let bs_pos = 0; const write = function(data) { const bits = bits_curr; let idxs = (bs_pos >> 3); const idxe = ((bs_pos + bits - 1) >> 3); data <<= (bs_pos & 7); if (idxs < buffer.length) { buffer[idxs] |= (data & 0xff); data >>= 8; idxs++; }; while (idxs <= idxe) { buffer.push(data & 0xff); data >>= 8; idxs++; }; bs_pos += bits; }; write(code_clear); if (!source_size) { write(code_end); return { lzw_minimum_code_size: bits_init, data: S.createDataSubBlocks(buffer), }; }; let siter = source.values(); let sdat = siter.next(); lzw: for (;;) { let data, next; let node = tree[(data = sdat.value)]; scan: for (;;) { if ((sdat = siter.next()).done) { write(node.code); break lzw; }; data = sdat.value; if ((next = tree[node.down]) == undefined) break; while (data != next.data) if ((next = tree[next.next]) == undefined) break scan; node = next; }; write(node.code); { const next = tree[++code_curr]; // next.code = code_curr; next.next = node.down; next.down = -1; next.data = data; node.down = code_curr; } if (code_curr < code_step) continue; if (code_curr < code_max) { bits_curr++; if ((code_step <<= 1) < code_max) continue; code_step = code_max; continue; }; write(code_clear); bits_curr = bits_init + 1; code_step = (1 << bits_curr); code_curr = code_end; for (let i = 0; i < code_base; i++) { const node = tree[i]; // node.code = i; node.next = -1; node.down = -1; // node.data = 0; }; }; write(code_end); return { lzw_minimum_code_size: bits_init, data: S.createDataSubBlocks(buffer), }; }; // Graphic Control Extension の生成. static createGraphicControlExtension(delay, method, transparent, input) { const S = MyGIF; const D = { descriptor: 0x21, label: 0xf9, disposal_method: method, user_input_flag: input, transparent_color_flag: false, delay_time: delay, transparent_color_index: 0, set_transparent: (function(index) { D.transparent_color_flag = (index != undefined); D.transparent_color_index = (!index ? 0 : index); }), get_binary: (function() { const method = (!D.disposal_method ? 0 : (7 & D.disposal_method)); const input = (!D.user_input_flag ? 0 : 1); const ftrans = (!D.transparent_color_flag ? 0 : 1); return [ D.descriptor, D.label, 4, /* Data Sub-blocks */ ((method << 3) | (input << 6) | (ftrans << 7)), S.uint16el(D.delay_time), S.uint8el(D.transparent_color_index), 0, /* Data Sub-blocks */ ].flat(); }), }; D.set_transparent(transparent); return D; }; // Comment Extension の生成. static createCommentExtension(text) { const S = MyGIF; const D = { descriptor: 0x21, label: 0xfe, text: text, get_binary: (() => [ D.descriptor, D.label, S.createDataSubBlocks(S.stringToUTF8(D.tex)), ].flat()), }; return D; }; // Plain Text Extension の生成. static createPlainTextExtension(x, y, w, h, cw, ch, fc, bc, text) { const S = MyGIF; const D = { descriptor: 0x21, label: 0x01, text_grid_left_position: x, text_grid_top_position: y, text_grid_width: w, text_grid_height: h, character_cell_width: cw, character_cell_height: ch, text_foreground_color_index: fc, text_background_color_index: bc, plain_text_data: text, get_binary: (() => [ D.descriptor, D.label, 12, /* Data Sub-blocks */ S.uint16el(D.text_grid_left_position), S.uint16el(D.text_grid_top_position), S.uint16el(D.text_grid_width), S.uint16el(D.text_grid_height), S.uint8el(D.character_cell_width), S.uint8el(D.character_cell_height), S.uint8el(D.text_foreground_color_index), S.uint8el(D.text_background_color_index), S.createDataSubBlocks(S.stringToUTF8(D.plain_text_data)), ].flat()), }; return D; }; // Application Extension の生成. static createApplicationExtension(id, code, data) { const S = MyGIF; const D = { descriptor: 0x21, label: 0xff, application_identifier: id, application_authentication_code: code, application_data: data, get_binary: (() => [ D.descriptor, D.label, 11, /* Data Sub-blocks */ S.stringToUTF8(D.application_identifier + ' ').slice(0, 8), S.stringToUTF8(D.application_authentication_code + ' ').slice(0, 3), S.createDataSubBlocks(D.application_data.map((v, _) => S.uint8el(v))), ].flat()), }; return D; }; // Trailer の生成. static createTrailer() { return { descriptor: 0x3b, get_binary: (() => 0x3b), }; }; }; /* **************************************** */ /* * Base 64 符号化 */ class MyBase64 { static EncodeTable = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; static encode(b) { const table = MyBase64.EncodeTable; const blen = b.length; const brem = blen % 3; const bcnt = blen - brem; let s = ''; let i = 0; while (i < bcnt) { const d0 = b[i++]; const d1 = b[i++]; const d2 = b[i++]; const d = (d0 << 16) | (d1 << 8) | d2; s += table[(d >> 18) & 0x3f]; s += table[(d >> 12) & 0x3f]; s += table[(d >> 6) & 0x3f]; s += table[d & 0x3f]; }; if (brem) { const b2 = (brem == 2); const d0 = b[i++]; const d1 = (b2 ? b[i++] : 0); const d = (d0 << 16) | (d1 << 8); s += table[(d >> 18) & 0x3f]; s += table[(d >> 12) & 0x3f]; s += (b2 ? table[(d >> 6) & 0x3f] : '='); s += '='; }; return s; }; }; /* ********************************** */ /* * GIF 画像生成用 */ class MyPixelBuffer { static grayScaleColor = function(count) { const m = 255; const n = count - 1; return [...Array(count)].map(function(_, k) { const l = Math.trunc((k * m) / n); return [l, l, l]; }); }; static grayScaleColor4 = MyPixelBuffer.grayScaleColor(4); static grayScaleColor8 = MyPixelBuffer.grayScaleColor(8); static grayScaleColor256 = MyPixelBuffer.grayScaleColor(256); static simpleColorBase = [...Array(8)].map((_, i) => Math.trunc(i * 255 / 7)); static simpleColor = [...Array(256)].map((_, i) => [ MyPixelBuffer.simpleColorBase[(i >> 5) & 7], MyPixelBuffer.simpleColorBase[(i >> 2) & 7], [0, 85, 170, 255][i & 3], ]); static interlaceMap = new Map(); static createInterlaceTable(h) { const m = MyPixelBuffer.interlaceMap; if (m.has(h)) return m.get(h); const ph = [ [0, 8, ((h + 7) >> 3)], [4, 8, ((h + 3) >> 3)], [2, 4, ((h + 1) >> 2)], [1, 2, (h >> 1)], ]; const tab = [...Array(4)].map( (_, i) => [...Array(ph[i][2])].map( (_, j) => ph[i][0] + ph[i][1] * j)).flat(); m.set(h, tab); return tab; }; // コンストラクタ. constructor(color, width, height, defcol=0) { this.S = MyPixelBuffer; this.GIF = MyGIF; this.color = color; this.width = width; this.height = height; this.pixel = Array(width * height).fill(defcol); }; // 点を打つ. setPoint(x, y, c) { const w = this.width; if ((0 <= x) && (x < w) && (0 <= y) && (y < this.height)) { this.pixel[p] = c; } }; // 線を引く. setLine(x1, y1, x2, y2, c, sep) { const dx = Math.trunc(Math.abs(x1 - x2)); const dy = Math.trunc(Math.abs(y1 - y2)); if (!dx) this.setVirticalLine(x1, y1, y2, c); else if (!dy) this.setHorizontalLine(x1, x2, y1, c); else if (dx >= dy) this.setLineH(x1, y1, x2, y2, dx, dy, c, sep); else this.setLineV(x1, y1, x2, y2, dx, dy, c, sep); }; setLineH(x1, y1, x2, y2, dx, dy, c, sep) { const S = this.S; const w = this.width; const h = this.height; let xs, ys, xe, ye; if (x1 < x2) { xs = x1; ys = y1; xe = x2; ye = y2; } else { xs = x2; ys = y2; xe = x1; ye = y1; }; if ((xe < 0) || (w <= xs)) return; const ay = ((ys < ye) ? +1 : -1); const ap = (ay < 0) ? -w: +w; let k = (dy >> 1); dx++; if (sep) { dy++; k = 0; }; if (xs < 0) { const kt = k - (dy * xs); const yh = Math.trunc(kt / dx); xs = 0; ys += ((ay < 0) ? -yh : +yh); k = (kt % dx); }; if (w <= xe) xe = w - 1; const yt = Math.min(ys, ye); const yb = Math.max(ys, ye); if ((yb < 0) || (h <= yt)) return; if (ay < 0) { if (ye < 0) ye = 0; if (h <= ys) { const yh = ys - h + 1; const kt = k + (dx * yh); const xw = Math.trunc(kt / dy); xs += xw; ys = h - 1; k = kt % dy; }; } else { if (h <= ye) ye = h - 1; if (ys < 0) { const kt = k - (dx * ys); const xw = Math.trunc(kt / dy); xs += xw; ys = 0; k = kt % dy; }; }; ye += ay; const pixel = this.pixel; let p = ys * w + xs; while (xs++ <= xe) { pixel[p++] = c; if ((k += dy) >= dx) { k -= dx; p += ap; if ((ys += ay) == ye) break; }; }; }; setLineV(x1, y1, x2, y2, dx, dy, c, sep) { const S = this.S; const w = this.width; const h = this.height; let xs, ys, xe, ye; if (y1 < y2) { xs = x1; ys = y1; xe = x2; ye = y2; } else { xs = x2; ys = y2; xe = x1; ye = y1; }; if ((ye < 0) || (h <= ys)) return; const ax = ((xs < xe) ? +1 : -1); let k = (dy >> 1); dy++; if (sep) { dx++; k = 0; }; if (ys < 0) { const kt = k - (dx * ys); const xw = Math.trunc(kt / dy); xs += ((ax < 0) ? -xw : +xw); ys = 0; k = (kt % dy); }; if (h <= ye) ye = h - 1; const xl = Math.min(xs, xe); const xr = Math.max(xs, xe); if ((xr < 0) || (w <= xl)) return; if (ax < 0) { if (xe < 0) xe = 0; if (w <= xs) { const xw = xs - w + 1; const kt = k + (dy * xw); const yh = Math.trunc(kt / dx); xs = w - 1; ys += yh; k = kt % dx; }; } else { if (w <= xe) xe = w - 1; if (xs < 0) { const kt = k - (dy * xs); const yh = Math.trunc(kt / dx); xs = 0; ys += yh; k = kt % dx; }; }; xe += ax; const pixel = this.pixel; let p = ys * w + xs; while (ys++ <= ye) { pixel[p] = c; p += w; if ((k += dx) >= dy) { k -= dy; p += ax; if ((xs += ax) == xe) break; }; }; }; // 水平線を引く. setHorizontalLine(x1, x2, y, c) { const w = this.width; const h = this.height; const pixel = this.pixel; const xs = Math.trunc(Math.min(x1, x2)); const xe = Math.trunc(Math.max(x1, x2)); if ((xe < 0) || (w <= xs) || (y < 0) || (h <= y)) return; const us = Math.trunc(Math.max(xs, 0)); const ue = Math.trunc(Math.min(xe, w-1)); const yp = y * w; pixel.fill(c, yp + us, yp + ue + 1); } // 垂直線を引く. setVirticalLine(x, y1, y2, c) { const w = this.width; const h = this.height; const pixel = this.pixel; const ys = Math.trunc(Math.min(y1, y2)); const ye = Math.trunc(Math.max(y1, y2)); if ((x < 0) || (w <= x) || (ye < 0) || (h <= ys)) return; const vs = Math.trunc(Math.max(ys, 0)); const ve = Math.trunc(Math.min(ye, h-1)); const q = ve * w + x; for (let p = vs * w + x; p <= q; p += w) pixel[p] = c; } // 四角形を描く. setBox(x1, y1, x2, y2, c) { const xs = Math.trunc(Math.min(x1, x2)); const xe = Math.trunc(Math.max(x1, x2)); const ys = Math.trunc(Math.min(y1, y2)); const ye = Math.trunc(Math.max(y1, y2)); this.setHorizontalLine(xs, xe, ys, c); if (ys != ye) { this.setVirticalLine(xs, ys+1, ye-1, c); if (xs != xe) this.setVirticalLine(xe, ys+1, ye-1, c); this.setHorizontalLine(xs, xe, ye, c); }; }; // 四角形で塗りつぶす. setBoxFill(x1, y1, x2, y2, c) { const w = this.width; const h = this.height; const pixel = this.pixel; const xs = Math.min(x1, x2); const xe = Math.max(x1, x2); const ys = Math.min(y1, y2); const ye = Math.max(y1, y2); if ((xe < 0) || (w <= xs) || (ye < 0) || (h <= ys)) return; const us = Math.trunc(Math.max(xs, 0)); const ue = Math.trunc(Math.min(xe, w-1)); const vs = Math.trunc(Math.max(ys, 0)); const ve = Math.trunc(Math.min(ye, h-1)); const nu = (ue - us) + 1; const nv = (ve - vs) + 1; let vp = vs * w + us; const vq = nv * w + vp; for (; vp < vq; vp += w) pixel.fill(c, vp, vp + nu); }; // 円描画用の座標情報を生成する. static getCirclePos(radius) { if (radius <= 0) return [[0, 0]]; let x = radius; let y = 0; let s = 0; let t = 1; let u = (radius << 1) - 1; let v = u; /* 大きな円は(上下左右)端の直線を少し短くする */ u -= ((u >> 2) + (u >> 3) + (u >> 4)); let otmp = new Array(); const out1 = new Array(); const out2 = new Array(); while (x > y) { otmp.push(y); out2.push([x]); y = y + 1; s = s + t; t = t + 2; if (s < u) continue; out1.push(otmp); x = x - 1; v = v - 2; u = u + v; otmp = new Array(); }; if (x == y) out2.push([x]); if (otmp.length) out1.push(otmp); out1.reverse(); return out2.concat(out1); }; // 円を描く. setCircle(x, y, r, c) { const w = this.width; const h = this.height; if (((x + r + 1) < 0) || (w < (x - r)) || ((y + r + 1) < 0) || (h < (y - r))) return; const pixel = this.pixel; const ypos = this.S.getCirclePos(r); const ylen = ypos.length; { let yi = 0; let ys = y; if (ys < 0) { yi = -ys; ys = 0; }; let yp = ys * w; while (yi < ylen) { if (ys >= h) break; for (const xp of ypos[yi]) { const xl = x - xp; const xr = x + xp; if (w <= xl) continue; if (xr < 0) continue; if (xr < w) pixel[yp + xr] = c; if (0 <= xl) pixel[yp + xl] = c; }; yi++; ys++; yp += w; }; }; { let yi = 0; let ys = y; if (ys >= h) { yi += (ys - h + 1); ys = h - 1; }; let yp = ys * w; while (yi < ylen) { if (ys < 0) break; for (const xp of ypos[yi]) { const xl = x - xp; const xr = x + xp; if (w <= xl) continue; if (xr < 0) continue; if (xr < w) pixel[yp + xr] = c; if (0 <= xl) pixel[yp + xl] = c; }; ys--; yi++; yp -= w; }; }; }; // 円で塗りつぶす. setCircleFill(x, y, r, c) { const w = this.width; const h = this.height; if (((x + r + 1) < 0) || (w < (x - r)) || ((y + r + 1) < 0) || (h < (y - r))) return; const pixel = this.pixel; const ypos = this.S.getCirclePos(r); const ylen = ypos.length; { let yi = 0; let ys = y; if (ys < 0) { yi = -ys; ys = 0; }; let yp = ys * w; while (yi < ylen) { if (ys >= h) break; const yl = ypos[yi]; const xp = yl[yl.length - 1]; let xl = x - xp; let xr = x + xp; if ((0 <= xr) && (xl < w)) { if (xl < 0) xl = 0; if (xr >= w) xr = w - 1; pixel.fill(c, yp + xl, yp + xr + 1); } yi++; ys++; yp += w; }; }; { let yi = 0; let ys = y; if (ys >= h) { yi += (ys - h + 1); ys = h - 1; }; let yp = ys * w; while (yi < ylen) { if (ys < 0) break; const yl = ypos[yi]; const xp = yl[yl.length - 1]; let xl = x - xp; let xr = x + xp; if ((0 <= xr) && (xl < w)) { if (xl < 0) xl = 0; if (xr >= w) xr = w - 1; pixel.fill(c, yp + xl, yp + xr + 1); } ys--; yi++; yp -= w; }; }; }; // 凸多角形を描画する. setPolygon(vertices, c) { const w = this.width; const h = this.height; const pixel = this.pixel; const vlen = vertices.length; if (!vlen) return; let xmin = w, xmax = -1; let ymin = h, ymax = -1; for (const [x, y] of vertices) { xmin = Math.min(xmin, x); xmax = Math.max(xmax, x); ymin = Math.min(ymin, y); ymax = Math.max(ymax, y); }; xmin = Math.trunc(xmin); xmax = Math.trunc(xmax); ymin = Math.trunc(ymin); ymax = Math.trunc(ymax); if ((xmax < 0) || (w <= xmin) || (ymax < 0) || (h <= ymin)) return; const raster = [...Array(h)].map(() => []); let [nx, ny] = vertices[vlen - 1]; for (const np of vertices) { const lx = nx; const ly = ny; [nx, ny] = np; const ay = (ny < ly) ? -1 : +1; const dx = nx - lx; const dy = (ay < 0) ? (ly - ny) : (ny - ly); if (dy == 0) { // if ((0 <= ly) && (ly < h)) // raster[ly].push(lx, nx); continue; }; let ys = ly; let ye = ny; let yp = 0; if (ay >= 0) { if ((ye < 0) || (h <= ys)) continue; if (ys < 0) { yp -= ys; ys = 0; }; if (ye >= h) ye = h - 1; } else { if ((ys < 0) || (h <= ye)) continue; if (ys >= h) { yp += ys - h + 1; ys = h - 1; }; if (ye < 0) ye = -1; }; for (; ys != ye; ys += ay, yp++) raster[ys].push(lx + Math.trunc(dx * yp / dy)); }; const rcmp = ((a, b) => (a - b)); let rs = Math.trunc(Math.max(ymin, 0)); let re = Math.trunc(Math.min(ymax, h - 1)); for (let yp = rs * w; rs <= re; yp += w) { const rp = raster[rs++]; const rplen = rp.length; if (rplen == 1) continue; rp.sort(rcmp); for (let np = 0; np < rplen; np += 2) { let xl = rp[np]; let xr = rp[np + 1]; if (xl > xr) [xl, xr] = [xr, xl]; if ((xr < 0) || (w <= xl)) continue; if (xl < 0) xl = 0; if (xr >= w) xr = w - 1; pixel.fill(c, yp + xl, yp + xr + 1); }; }; }; // インターレース画像を作成する. createInterlaced() { const tab = this.S.createInterlaceTable(h); const w = this.width; const h = this.height; const pixel = this.pixel; const line = [...Array(h)].map( function(_, y) { const p = y * w; return pixel.slice(p, p + w); }); return [...Array(h)].map((_, y) => line[tab[y]]).flat(); }; // GIF 処理用オブジェクトを作成する. createGIF(global_color) { const color = (global_color ? this.color : undefined); return new this.GIF(this.width, this.height, (8 - 1), 0, color); }; // Image Descriptor を作成する. createImageDescriptor(local_color, x, y, interlace) { const color = (local_color ? this.color : undefined); const w = this.width; const h = this.height; const pixel = this.pixel; const data = (!interlace ? pixel : this.createInterlaced()); return this.GIF.createImageDescriptor(x, y, w, h, data, color, interlace); }; }; /* * 3D 処理用 */ class MyImage3DGIF { // コンストラクタ. constructor(color, width, height, defcol=0) { this.S = MyImage3DGIF; this.pixel = new MyPixelBuffer(color, width, height, defcol); this.viewport = new MyViewport(width, height, 1); this.projection = new MyProjection3D(); this.projection.setPerspective(1, 1000, 1, (height / width)); }; // 座標変換済み頂点情報を生成する. createVertex(vectors) { const matrix = this.projection; return [...Array(vectors.length)].map( (_,i) => new MyVertex3D(matrix.mul(vectors[i], true).vector)); }; // 線分をクリッピングする. clipLine(p1, p2) { if (!(p1.fclip | p2.fclip)) return [p1, p2]; if ((p1.fclip & p2.fclip)) return undefined; let xf = p1.fclip ^ p2.fclip; for (let cf = 1, cn = 0; xf; cf <<= 1, cn++) { if (!(xf & cf)) continue; xf -= cf; const c1 = p1.wclip[cn]; const c2 = p2.wclip[cn]; if ((p1.fclip & cf)) { const s = c2 / (c2 - c1); p1 = new MyVertex3D(p1.nMul(s, true).add(p2.nMul((1 - s), true), true).vector) } else { const s = c1 / (c1 - c2); p2 = new MyVertex3D(p2.nMul(s, true).add(p1.nMul((1 - s), true), true).vector) }; if ((p1.fclip & p2.fclip)) return undefined; }; return [p1, p2]; }; // 線を引く(3D) setLine(v1, v2, color) { const np = this.clipLine(v1, v2); if (!np) return; const view = this.viewport; const s1 = view.getScreen(np[0]); const s2 = view.getScreen(np[1]); const [x1, y1] = s1.vector; const [x2, y2] = s2.vector; this.pixel.setLine(x1, y1, x2, y2, color); }; }; /* ********************************** */ /* * DOM の操作など */ function createImgWithBase64(b64s, id) { const priority = 'important'; const img = document.createElement('img'); if (id != undefined) img.setAttribute('id', id); img.setAttribute('class', 'b64img'); img.setAttribute('src', 'data:image/gif;charset=utf-8;base64,' + b64s); img.style.setProperty('margin', '0', priority); // img.style.setProperty('padding', '8px', priority); // img.style.setProperty('image-rendering', 'pixelated', priority); return img; } function createFrameBuffer(color) { const fb = new MyImage3DGIF(color, 640, 320); const proj = fb.projection; proj.scale(8, 8, -1); proj.translate(0, 0, -12); proj.degRotateX(+20); return fb; } function renderPolyhedronEdge(fb, vertex, edge, deg, movx, color) { const pixel = fb.pixel; const proj = fb.projection; const mobj = new MyObject3D(); mobj.translate(movx, 0, 0); mobj.degRotateY(deg); mobj.aMulUpdate([ 0, 1, 0, 0, // Y → X 0, 0, 1, 0, // Z → Y 1, 0, 0, 0, // X → Z 0, 0, 0, 1, ]); proj.pushMatrix(mobj); { const vertex3d = fb.createVertex(vertex); for (const e of edge) fb.setLine(vertex3d[e[0]], vertex3d[e[1]], color); } proj.popMatrix(); return pixel; } function renderPolyhedronPolyEdge(fb, vertex, polygon, deg, movx) { const pixel = fb.pixel; const proj = fb.projection; const mobj = new MyObject3D(); mobj.translate(movx, 0, 0); mobj.degRotateY(deg); mobj.aMulUpdate([ 0, 1, 0, 0, // Y → X 0, 0, 1, 0, // Z → Y 1, 0, 0, 0, // X → Z 0, 0, 0, 1, ]); proj.pushMatrix(mobj); { const vertex3d = fb.createVertex(vertex); const bcol = 2; const fcol = 3; const fgp = new Array(); const bgp = new Array(); for (const p of polygon) { const v0 = vertex3d[p[0]]; const v1 = vertex3d[p[1]]; const v2 = vertex3d[p[2]]; const nv = v1.sub(v0).cross(v2.sub(v1)); if (v0.dot(nv) < 0) bgp.push(p); else fgp.push(p); } for (const [pl, col] of [[bgp, bcol] /*, [fgp, fcol] */]) { for (const p of pl) { let vp = vertex3d[p[p.length - 1]]; for (const n of p) { const vn = vertex3d[n]; fb.setLine(vp, vn, col); vp = vn; } } } for (const p of fgp) { let vc = new MyVertex3D(); for (const n of p) { const vn = vertex3d[n]; vc = vc.add(vn); } const small = 0.95; vc = vc.div(p.length); let vp = vertex3d[p[p.length - 1]]; vp = new MyVertex3D(vp.sub(vc).mul(small).add(vc).vector); for (const n of p) { let vn = vertex3d[n] vn = new MyVertex3D(vn.sub(vc).mul(small).add(vc).vector); fb.setLine(vp, vn, fcol); vp = vn; } } } proj.popMatrix(); return pixel; } function setRegularPolyhedron(M) { const mIndex = { 4:1, 6:2, 8:3, 12:4, 20:5 }[M]; const id_table = [ 'RegularTetrahedron', 'RegularTetrahedron', 'RegularHexahedron', 'RegularOctahedron', 'RegularDodecahedron', 'RegularIcosahedron', ]; const name_table = ['正4面体', '正4面体', '正6面体', '正8面体', '正12面体', '正20面体']; const dual_table = ['正4面体', '正4面体', '正8面体', '正6面体', '正20面体', '正12面体']; const loop_table = [3, 3, 3, 4, 3, 5]; const nbsp = '\xa0'; const rp = new MyRegularPolyhedron(M); const table = document.createElement('table'); const set_vector = function(title, pmsg, table, vectors, poly, dual) { { const tr = document.createElement('tr'); const th1 = document.createElement('th'); th1.setAttribute('colspan', '4'); th1.innerText = title; tr.append(th1); const th2 = document.createElement('th'); th2.innerText = pmsg; tr.append(th2); table.append(tr); } for (let i in vectors) { const v = vectors[i].fixInt().vector; const tr = document.createElement('tr'); const tdn = document.createElement('td'); tdn.style.setProperty('text-align', 'right'); tdn.innerText = i; tr.append(tdn); for (let j = 0; j < 3; j++) { const s = v[j].toFixed(6); const tdv = document.createElement('td'); tdv.innerText = nbsp + ((s[0] != '-') ? '+' : '') + s + nbsp; tr.append(tdv); } const tdp = document.createElement('td'); tdp.innerText = (nbsp + dual[i] + nbsp).replaceAll(',', ',' + nbsp); tr.append(tdp); table.append(tr); } } set_vector('頂点表(対の多角形の法線表)', '(対)' + dual_table[mIndex] + '\n対の頂点番号表', table, rp.vertex, rp.polygon1, rp.polygon2); set_vector('多角形の法線表(対の頂点表)', name_table[mIndex] + '\n頂点番号表', table, rp.normal, rp.polygon2, rp.polygon1); { const bcol = 1; const fcol = 3; const nloop1 = loop_table[mIndex]; const nloop2 = nloop1; const angle1 = Math.trunc(360 / nloop1); const angle2 = Math.trunc(360 / nloop2); const step = (150 / nloop1); const speed = 3; const color_table = [ [ 0, 0, 0], [ 90, 90, 0], [ 85, 85, 85], [255, 255, 255], ] const mfb = createFrameBuffer(color_table); const gif = mfb.pixel.createGIF(true); gif.appendApplicationExtensionForLoop(0); for (let s = 0; s < step; s++) { const deg1 = angle1 * s / step; const deg2 = angle2 * s / step; gif.appendGraphicControlExtension(speed); const sfb = createFrameBuffer(color_table); // const pix3 = renderPolyhedronEdge(sfb, rp.normal, rp.edge2, deg2, -1.25, bcol); const pix4 = renderPolyhedronEdge(sfb, rp.vertex, rp.edge1, deg1, +1.25, bcol); const pix1 = renderPolyhedronPolyEdge(sfb, rp.vertex, rp.polygon1, deg1, -1.25, fcol); const pix2 = renderPolyhedronPolyEdge(sfb, rp.normal, rp.polygon2, deg2, +1.25, fcol); gif.appendDescriptor(pix1.createImageDescriptor()); } gif.appendTrailer(); const tr = document.createElement('tr'); const td = document.createElement('td'); const span = document.createElement('span'); span.innerText = '左は' + name_table[mIndex] + '、右は' + name_table[mIndex^1]; td.setAttribute('colspan', '5'); td.style.setProperty('text-align', 'center'); td.append(span); td.append(document.createElement('br')); td.append(createImgWithBase64(MyBase64.encode(gif.get_binary()))); tr.append(td); table.append(tr); } document.getElementById(id_table[mIndex]).append(table); } function onLoad() { for (let M of [4, 6, 8, 12, 20]) setRegularPolyhedron(M); } onLoad(); /* ********************************** */ // !--> </script> </body> </html> HTML を開く(少し時間がかかる)と、座標と多角形のデータとともに GIF 画像を表示します。 生成される画像 GIF 画像は、上から見て反時計回りに回転しています。 時計回りに見える場合は、奥行方向を逆に錯覚しています。 正四面体と対の正四面体 正六面体と対の正八面体 正八面体と対の正六面体 正十二面体と対の正二十面体 正二十面体と対の正十二面体 対の対を追加して裏面の線を暗くした版の画像 正四面体と対の正四面体 正六面体と対の正八面体 正八面体と対の正六面体 正十二面体と対の正二十面体 正二十面体と対の正十二面体 対の対を追加した版の画像 正四面体と対の正四面体 正六面体と対の正八面体 正八面体と対の正六面体 正十二面体と対の正二十面体 正二十面体と対の正十二面体 初期版の画像 正四面体と対の正四面体 正六面体と対の正八面体 正八面体と対の正六面体 正十二面体と対の正二十面体 正二十面体と対の正十二面体
- 投稿日:2022-01-13T17:43:20+09:00
【JavaScript】関数を別ファイルでも使えるようにする【忘備録】
JavaScriptのファイル間で同じ関数を使用したい場合は const 関数名 = () => { }; から window.関数名 = () => { }; に変えるだけで、ファイルを跨いで使用することができるようになります。 ファイルを分割したいときなどに便利です。
- 投稿日:2022-01-13T16:08:45+09:00
javascriptでRPG#14
こんにちは 今回は、石の採掘を作っていこうと思います では、作っていきます つるはしの大きさ 今回から項目ごとに太字で書いていきます では、前作ったつるはしの大きさが大きすぎると思ったので、小さくします main.js //石のツルハシ表示 if(pl["stone_pickel"]==1&&map_r==3){ ctx.drawImage(system["stone_pickel"],0,0,32,32,0,64,64,64); } この文章を、石のつるはし表示というところと、交換してください これで、つるはしを入手してから、アイテム欄を確認すると、小さくなっています 装備の確認 では、次にいま装備しているアイテムは何かを見れるものを作ります main.js main.js "use strict"; const pl = { "x":0, "y":0, "map":0, "stone_pickel":0 } const system = { "ctx_font":"32px monospace", "ctx_font2":"16px monospace", "talk-1":0, "mapchip_size":32, "pl_animation":0, "mapchip":"", "pl1":"", "stone_pickel":"", "item_box":0, "item_box_i":0, "current_item":0 } let pl_g_up = false; let pl_g_left = false; let pl_g_right = false; let pl_g_down = false; let pl_k = ""; let pl_n = 0; let talk_number=1; let mapdate; let map_r=0; let map_l=1; //メインループ function main_loop() { const canvas = document.getElementById("canvas");//キャンバスの取得 const ctx = canvas.getContext("2d");//2Dコンテキスト取得 ctx.clearRect(0, 0, 640, 640);//canvasの初期化 for( let y = 0; y < mapdate.length; y++ ){ for( let x = 0; x < mapdate[0].length; x++ ){ //マップの表示 ctx.drawImage( system["mapchip"], mapdate[y][x]*32,0,system["mapchip_size"],system["mapchip_size"], x*system["mapchip_size"]-(pl["x"]),y*system["mapchip_size"]-(pl["y"]),32,system["mapchip_size"]); } } //キャラクターの表示 ctx.drawImage(system["pl1"],(system["pl_animation"]%3)*32,(Math.floor(system["pl_animation"]/3))*32, system["mapchip_size"],system["mapchip_size"],320,320,32,system["mapchip_size"]); //フォントを設定 ctx.font = system["ctx_font"]; //移動の準備 if(pl_n==0){ //上 if(pl_g_up&&pl["y"]>-320&&!(move_no.includes(mapdate[pl["y"]/32+9][pl["x"]/32+10]))&&map_r==0){ pl_k="up"; pl_n=32; system["pl_animation"]=9; } //左 if(pl_g_left&&pl["x"]>-320&&!(move_no.includes(mapdate[pl["y"]/32+10][pl["x"]/32+9]))&&map_r==0){ pl_k="left"; pl_n=32; system["pl_animation"]=3; } //下 if(pl_g_down&&!(move_no.includes(mapdate[pl["y"]/32+11][pl["x"]/32+10]))&&map_r==0){ pl_k="down"; pl_n=32; system["pl_animation"]=0; } //右 if(pl_g_right&&!(move_no.includes(mapdate[pl["y"]/32+10][pl["x"]/32+11]))&&map_r==0){ pl_k="right"; pl_n=32; system["pl_animation"]=6; } } //移動の命令・上 if(pl_k=="up"&&pl_n!==0){ pl["y"]-=4; pl_n -= 4; if(system["pl_animation"]==11){ system["pl_animation"]=9; } system["pl_animation"]+=1; } //移動の命令・下 if(pl_k=="down"&&pl_n!==0){ pl["y"]+=4; pl_n -= 4; if(system["pl_animation"]==2){ system["pl_animation"]=0; } system["pl_animation"]+=1; } //移動の命令・左 if(pl_k=="left"&&pl_n!==0){ pl["x"]-=4; pl_n -= 4; if(system["pl_animation"]==5){ system["pl_animation"]=3; } system["pl_animation"]+=1; } //移動の命令・右 if(pl_k=="right"&&pl_n!==0){ pl["x"]+=4; pl_n -= 4; if(system["pl_animation"]==8){ system["pl_animation"]=6; } system["pl_animation"]+=1; } //下の吹き出し表示 ctx.beginPath(); ctx.fillStyle = "rgba(" + [0, 0, 0, 0.5] + ")"; ctx.fillRect(16, 512, 608, 128); //文字を取得し、表示 ctx.font=system["ctx_font"]; ctx.fillStyle = "rgba(" + [255, 255, 255, 1] + ")"; ctx.fillText(talk[Number(talk_number)],16,544) //スペースキーで次へを挿入 if(talk_number==1||talk_number==4||talk_number==6||talk_number==7||talk_number==8||talk_number==9||talk_number==10){ ctx.font=system["ctx_font2"]; ctx.fillStyle = "rgba(" + [255, 255, 255, 1] + ")"; ctx.fillText("スペースキーで次へ",464,624); } //現在持っているアイテムを表示する枠の表示 if(system["item_box"]==0){ ctx.fillStyle = "rgba(" + [0, 0, 0, 1] + ")"; ctx.fillRect(0, 0, 100, 44); ctx.fillStyle = "rgba(" + [0, 0, 0, 1] + ")"; ctx.fillRect(0, 44, 5, 100); ctx.fillStyle = "rgba(" + [0, 0, 0, 1] + ")"; ctx.fillRect(0, 144, 100, 5); ctx.fillStyle = "rgba(" + [0, 0, 0, 1] + ")"; ctx.fillRect(95, 44, 5, 100); ctx.font=system["ctx_font"]; ctx.fillStyle = "rgba(" + [255, 255, 255, 1] + ")"; ctx.fillText("装備中",0,30); } if(system["current_item"]==1){ ctx.drawImage(system["stone_pickel"],0,0,32,32,0,40,114,114); } //Enterで次へを挿入 if(map_r==1){ ctx.font=system["ctx_font2"]; ctx.fillStyle = "rgba(" + [255, 255, 255, 1] + ")"; ctx.fillText("上下矢印キーとEnterで選択",400,624) } //家に入る時のイベント if(map_r==0&&mapdate[pl["y"]/32+9][pl["x"]/32+10]==4){ talk_number=4; }else{ if(map_r==0&&talk_number>=4){ talk_number=5; } } //家に入るときの『はい』と『いいえ』の選択肢の『はいver』 if(map_r==1&&map_l==1){ talk_number=5; ctx.font=system["ctx_font"]; ctx.fillStyle = "rgba(" + [255, 255, 255, 1] + ")"; ctx.fillText("はい←",16,544) ctx.font=system["ctx_font"]; ctx.fillStyle = "rgba(" + [255, 255, 255, 1] + ")"; ctx.fillText("いいえ",16,624) } //家に入るときの『はい』と『いいえ』の選択肢の『いいえver』 if(map_r==1&&map_l==0){ talk_number=5; ctx.font=system["ctx_font"]; ctx.fillStyle = "rgba(" + [255, 255, 255, 1] + ")"; ctx.fillText("はい",16,544) ctx.font=system["ctx_font"]; ctx.fillStyle = "rgba(" + [255, 255, 255, 1] + ")"; ctx.fillText("いいえ←",16,624) } //家に入るときの『はい』と『いいえ』の選択肢の制御用 if(map_l>=2)map_l=0; //家の中のNPC1と喋るとき if(pl["map"]==1&&pl["x"]==64&&pl["y"]==64){ if(system["talk-1"]==0){ //初めて話す場合 system["talk-1"]=1; map_r=2; talk_number=6; } if(system["talk-1"]==1){ //2回目以降で話す場合 } } //アイテム欄の表示 if(system["item_box"]==1){ ctx.beginPath(); ctx.fillStyle = "rgba(" + [0, 0, 0, 0.8] + ")"; ctx.fillRect(0, 0, 640, 320); ctx.beginPath(); ctx.fillStyle = "rgba(" + [156, 156, 156, 1] + ")"; ctx.fillRect(0, 64, 640, 5); ctx.font=system["ctx_font"]; ctx.fillStyle = "rgba(" + [255, 255, 255, 1] + ")"; ctx.fillText("〜アイテム〜",224,32) ctx.font=system["ctx_font2"]; ctx.fillStyle = "rgba(" + [255, 255, 255, 1] + ")"; ctx.fillText("もう一度シフトキーを押して閉じる",192,56) } //石のツルハシ表示 if(pl["stone_pickel"]==1&&map_r==3){ ctx.drawImage(system["stone_pickel"],0,0,32,32,0,64,64,64); } //家から出るときのイベント if(pl["map"]==1&&pl["x"]==160&&pl["y"]==160){ mapdate=[...world]; pl["map"]=0; pl["x"]=96; pl["y"]=96; } //メインループ終了 } //ブラウザ起動時の処理 window.onload = function(){ system["mapchip"] = new Image(); system["mapchip"].src = 'https://raw.githubusercontent.com/sugoruru/rurukun3-save/main/tile-set4.png';//マップタイルの読み込み system["stone_pickel"] = new Image(); system["stone_pickel"].src = 'https://raw.githubusercontent.com/sugoruru/rurukun3-save/main/d-tile.png';//石のつるはしの読み込み system["pl1"] = new Image(); system["pl1"].src = 'https://raw.githubusercontent.com/sugoruru/rurukun3-save/main/p-tile-set1.png';//キャラクタータイルの読み込み setInterval(function() {main_loop()},20);//「main_loop」を20mm秒ごとに呼び出し //世界のマップ mapdate=[...world]; } //キーボード操作 document.addEventListener("keydown", key_down, false);//押されたときの操作→「key_down」へ addEventListener("keyup", key_up, false);//押すのをやめたときの処理→「key_up」へ //キーが押されたとき function key_down(e) { if(talk_number>1){ if(e.which==38){ //上キーが押されたとき pl_g_up=true; if(talk_number==2)talk_number++; if(map_r==1)map_l++; } if(e.which==37){ //左キーが押されたとき pl_g_left=true; if(talk_number==2)talk_number++; } if(e.which==40){ //下キーが押されたとき pl_g_down=true; if(talk_number==2)talk_number++; if(map_r==1)map_l++; } if(e.which==39){ //右キーが押されたとき pl_g_right=true; if(talk_number==2)talk_number++; } } if(e.which==32){ //スペースキーが押されたとき //はじめの「ここは...?」の文章を進む if(talk_number==1){ talk_number++; } //「家に入りますか?」の質問 if(mapdate[pl["y"]/32+9][pl["x"]/32+10]==4){ map_r=1; } //NPC1の会話終了 if(talk_number==10){ talk_number=5; map_r=0; pl["stone_pickel"]=1; system["current_item"]=1; } //NPC1の会話で次へ進む if(talk_number==6||talk_number==7||talk_number==8||talk_number==9){ talk_number++; } } if(e.which==13){ //エンターキーが押されたとき //家に入る(いいえ) if(map_r==1&&map_l==0){ map_r=0; } //家に入る(はい) if(map_r==1&&map_l==1){ pl["map"]=1; mapdate=[...home1]; map_r=0; pl["x"]=160; pl["y"]=128; } } if(e.which==16){ //シフトキーが押されたとき if(system["item_box"]==0&&system["item_box_i"]==0){ map_r=3; system["item_box"]=1; system["item_box_i"]=1; } if(system["item_box"]==1&&system["item_box_i"]==0){ map_r=0; system["item_box"]=0; system["item_box_i"]=1; } } } //キーが上げられたとき function key_up(e){ pl_g_up=false; pl_g_left=false; pl_g_down=false; pl_g_right=false; system["item_box_i"]=0; } こちらはすべて載せてありますので、コピーしてください。 comment.txt これは、「qiita」で皆様と作っているときの備考用です ↓#0 https://qiita.com/rurukun82/items/b5e376f188bc8a1b5c7d ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー map_rについて 0→何もなし 1→家の中に入るときの制限 2→NPC1と喋るとき 3→持ち物欄選択 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー mapdateについて world→実際の世界 home1→鍛冶屋さんがいる家 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー pl変数について x→今いるdatemap変数のときの座標 y→今いるdatemap変数のときの座標 map→今どのマップに居るのか stone_pickel→石のつるはしを持っているか ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー pl変数(map)について 0→world 1→home1 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー system変数について ctx_font→32pxの文字のfont ctx_font2→16pxの文字のfont talk-1→鍛冶屋の人との関わり mapchip_size→マップタイルの大きさ pl_animation→プレイヤーのアニメーション番号 mapchip→マップチップ画像挿入用 pl1→プレイヤー1の画像挿入用 stone_pickel→石のつるはしの画像挿入用 item_box→アイテム欄の表示イベント中かどうか item_box_i→アイテム欄のイベント制御用 current_item→現在の持っているアイテムはなにか ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー これで、枠が表示されて、つるはしも入手したら表示されていると思います 変えた部分がわかりにくくなったので、main.jsは全て載せました comment.txtもついでに変更しました 仕組みはこうです ①system変数に「current_item」をついか→comment.txtを参照 ②この変数にそってアイテムを表示 スペースキーで次への配列化 main.jsからこの文章を探してください main.js //スペースキーで次へを挿入 if(talk_number==1||talk_number==4||talk_number==6||talk_number==7||talk_number==8||talk_number==9||talk_number==10){ ctx.font=system["ctx_font2"]; ctx.fillStyle = "rgba(" + [255, 255, 255, 1] + ")"; ctx.fillText("スペースキーで次へ",464,624); これだと、たくさん作っていくときに不便です なのでこれをこうしてください main.js //スペースキーで次へを挿入 if(space_key_next.includes(talk_number)){ ctx.font=system["ctx_font2"]; ctx.fillStyle = "rgba(" + [255, 255, 255, 1] + ")"; ctx.fillText("スペースキーで次へ",464,624); そしたらtalk.jsに下の配列を追加してください talk.js //スペースキーで次への配列化 space_key_next=[1,4,6,7,8,9,10]; これで変わらずに動くと思います 石の採掘 これから本題です まず、talk.jsに「14」個目の文章を追加し、 space_key_next配列に「14」を追加してください talk.js //下の会話文 const talk = { "1":"ここは...?", "2":"矢印キーで移動してみよう!", "3":"家の中に入ってみよう!", "4":"家の中に入りますか?", "5":"", "6":"ここは道具屋。", "7":"よく見ない顔だね", "8":"じゃあ、初めてあった縁として...", "9":"つるはしをあげよう!", "10":"これで外の岩を砕いてきてくれ", "11":"石をくれるかな?", "12":"石がないよ。外からとってきてくれ", "13":"よし、じゃあ、この石でおのを作ってあげよう!", "14":"この石を採掘しますか?" } //スペースキーで次への配列化 space_key_next=[1,4,6,7,8,9,10,14]; そしたら、main.jsのなかのメインループにこちらを入れてください main.js //石の採掘のイベントスタート if(mapdate[pl["y"]/32+9][pl["x"]/32+10]==3&&system["current_item"]==1){ talk_number=14; ctx.font=system["ctx_font"]; ctx.fillStyle = "rgba(" + [255, 255, 255, 1] + ")"; ctx.fillText(talk[talk_number],16,544); } //メインループ終了 条件は、石のつるはしを装備しているかつ石の前に立っているです そしたらまた、comment.txtを変更しました comment.txt これは、「qiita」で皆様と作っているときの備考用です ↓#0 https://qiita.com/rurukun82/items/b5e376f188bc8a1b5c7d ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー map_rについて 0→何もなし 1→家の中に入るときの制限 2→NPC1と喋るとき 3→持ち物欄選択 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー mapdateについて world→実際の世界 home1→鍛冶屋さんがいる家 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー pl変数について x→今いるdatemap変数のときの座標 y→今いるdatemap変数のときの座標 map→今どのマップに居るのか stone_pickel→石のつるはしを持っているか ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー pl変数(map)について 0→world 1→home1 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー system変数について ctx_font→32pxの文字のfont ctx_font2→16pxの文字のfont talk-1→鍛冶屋の人との関わり mapchip_size→マップタイルの大きさ pl_animation→プレイヤーのアニメーション番号 mapchip→マップチップ画像挿入用 pl1→プレイヤー1の画像挿入用 stone_pickel→石のつるはしの画像挿入用 item_box→アイテム欄の表示イベント中かどうか item_box_i→アイテム欄のイベント制御用 current_item→現在の持っているアイテムはなにか ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー system変数(current_item)について 0→なにもなし 1→石のつるはし所持 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー マップタイルについて 0→海1(壁) 1→海2(壁) 2→草(地面) 3→石(壁) 4→家(壁) 5→木(壁) 6→家の床(地面) 7→家の出口(地面) 8→人1(壁) 9→家の壁横(壁) 10→家の壁縦(壁) マップタイルについてとcurrent_itemについてを加えました では、これで終わりたいと思います できない等のことがあればコメントで教えて下さい
- 投稿日:2022-01-13T15:47:31+09:00
HostObject内でしれっと JS を書いていく
HostObject.cs public class HostObject{ protected CoreWebView2 core = null!; public HostObject(WebView2 view) { view.NavigationCompleted += (s,e)=>{ core = view.CoreWebView2; core.AddHostObjectToScript("test",this); IIFE(@$" var e = new Event('HostObjectAdded') e.value = {{ name:'exe', methods:{JsonSerializer.Serialize(GetMethods())}, }} dispatchEvent(e) "); }; } protected async void IIFE(string script){//即時実行関数式 try{ if(core!=null) { var r = await core.ExecuteScriptAsync($"(function(){{{script}}})()"); } }catch{Debug.WriteLine("x");} } public string[] GetMethods(){ var methods = new List<string>(); //... return methods.ToArray(); } }
- 投稿日:2022-01-13T14:40:34+09:00
javascriptでクリックやタップの長押し処理をする方法
はじめに 散々出尽くされていると思いますが健忘録として残しておきます。極力シンプルな方法かつPCとスマホ両対応のつもりです。 実装例 クリック押しっぱなしで数値が加算され続けます。 See the Pen Untitled by rahhi555 (@rahhi555) on CodePen. コード解説 html <p id="target">0</p> <!-- touch-action: none; スマホでタップしたままスワイプするとpointerupイベントが発生しない現象の防止 user-select: none; 及び oncontextmenu="return false;" 長押し時のポップアップ停止 --> <button id="btn" style="touch-action: none; user-select: none;" oncontextmenu="return false;"> クリック </button> javascript /** * 押下時にインクリメントし続ける処理を登録し、解放時に登録した処理を削除する */ document.getElementById('btn').addEventListener('pointerdown', () => { const intervalId = setInterval(increment, 50) // document要素にイベント登録することで、クリックした後ボタンから動かしてもOK // once: true を指定して一度発火したらイベントを削除する document.addEventListener('pointerup', () => { clearInterval(intervalId) }, { once: true }) }) const increment = () => { // インクリメント処理は記事の趣旨と関係ないので省略 あとがき スマホの動作はchrome dev toolsで確認しただけなので、実機だと不具合があるかもしれません。
- 投稿日:2022-01-13T11:56:16+09:00
カレンダーを表示⇒日付をクリックすると入力フォームに自動入力される【Django】
カレンダーを表示⇒日付選択で入力フォームに反映 カレンダーボタンから、カレンダーを表示し日付をクリックできます。 その後選択した日付が入力フォームに自動で反映される設定です。 ※めちゃくちゃ簡素に書いてますmm フォーム(forms.py)の設定 forms.py from django import forms #(中略) class EnqueteForm(forms.Form): birth = forms.DateField( label='生年月日', widget=forms.DateInput(format='%Y-%m-%d', attrs={'type': 'date'}), # required=False ) #表示するための記述 def __init__(self,*args,**kwargs): super().__init__(*args,**kwargs) self.fields['birth'].widget.attrs['class']='form-control col-11' Tabがこちらで反映されてない部分がありますので、適宜修正をお願いします。 ビュー(views.py)の設定 views.py from .forms import EnqueteForm #(中略) class EnqueteView(generic.FormView): template_name = "enquete.html" form_class = EnqueteForm #forms.pyで作ったクラス名 success_url = reverse_lazy('information:enquete') JavaScriptの設定(base.html) base.html <script> $(function () { // 日付は、年-月-日 の形式でお願いする。 let dateFormat = 'yy-mm-dd'; $('#id_created_at').datepicker({ dateFormat: dateFormat }); }); // drag and drop event $(document).ready(function() { var obj = $("#dragandrophandler"); obj.on('dragenter', function (e) { e.stopPropagation(); e.preventDefault(); $(this).css('border', '2px solid #0B85A1'); }); obj.on('dragover', function (e) { e.stopPropagation(); e.preventDefault(); }); obj.on('drop', function(e) { $(this).css('border', '2px dotted #0B85A1'); e.preventDefault(); var files = e.originalEvent.dataTransfer.files; // Modal dialog popupImage(file, obj); }); // Avoid opening in a browser if the file is dropped outside the div $(document).on('dragenter', function (e) { e.stopPropagation(); e.preventDefault(); }); $(document).on('dragover', function (e) { e.stopPropagation(); e.preventDefault(); obj.css('border', '2px dotted #0B85A1'); }); $(document).on('drop', function (e) { e.stopPropagation(); e.preventDefault(); }); }); </script> 今回もJS部分はhtmlにべた書きしましたが、他にいい方法があったらご教示いただけますと幸いです。
- 投稿日:2022-01-13T11:35:08+09:00
【React】【Vue】 など流行りのフレームワークを使うもののためのJS基礎 [this, call(), apply(), bind()]
this thisは、その関数を呼び出しているオブジェクト グローバルスコープで呼び出した場合は、グローバルオブジェクト javaScriptは一番外におおきなグローバルオブジェクトで囲まれているため。 bind() thisが参照するオブジェクトを指定する ※ const myButton = { content: 'OK', click() { console.log(this) console.log(this.content + ' clicked'); } }; myButton.click(); //コンソール { content: 'OK', click: [Function: click] } OK clicked この場合のthisはmyButtonのオブジェクトを示している では次の場合はどうなるだろうか myButtonクラスのメソッドを変数looseClickに代入している const myButton = { content: 'OK', click() { console.log(this) console.log(this.content + ' clicked'); } }; const looseClick = myButton.click; looseClick(); //コンソール Object [global] undefine clicked この場合のthisはグローバルオブジェクトを示している なぜかというとlooseClickには click() { console.log(this) console.log(this.content + ' clicked'); } が代入されている 記事の最上部に書いたが、この時のthisはmyButtonのようなクラスに囲まれていない →グローバルオブジェクトを示している これを※と同じ動きにするにはbindを使用する const myButton = { content: 'OK', click() { console.log(this) console.log(this.content + ' clicked'); } }; const bindClick = myButton.click.bind(myButton); bindClick(); //コンソール Object [global] undefine clicked clickメソッドの中にあるthisがmyButtonオブジェクトを指していることをbindをつかって指定する 追記予定 call applay
- 投稿日:2022-01-13T11:30:33+09:00
Warning: React has detected a change in the order of Hooks called by X でハマる
何をしていたか(悪いコード) React Native で、とあるアプリで、アカウント情報を表示する画面を作成していた。 ReactNavigation を利用していた。 処理の概要は以下 export default function MyPage { // ユーザ情報 const [user, setUser] = useState(); // ユーザ情報取得処理(あとで再利用する) const userCheck = async () => { const user = await getUser(); setUser(user); } // 画面表示開始時に一度だけ読み取る useEffect(userCheck, []); // ユーザ情報がなければ処理中を表示 if (!user) { return ( <Text>処理中</Text> ); } // ここから本画面 画面遷移用にnavigation 確保 const navigation = useNavigation(); return ( <Text>本画面</Text> {/* 実際は、画面遷移などをする処理がある */} ); 先に結論 useNavigation も立派な Hook なので、条件によって呼ばれたり呼ばれなかったりするとこのエラーになります。 何が起きたか 処理的には問題名けど、以下のエラーが表示されてビビる Warning: React has detected a change in the order of Hooks called by MyPage. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks Previous render Next render ------------------------------------------------------ 1. useState useState 2. useState useState 3. useEffect useEffect 4. undefined useContext ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 何をしたか ひとまずURL https://reactjs.org/link/rules-of-hooks の記載があるので、皿の目で読む。 翻訳などを使って、以下のように理解 Hooks は、レンダリング毎に、同じ順序、同じ量、同じものを毎回呼ばれるようにしなきゃいかんよ 上を読んでもハマる。どう考えたか useState は毎度同じものが同じ順序で呼び出されるはず useEffect も useState の後に呼び出されているし、?のURL記載事項には反していないはず あれ? useContext ってなんだっけ? 使ってたっけ? 結局何だったか useNavigation がHookであることを忘れていた。 useNavigation は、内部で useContext を使っているので、この順序になっていた。 state の条件により、 useNavigation が呼ばれたり呼ばれなかったりすると、このエラーになるようだ。 良いコード export default function MyPage { // ユーザ情報 const [user, setUser] = useState(); // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ // 画面遷移用にnavigation 確保(条件に左右されず useNavigation は呼ばれるようにしないとエラーになるぞ) const navigation = useNavigation(); // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ // ユーザ情報取得処理(あとで再利用する) const userCheck = async () => { const user = await getUser(); setUser(user); } // 画面表示開始時に一度だけ読み取る useEffect(userCheck, []); // ユーザ情報がなければ処理中を表示 if (!user) { return ( <Text>処理中</Text> ); } // ここから本画面 return ( <Text>本画面</Text> {/* 実際は、画面遷移などをする処理がある */} ); 教訓 ライブラリをじゃんじゃん使うのはいいが、中で何をしているのか、特に Hooks については気を付けよう use という言葉から、Hooks だと連想できるようになろう 正常に動作するので、最悪放置しようと思ったけど、そんな自分にばいばいきん
- 投稿日:2022-01-13T11:11:51+09:00
Swiperを使ってスライダーをつくる
はじめに スライダーを作りたい!ということで、今回Swiperを使って実装しました。 基本の書き方と応用したものを、備忘録としてまとめています。 Swiperとは? jQueryを使わず、スライダーが作れるJavaScriptのライブラリです。 ただし、バージョン5以降はIE対象外のため、IE対応させるにはバージョン4を使う必要があります。 Swiperを使うには GitHubからファイルをダウンロード、もしくはCDNを読み込む 今回こちらのサイトからCDNで読み込んで使用しました。 基本の記述 HTML <!-- スライダー大枠 --> <div class="swiper-container"> <!-- スライドしたい内容を覆う枠 --> <div class="swiper-wrapper"> <!-- スライド --> <div class="swiper-slide">Slide 1</div> <div class="swiper-slide">Slide 2</div> <div class="swiper-slide">Slide 3</div> ... </div> <!-- ページネーション(※使いたい場合) --> <div class="swiper-pagination"></div> <!-- ナビゲーション(※使いたい場合) --> <div class="swiper-button-prev"></div> <div class="swiper-button-next"></div> <!-- スクロールバー(※使いたい場合) --> <div class="swiper-scrollbar"></div> </div> CSS サイズや見た目など設定する際は適宜追加します。 .swiper-container { max-width: 1000px; margin: 0 auto; } .swiper-slide { text-align: center; } JSでSwiperを初期化 変数swiperを初期化。 この時点でスライダーとしての形はできてる状態。 中身に使用するオプションを追加していく。(必要なオプション機能を適宜) const swiper = new Swiper('.swiper-container', { //↓追加していく // パラメーター direction: 'vertical', loop: true, // ページネーション pagination: { el: '.swiper-pagination', }, // ナビゲーション navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev', }, // スクロールバー scrollbar: { el: '.swiper-scrollbar', }, }); 基本的な実装 矢印をクリックしたらスライドするのみ See the Pen Untitled by chisato (@mgkita) on CodePen. JSにパラメーターなどを追加することで色んな動きをつけることができます。 ※「値」を記述する際はtrue・false、数字以外は""か''で囲う 例) loop: true:最後までいったら1枚目に戻ってループする effect: 'fade' :スライドしたときにフェードしながら画像が切り替わる slidesPerView: 任意の数字:仮に3を指定すると、3枚横並びで表示される など ↓公式より 応用ver 汎用的なスライダーを作成しました。 今回は実装してませんが、もっと見るをクリックすると画像一覧のようなページにとぶイメージのスライダーです。 See the Pen Untitled by chisato (@mgkita) on CodePen. JS const mySwiper = new Swiper(".cat-slider", { spaceBetween: 50, width: 370, breakpoints: { 767: { spaceBetween: 15, width: 290, slidesPerView: 1 } }, navigation: { nextEl: ".swiper-button-next--cat", prevEl: ".swiper-button-prev--cat" } }); spaceBetween:画像と画像の幅を指定 width:画像自体の横幅を指定 breakpoints:767px以下の時のスタイルを指定 slidesPerView:横幅100%としたときに画像を何枚見せるか navigation:両端の矢印を設置 JS部分はこれだけの記述で完成! まとめ SwiperはJSを数行書くだけで動くものができてしまうので、手早くスライダーを作成したいときに便利です。 ただ複雑なレイアウトになると難しい部分もあったので、cssを使ったスライダーなどとうまく使い分けれるようにしていきたいと思いました。 おまけ 今回使用した画像は、私の先輩のおうちのにゃんこちゃん達でした! 快く提供してくださり、スペシャルサンクスです!! 可愛すぎるので最後にもう一度貼っておきます。 皆さんもぜひ癒されてください。
- 投稿日:2022-01-13T08:26:57+09:00
Denoは全てのコードをstrict modeで実行する
strict modeとは何か 非ES ModulesのJavaScriptでは、ファイルの先頭や関数の先頭に"use strict"という記述を行うことで、strict modeが有効になります。 function log(val) { "use strict"; console.log(val) } このstrict modeは以下のような効果があります。 存在しない変数への代入をエラーとして扱う with文の削除 8進数表記を禁止 予約語の追加 eval内で宣言した変数のスコープ などなど strict modeは、後方互換性のために排除できなかった、挙動に問題がある構文を排除し、将来の言語の拡張や動作の高速化に対応するものです。 (たまに後方互換性を排除してJavaScriptをイチから作り直してほしいという人がいますが、strict modeが実質それだと考えられます。) このstrict modeですが、ES Modulesではデフォルトで有効になっています。 Deno と ES Modules と strict mode Denoでは全てのコードがES Modulesとして実行されます。 ES Modulesの場合、全てのコードがstrict modeとして実行されます。 モジュール内部で定義されたスクリプトの動作は、通常のスクリプト内部のものと異なるかもしれません。これは、モジュール内部では自動的に Strict モード が使われるからです。 https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Modules#other_differences_between_modules_and_standard_scripts よって、Denoで動作するコードは全てstrict mode扱いになります。 function log(val) { // "use strict"が無くても自動的にstrict modeになる console.log(val) } このため、Denoでは非strict modeで使えていたwith文などの古い構文を使うことができません。 TypeScript の strict オプションもデフォルトで有効 TypeScriptにもstrictオプションが存在します。TypeScriptのstrictオプションはstrict modeとは別物なので注意してください。 TypeScriptのstrictオプションは型チェックを厳格にするものです。 公式マニュアルによると、Denoではstrictオプションがデフォルトで有効になっています。 これを無効化するには、deno.jsonかtscofig.jsonに設定を記述した上で、実行時にコマンドライン引数で渡します。 tsconfig.json { "compilerOptions": { "strict": false // デフォルトではtrue } } > deno run --config ./tsconfig.json main.ts tsconfigのオプションはライブラリを含め、コード全体に適用されることに注意してください。 例えば「実験的なデコレータ」(experimentalDecoratorsオプション)を有効にした上でライブラリを作成してしまうと、そのライブラリのユーザーは全てのコードをexperimentalDecoratorsオプション付きで実行する必要があります。 一方で、「実験的なデコレータ」の代わりに「ES標準デコレータ」というものが数年後に導入される予定があります。使用しているライブラリの中に一つでもexperimentalDecoratorsが含まれる場合、他のライブラリがES標準デコレータを使うことができなくなるため、ES標準デコレータへのアップデートが妨げられます。 カスタムのtsconfigを使用する場合、このような将来的な後方互換性問題が発生する可能性が高く、それなりの覚悟が必要です。 まとめ Denoは全てのコードをstrict modeで実行する TypeScript の strict オプションもデフォルトで有効になっている