◆ DataURI 自体は 2MB 以上にもできるけど URL として開くときは 2MB まで
  ◆ a タグで開いて保存しようとするとエラーになる
◆ copy() で DataURI の文字列でクリップボードに保存して Node.js でファイルとして保存できる
◆ URL.createObjectURL だと 200MB 超えても保存できるので保存するときはこっちがよさそう

便利なので普段から DataURI 形式をよく使っています
ですが DataURI 形式にも欠点がありました

ファイルサイズです

Chrome だと 2MB までらしい

Canvas の画像を保存していたのですが 一部のファイルだけ保存エラーになっていました
何が原因だろうととりあえず img タグの src にエラーになっていた DataURI を設定してみたらちゃんと表示されました

保存するために a タグに download 属性をつけて click() しているので URL として開こうとすると制限にかかるのかもしれません
試しに window.open してみたらエラーじゃなくクラッシュしました
やっぱり Canvas で DataURI 形式で出力する部分や DataURI の扱い自体が対応していないんじゃなく URL として開くのがだめのようです

文字列の length を見ると エラーになっていたのは 2MB ちょっとのサイズで 保存できていたのは 1MB 台でした
昔何処かで Chrome は 2MB というのをみたような覚えもありますし 2MB が限度みたいです


[Download]
function download(url, filename){
const a = document.createElement("a")
a.download = filename
a.href = url
a.click()
}

[preview]
function preview(url){
const i = new Image()
i.src = url
document.body.append(i)
}

コピーして Node.js で保存する

ブラウザでファイルを保存となると download 属性付き a タグクリックくらいしかないです

今回は画像なので img タグで表示してデスクトップなどにドラッグして保存もできますがひとつひとつ手作業で画像表示してドラッグして保存はさすがに嫌です
それに今回は Canvas の画像だからできるものの 画像じゃないとできない方法です

なので DataURI のまま取り出して Node.js で画像として保存するということをやってみます


表示してコピーするのだと 2MB を超えてるのでちょっと重いです
なので

copy(canvas.toDataURL())

これでクリップボードに DataURI をコピーします
copy はコンソールから使えるものなので devtools で行います


次に Node.js でファイル形式で保存します
Node.js だと 2MB 超えていても関係なく保存できます

const fs = require("fs")
const urls = [***]
urls.forEach(save)

function save(url, num){
const b64 = url.replace(/^data:image\/png;base64,/, "")
fs.writeFile(`image${num}.png`, b64, "base64", function(err) {
err && console.log(err);
})
}

上の *** のところに DataURI 文字列をいれます
配列形式にしているので複数まとめてもできます

URL.createObjectURL()

と ここまで書いて URL.createObjectURL 使えば Node.js 使わなくてもいいんじゃない?とひらめきました

function dataURItoBlob(dataURI) {
const b64 = atob(dataURI.split(',')[1])
const u8 = Uint8Array.from(b64.split(""), e => e.charCodeAt())
return new Blob([u8], {type: "image/png"})
}

function download2(dataURI, filename){
const blob = dataURItoBlob(dataURI)
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.download = filename
a.href = url
a.click()

// ダウンロードの時間がわからないので多めに 最低 3s, 1MiB / sec として
// 終わった頃に revoke する
setTimeout(() => {
URL.revokeObjectURL(url)
}, Math.max(3000, 1000 * dataURI.length / 1024 * 1024))
}

こんな感じです
これだと 2MB を超えたファイルも保存されました

サイズが大きいのでダウンロードが終わったら URL を破棄したいのですが a タグダウンロードで終わったら呼び出されるコールバック登録はできなかったと思うので適当に setTimeout して revokeObjectURLしています


上で書いた方法は何かの web サービスのページから個人的に画像保存したいときには使えますが 自分で作るアプリケーションに保存機能をつけるというのには向かないです
ユーザに devtools 開いたり Node.js で保存してね というのはちょっとハードル高いですからね

ですが createObjectURL だとページ内の JavaScript だけで完結できます

文字列の最大長でも保存できた

createObjectURL ですが Chrome での文字列の最大長のテキストでも保存できました

copy(urls) と Node.js の方法でやっていたとき まとめて保存しようとして 200MB くらいを copy しようとすると devtools が落ちてしまいました
createObjectURL だとこれでもいけるかな っとやってみると問題なく保存できました

もうちょっと大きくなって 270MB くらいまでいくと Chrome の最大文字数の都合で複数の DataURI を 1 つの文字列にしようとしたところで文字列生成できないとエラーになります
JavaScript の文字列の最大の長さ

今回は全部半角文字なので 270MB は 2億7000万文字です

通常ブラウザで 1 ファイルで 200MB 以上のファイルを作って保存なんてしないでしょうし DataURI で保存できないなら URL.createObjectURL を使えば大丈夫そうです