Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

ドラッグカメラぐるぐるをCSSでする

名前がよくわかりませんが、動作を見てもらえれば一発でわかると思います。

youtu.be

ドラッグによるカメラコントロールというのでしょうか?

やり方

方針

ドラッグ開始地点からの差分にもとづいてX軸、Y軸の回転を計算します。

f:id:panda_noir:20201003094634p:plain

このような感じです。asin(移動したぶん)で角度が求まります。

回転行列について

CSS transform には matrix3d という関数があり、行列を渡すと回転させることができます。

行列の基本的な部分については既知として、回転行列から話していきます。

まず、なにも変形しないときは 4x4 の単位行列になります。

[
  [1, 0, 0, 0],
  [0, 1, 0, 0],
  [0, 0, 1, 0],
  [0, 0, 0, 1],
]

X軸まわりでの回転を表す回転行列はこのようになります。

[
  [1, 0, 0, 0],
  [0, cos(theta), -sin(theta), 0],
  [0, sin(theta), cos(theta), 0],
  [0, 0, 0, 1],
]

(Y軸、Z軸についてはwikiを参照ください。)

回転行列をかけてやると、回転後の行列が求まります。とても簡単ですね。

しかし、今回やりたいことはこれだけでは実現できません。なぜなら、回転させてやると軸も一緒に回転してしまいます。例えばY軸まわりに90度回転させると、もともとのX軸のところにはZ軸、Z軸のところにはX軸がきます。

f:id:panda_noir:20201003101841p:plain
回転前

f:id:panda_noir:20201003101859p:plain
回転後

そのため、1度目の回転はそれぞれの軸まわりでの回転行列をつかえば良いですが、2回目以降はみかけ上の3軸を計算し、それらの軸での回転行列を使う必要があります。

まず、みかけ上の3軸は簡単に求まります。単純にそれぞれの軸を回転させるだけです。

次に、それぞれの軸での回転行列ですが、これもwikiにあるロドリゲスの回転公式がそのまま使えます。

というわけで、道具はそろったので実際にプログラムにしてみます。

実際のプログラム

// ロドリゲスの回転公式
const rot = ([nx, ny, nz], theta) => { /* ... */ };

// 行列の掛け算
const multiple = (a, b) => { /* ... */ };

// 行列の転置
const transpose = (a) => { /* ... */ };

const state = {
  prevMatrix: [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1],
  ],
  theta: 0,
  phi: 0,
  xAxis: [1, 0, 0, 0],
  yAxis: [0, 1, 0, 0],
  get matrix() {
    return multiple(
      multiple(rot(this.xAxis, this.phi), rot(this.yAxis, this.theta)),
      this.prevMatrix
    );
  },
};
let isDragging = false;
let initialX = 0,
  initialY = 0; // ドラッグ開始地点

// 回転終了時の処理
const mouseup = () => {
  const matrix = state.matrix.map((arr) => arr.concat());

  // いまの行列を次の初期値とする
  isDragging = false;
  for (let i = 0; i < 4; i++)
    for (let j = 0; j < 4; j++) {
      state.prevMatrix[i][j] = matrix[i][j];
    }

  const rotation = multiple(
      rot(state.xAxis, state.phi),
      rot(state.yAxis, state.theta)
    ),
    transpose = (value) => value.reduce((acc, item) => [...acc, [item]], []);

  // X軸、Y軸を回転させる
  // phi と theta は回転中の値なので、回転してないときは0
  Object.assign(state, {
    xAxis: multiple(rotation, transpose([state.xAxis])).map(([value]) => value),
    yAxis: multiple(rotation, transpose([state.yAxis])).map(([value]) => value),
    phi: 0,
    theta: 0,
  });
};
const mousemove = ({
  clientX,
  clientY,
  view: { innerWidth: width, innerHeight: height },
}) => {
  if (!isDragging) {
    return;
  }
  // マウスの移動量から回転角を計算する
  // 円の直径は画面の大きさとした
  const wid = width / 2,
    hei = height / 2;
  Object.assign(state, {
    theta: Math.asin((clientX - wid) / wid) - Math.asin((initialX - wid) / wid),
    phi: Math.asin((clientY - hei) / hei) - Math.asin((initialY - hei) / hei),
  });
};

window.addEventListener('mouseleave', mouseup);
window.addEventListener('mouseup', mouseup);
window.addEventListener('mousemove', mousemove);
window.addEventListener('mousedown', ({ clientX, clientY }) => {
  isDragging = true;
  initialX = clientX;
  initialY = clientY;
});

Let's Encrypt の発行方法まとめ

未だに迷うのでまとめる。

まず、大きく分けていくつか方法がある。

  • 既存のウェブサーバーをそのまま使う方法(nginx, apache)
  • 発行時にのみウェブサーバーを立てる方法(standalone)
  • DNS のレコードを設定して対応する方法(manual)
  • 一時ファイルを設置する方法(webroot, manual)

どのオプションを使うべきか?

