とりあえず「正方形で充填」の条件をクリアするなら最小の正方形の大きさを決めそれを方眼紙のようなグリッドと見立て、充填に用いる様々な大きさの正方形を「グリッドの整数倍の大きさ」とすれば比較的素朴なアルゴリズム(※)で達成できると思います。
※: 素朴なアルゴリズム
例えば、大きな正方形から埋め始め、ある大きさの正方形の空きがなくなったらもう少し小さな正方形にして埋めていき・・・を繰り返し、最後にグリッド単位の正方形で残りを埋めると以下のような感じにできます。
しかし「デザイン」というのは難しいもので、こうした素朴な方法でやった結果が「必ずしも意図したデザインになるとは限らない」と思います。
一見して質問にある例は「充填に用いる正方形の大きさのバリエーションや数が押さえられておりスッキリしたデザインになっている」と感じます。
一方、自分の回答例は「アルゴリズムは単純だが、充填に用いた四角形の大きさのバリエーションや数がごちゃごちゃしていて今一つよくない」と見えるのではないでしょうか?
人間が「よい」と感じるデザインには「単に正方形で充填できればよい」という以上に「なんらかの評価基準がある」と思います。その評価基準を想定して「ほどよく埋めるアルゴリズム」を考えないといけなくて「そこが難しい点」ではないでしょうか?
質問者さんは「例は挙げておられる」のですが「具体的な評価基準」を述べておられません。よって「実際に何を狙いとしたデザインにしたいのか」が曖昧なため、どういうアルゴリズム(方針)がよいか案を出しにくいと思います。回答側がデザイン基準まで想定することはできるでしょうけど、それはもう「回答者独自のデザインアイデア」であって回答が発散しかねないのでは?
追記:とりあえずアルゴリズムの雰囲気を示すものとしてPythonの実装例を挙げます。
JavaScriptの基本機能とnumpyライブラリーを用いたPythonを比べると、後者の方が多次元配列処理の記述能力が高いためJavaScriptに書き直すならnumpyがやっていることを理解する必要があります。numpyに大きく依存した記述についてはコメントを書いておきました。やっていることの要点さえつかめればJavaScriptでも二次元配列の要素を調べたり変更したりするループ処理っぽいものを書けば実現は容易です。
Python
1from tkinter import * # Pythonの標準GUIライブラリー
2import numpy as np # 行列演算を簡便に記述することができるライブラリー
3
4G = 4
5W, H = 16*5, 9*5
6CF = '#c5eb99' # 充填色
7CB = '#FFF' # ボーダー色
8
9
10def range2(n):
11 prime = 2**31-1 # 適当な大きさの素数
12 return map(lambda x: prime * x % n, range(n))
13
14
15def find(r, cv: Canvas, a, range):
16 ww = W - r + 1
17 hh = H - r + 1
18 x0 = np.random.randint(0, ww)
19 y0 = np.random.randint(0, hh)
20 for ofs in range(ww * hh):
21 dy = ofs // ww
22 dx = ofs % ww
23 y = (y0 + dy) % hh
24 x = (x0 + dx) % ww
25 # 以下のif文は配列aの1次元目(y座標)がy~y+r-1まで、
26 # 2次元目(x座標)がx~x+r-1までの要素が全て0かを判定するものです。
27 if np.all(a[y:y + r, x:x + r] == 0):
28 # 以下の代入文でy座標がy~y+r-1, x座標がx~x+r-1の範囲の全要素を1にしてます
29 a[y:y + r, x:x + r] = 1
30 x1, y1 = x * G, y * G
31 x2, y2 = (x + r) * G - 1, (y + r) * G - 1
32 cv.create_rectangle((x1, y1), (x2, y2), fill=CF, outline=CB)
33 return True
34
35
36def main():
37 a = np.zeros((H, W), dtype=np.uint8) # HxWの2次元行列(初期値0)を生成している
38
39 root = Tk()
40 cv = Canvas(root, width=W * G, height=H * G) # HTMLのcanvasみたいなもの
41 cv.pack()
42 # 空間充填に用いる正方形の大きさの候補のリスト
43 # この値を変えると結果の雰囲気は大分変化する
44 rs = [20, 10, 4, 2, 1]
45 for r in rs:
46 while find(r, cv, a, range2):
47 pass
48 root.mainloop()
49
50
51if __name__ == '__main__':
52 main()