ワンライナー
(function rec(e) { const FLUCTUATION_WIDTH = 0.1; e.nodeType === e.TEXT_NODE ? e.data = [...e.data].map(s => s.match(/\s/) ? s : String.fromCodePoint(Math.max(s.codePointAt(0) + Math.round(Math.random() * (1 + FLUCTUATION_WIDTH * 2) - (0.5 + FLUCTUATION_WIDTH)), 0x20))).join("") : [...e.childNodes].forEach(rec); })(document.body);Minify
(function rec(e){const FLUCTUATION_WIDTH=0.1;e.nodeType===e.TEXT_NODE?e.data=[...e.data].map(s=>s.match(/\s/)?s:String.fromCodePoint(Math.max(s.codePointAt(0)+Math.round(Math.random()*(1+FLUCTUATION_WIDTH*2)-(0.5+FLUCTUATION_WIDTH)),0x20))).join(""):[...e.childNodes].forEach(rec);})(document.body);要点解説
インデントして比較的ましになったコード
(function rec(e) {
const FLUCTUATION_WIDTH = 0.1;
e.nodeType === e.TEXT_NODE
? e.data = [...e.data]
.map(c =>
c.match(/\s/)
? c
: String.fromCodePoint(Math.max(c.codePointAt(0) + Math.round(Math.random() * (1 + FLUCTUATION_WIDTH * 2) - (0.5 + FLUCTUATION_WIDTH)), 0x20))
).join("")
: [...e.childNodes].forEach(rec);
})(document.body);
(function rec(e) { /*...*/ })(document.body);名前付きのIIFEによる即自実行。
名前があることにより、再帰呼出が可能になる。1
引数にはHTMLドキュメントのbody全体。HTMLElementであれば何でもかまわない。(eはelementの略)
const FLUCTUATION_WIDTH = 0.1
後述する文字コードを震わせる処理の震わせる幅・確率を定義する。
e.nodeType === e.TEXT_NODE ? /*...*/ : /*...*/ ;ノードが
TEXT_NODEであれば、文字コードを震わせる処理をテキストに対して行う。
? e.data = [...e.data].map( /*...*/ ).join("")
e(テキストノード)内部の文字列を.map()で変換したものに書き換える。
[...e.data]で文字列を配列へと展開する。.split("")と異なりこの方法だとサロゲートペアを分割しない。
そして、.join("")で配列から文字列へ再結合する。
: String.fromCodePoint(Math.max(c.codePointAt(0) + Math.round(Math.random() * (1 + FLUCTUATION_WIDTH * 2) - (0.5 + FLUCTUATION_WIDTH)), 0x20))
String.fromCodePoint()とc.codePointAt(0)1で一度数値に変換し、文字に戻す流れ。
String.fromCodePoint()に負の数を渡すと例外を発生させてしまうため、Math.max(/*...*/, 0x20)でその他有象無象の制御文字と共に全て空白文字にフォールバックしてしまう。文字コードを震わせる処理をランダム関数で行う。
定義したFLUCTUATION_WIDTHに応じて、以下のように文字が震える確率を制御する。ランダム範囲 .........------------......... -1 一文字範囲 +1 !!!!!!!!!!----------!!!!!!!!!!
String.fromCodePoint()に小数を渡すと例外を発生させてしまうため、Math.round()で整数に丸める。ノードが
TEXT_NODEでなければ、そのノードの全ての子に対してこの関数を呼び出す。
: [...e.childNodes].forEach(rec)
末端のノードに達するまでこの関数を再帰呼び出しする。
末端のノードであればこの時点で自然とコールスタックの積み上げは停止する。
Footnotes
-
arguments.calleeでも可能だが、現在は非推奨になっているため使用していない。 ↩