結論から書くと、certonly で使うべきオプションは以下のとおり

  1. --nginx, --apache
  2. --manual(http challenge), --webroot
  3. --manual(dns challenge)
  4. --standalone

セットアップが不要なもの、既存のウェブサーバーを動かしたまま発行が行えるものほど上に設定した。

以下、各方法の特徴を簡単にまとめた。

既存のウェブサーバーをそのまま使う

これが恐らく一番楽。できることならこれで設定したい。

  • セットアップが不要
  • 証明書のインストールを勝手にしてくれる
  • サーバーを停止せず行える

certbot-nginx プラグインを使うと、設定を自動で書き換えて発行を行ってくれる。そのため、何も設定する必要がない。まずはこれを使えるか検討して、使えないようなら他のやり方を取る。

もちろん、使えないケースはある。たとえば docker でサーバーを動かしているケースでは使えない。

やり方

特筆することはほぼない。

  1. ウェブサーバーを起動しておく
  2. certbot-auto certonly --nginx を実行

これだけである。

/.well-known/acme-challenge へのアクセスを設定する

これも楽なほう。サーバーの設定をして、/.well-known/acme-challenge へアクセスできるよう調整するだけだ。

特徴

  • 既存のサーバーを止めずに行える
  • ちょっと設定を書き直すだけでいける

おそらく、ウェブサーバーがすでにあるなら、上記のやり方かwebrootを使う方法で発行できるはずだ。まだウェブサーバーがないケース、ワイルドカード証明書を取得する場合などは下記を参照。

やり方

やり方は簡単だ。

  1. --webroot-path /path/to/content/.well-known/acme-challengeを置くディレクトリを設定する(パーミッションなど適宜設定する)
  2. 外部から/.well-known/acme-challengeへアクセスした際に上記のファイルへアクセスできるようウェブサーバーを設定
  3. ウェブサーバーを走らせておく

あとはcertbot-auto certonly を実行するだけだ。

/.well-known/acme-challenge 自体は発行する際に certbot が自動で設置するので気にしなくてよい。

standalone なウェブサーバーを建てる

これは若干めんどうで、あまりメリットがない。

特徴

  • 80番ポート・443番ポートを空けておく必要がある
    • 既存のウェブサーバーは止める必要がある
  • セットアップは不要
  • コマンドの前後のウェブサーバーの停止・再起動処理を書く必要がある
  • ウェブサーバーを自前で用意する必要がない

特殊な設定が何も要らないため、ウェブサーバー以外の用途で証明書が欲しいケースでは有用かもしれない。ただし、80番ポートと443番ポートを空けておかなければならない。やはりあまりいい方法ではないだろう。

ウェブサーバー用に発行したい場合は他の方法が断然楽なので、そちらを使おう。

DNS で TXT レコードを貼る方法

DNS を設定できるなら、この方法はかなり楽である。また、ワイルドカード証明書を取得するケースではこの方法しか使えない。

特徴

  • ウェブサーバーについて一切気にしなくて良い
  • TXT レコードを貼るだけなので失敗が少ない
  • 自動更新したい場合、DNS を自動で設定するプログラムを組む必要がある

DNS をプログラムからいじれない場合(お名前comなど)は自動更新ができない。また、そもそも自動更新のために組むプログラムがやや面倒なものである。しかし、一回つくってしまえばあとは他のケースでも流用できる。

やり方

ConoHaでの更新プログラムを書いていた人がいたので紹介する。

www.eastforest.jp

簡単にかいつまんで話すと、

  1. authHook.sh と cleanupHook.sh を用意する
  2. certbot-auto certonly --manual --preferred-challenges dns-01 --manual-auth-hook /path/to/authHook.sh --manual-cleanup-hook /path/to/cleanupHook.sh を実行

authHook.sh と cleanupHook.sh はそれぞれ次のようなファイルである。

  1. authHook.sh には TXTレコードを設定する処理を書く
    • CERTBOT_VALIDATION という環境変数で設定するべき値が渡される
    • これをTXTレコードに設定
  2. cleanupHook.sh を実行
    • 実は実行しなくても良い(もちろん実行したほうが良い)
    • 設定したTXTレコードを削除する

Shell Script で説明したが、環境変数を受け取ってうまく処理できれば、どの言語を使ってもよい。その場合は--manual-auth-hook python setup.sh のように指定する。

コマンドについて

これも若干混乱するので記す。まず、certbot と certbot-auto について。

  • certbot: こちらが本体。
  • certbot-auto: certbot のラッパー。certbotの自動更新などを行ってくれる。

certbot-auto を使うと、certbot を自動的に最新版へ更新してくれる。

次に、certbot のサブコマンドについて説明する。

  • certonly: 証明書の発行のみ行う
  • install: 取得した証明書をウェブサーバーへインストールする(設定の上書きなど)
  • run: 証明書の取得とインストールをする
  • renew: 証明書の更新を行う

このあたりがよく使われる。特に、certonly と renew が一番使われる。インストール処理は不要なことが多い。

発行したあとの期限の確認方法

何度も失敗した経験があるので、本当に更新できているか確認できないと不安なのだ。

(こちらのSSL証明書の有効期限や内容を確認する方法一覧のコードを参考にしている)

openssl x509 -noout -dates -in /etc/letsencrypt/live/example.com/cert.pem
openssl s_client -connect example.com:443 | openssl x509 -noout -enddate

ルービックキューブ用ライブラリを作りました

github.com

概要

  • TypeScript で書かれています
  • d.tsファイルを同梱しています
  • テストカバレッジは99%です
  • ルービックキューブの回転をシミュレートできます
  • 回転記号の表記ゆれにも対応しています

インストール

npmで公開しています。

$ npm install @pandanoir/rubikscube

unpkgからダウンロードして使うこともできます。

<script src="https://unpkg.com/@pandanoir/rubikscube@0.1.0/dist/cube.js"></script>

TSで書かれています

すべてTSで書いてあります。そのため、型チェックによって快適に補完できます。たとえば、存在しない回転記号で回そうとすると型エラーが起こります。

const cube = new Cube();
cube.rotate('R'); // OK
cube.rotate('Mw'); // Type error

かなり厳格に型をつけたので、使いやすいと思います。

表記ゆれにも対応

回転記号には統一された規格がないので、表記ゆれがあります。

  • Rw2'
  • Rw'2
  • R2'
  • R'2

本ライブラリはすべての表記に対応しています。

対応回転記号

  • R, L, U, D, F, B
  • M, S, E
  • x, y, z
  • (r), (u), (f)

また、それぞれ180度回転、反転、ワイド回転にも対応しています。

回転記号を一括で入力可能

たとえば J-perm をこのように入力できます。

cube.rotate('R', 'U', `R'`, `F'`,'R', 'U', `R'`, `U'`, `R'`, 'F', 'R2', `U'`, `R'`, `U'`)

npm publishの練習をするならverdaccio + docker

みなさん、npm publish、怖くないですか???

npmでライブラリ公開したときに踏みつけた地雷7つ - Panda Noir

npmでアップデートするときに踏みがちな3つの落とし穴 - Panda Noir

僕はもうトラウマだらけです。怖いです。しかし、いつかは克服しなければいけません。では、どうすればいいのでしょうか?

失敗しても良い環境を作ればよいのです。

npm publish はなぜ怖いのか?

npm publish は npm にパッケージを公開するコマンドです。パッケージの更新も同じコマンドでできます。

このコマンドが恐ろしいのは「失敗してもやり直せない」点です。バージョンアップはできるのですが、過去のバージョンの内容を変更することはできません。同じバージョンのはずなのに内容が異なると利用者からすればとんでもないことですから、当然といえば当然です。

しかし、一度公開すると二度とやり直せないのは恐ろしいことです。実際、上の記事で書いたように僕は何度も失敗してしまいました。おかげで npm publish に恐怖しております。

じゃあ npm registry を作ろう

ならば、練習環境を整えれば良いのです。いきなり本番環境にあげるから失敗するのです。開発環境を作りましょう。

やりかたはとても簡単です。Verdaccio というレジストリを Docker 上に起動するだけです。

$ docker run -d -it --rm --name verdaccio -p 4873:4873 verdaccio/verdaccio
$ npm adduser --registry http://localhost:4873
$ npm publish --registry http://localhost:4873

これだけで使い捨てのnpm registryができます。失敗したらコンテナを破棄してもう一度立ち上げれば良いです。なんと素敵なのでしょうか。

あとはたくさん練習するだけですね!!!みなさんもいっぱい練習しましょう。

esbuild なら React の playground が5秒で出来る!!!!!

esbuild はプラグインなしで JSX・TSXをコンパイルできるから、React のプレイグラウンドがすぐ作れる!!

$ npm init --yes
$ npm i esbuild react react-dom
$ esbuild src/main.tsx --bundle '--define:process.env.NODE_ENV="development"' --outfile=out.js

型チェックが不要なら typescript のインストールすらいらない!!!!tsconfig.json なんて要らなかったんだ!!!

import React, {render} from 'preact/compat';

interface Props {
  name: string;
}
const App: React.FC<Props> = ({name}: Props) => <h1>Hello {name}</h1>;

render(<App/>, document.querySelector('#main'));

webpack も typescript もナシで TSX がコンパイルできる!!!!!!!すごいだろ。しかも早いんだぜ。すげえだろ。

ただし: 結局 Next.js が欲しくなる

まあ、作ってるうちに結局 ESLint も Prettier も欲しくなってくるし、 型チェックもほしくなる。watch すら簡単にできないので面倒。

はじめから Next.js で作るほうが結局は楽なので、playground と割り切るとき以外 esbuild は使わないほうが吉。

ただ、React の機能を試す playground がほしい時は本当にすぐ完成するので最高。