昔の画像はなぜ一行ずつ表示されたのか — インターレースGIFとプログレッシブJPEGの仕組み

28.8kモデムの時代、画像は上から一行ずつ、あるいはブラインドが閉じるように出てきました。あれは演出ではなく、画像フォーマットのデータが並ぶ順番がそのまま見えていただけです。3方式の仕組みを解説し、当時のデコード順のままCanvasで再現しました。

🕰️ 「読み込み中」が目に見えていた時代

ふと思い出した話から始めます。

30年ほど前、インターネットが遅かった頃。画像が一気に表示されることはありませんでした。上から一行ずつじわじわ現れるもの。ブラインドが閉じるように粗い像から埋まっていくもの。全体がぼやけた状態からじわっとピントが合うもの。

当時は 28.8kbps のモデム(=電話回線でインターネットにつなぐ機械。ピーガガーという接続音のあれ)が主流で、100KB の画像 1 枚に 30 秒近くかかりました。テレホーダイ(=NTTの深夜定額サービス。23時から翌朝8時まで通話料が定額)の開始時刻を待ってから重いページを開く、という生活です。

で、思ったんです。

あれ、再現できるんじゃないか。

作りました。まず見てください。画面に入ると当時と同じように読み込まれます。見逃したら、下のボタンで何度でも。

地平線に沈む太陽と、手前に伸びるワイヤーフレームのグリッド

これは GIF のインターレースという方式の再現です。以下、なぜこう見えていたのかを解きます。


🧩 あれは演出ではなく、データが届く順番そのものだった

いちばん大事なのはここです。あの見え方は「読み込み中アニメーション」ではありませんでした。 誰も演出として作っていない。

画像ファイルの中でデータが並んでいる順番が、そのまま画面に出ていただけです。ブラウザは「届いたぶんだけ描く」という素直なことをしていて、回線が遅かったから、その過程が人間の目に見えていた。

だから見え方が3種類あったのは、データの並べ方が3種類あったからです。

📜 上から一行ずつ — ベースラインJPEG / 非インターレースGIF

いちばん素直な並べ方です。画像を上の行から順番に、1行ずつ記録していく。

上から一行ずつ読み込まれる夕日の風景

届いたバイトから順に描けば、当然こうなります。「今どこまで届いたか」が定規のように可視化されていたわけです。

ちなみにこの方式、待っている側からするとけっこう辛いものでした。目当ての画像かどうかが、半分以上ダウンロードするまで分からない。人の顔写真なら、髪の毛から順に出てくるので、顔が見えるまで待つしかない。

🪟 ブラインドが閉じるように — GIF89aのインターレース

そこで発明されたのがインターレース(=飛ばし飛ばしに記録する方式)です。GIF89a(=1989年に策定された GIF の規格)に入っています。

行を4回に分けて、間引きながら送ります。

パス 送る行 この時点で 見え方
1回目 0, 8, 16, 24… 全体の 1/8 極端に粗いが、全体の構図はもう分かる
2回目 4, 12, 20… 1/4 少しマシに
3回目 2, 6, 10… 1/2 だいぶ見える
4回目 1, 3, 5, 7… 完成 くっきり
ブラインドが閉じるように読み込まれる夕日の風景

ブラインドが閉じるように見えるのは、まだ届いていない行を、直前に届いた行で埋めているからです。1回目のパスで届いた 0 行目を、1〜7 行目にも縦に引き伸ばしてコピーする。だからあの独特の「縞が細かくなっていく」動きになります。

これは当時としてはかなり実用的な発明でした。全体像が最初の 1/8 で分かるので、「目当ての画像じゃないな」と判断して次のページに行ける。 回線が遅い時代の、まっとうなユーザー体験の工夫です。

PNG はこれをさらに進めて Adam7(=縦横の両方向に間引く7パス方式)を採用しています。名前は考案者 Adam M. Costello に由来します。

🌫️ ぼやけてから鮮明に — プログレッシブJPEG

3つ目がこれです。仕組みが他の2つとかなり違います。

JPEG は画像を DCT(=離散コサイン変換。画像を「なだらかな成分」と「細かい成分」に分解する処理)にかけて記録しています。空のグラデーションのようななだらかな変化が低周波、輪郭や細い線のような急な変化が高周波です。

プログレッシブ JPEG は、この低周波のほうから先に送ります

ぼやけた状態から徐々に鮮明になる夕日の風景

最初に届くのは「だいたいの色と形」だけ。だからぼやけて見える。そこへ細部が乗っていって、最後にシャキッと決まる。

インターレースとの違いは、粗さの出方です。インターレースは縦方向にガタガタしますが、プログレッシブはピントが合っていないように見える。データの分け方が「行」なのか「周波数」なのかの差が、そのまま見え方の差になっています。


🛠️ どう作ったか

Canvas(=JavaScript で自由に絵を描ける HTML の要素)で実装しました。技術的な判断をいくつか書いておきます。

元の画像には手を出さず、canvas を上にかぶせる

既存の <img> を包むだけで動くようにしました。

<retro-image mode="interlace" duration="1400">
  <img src="/photo.jpg" alt="..." width="960" height="540">
</retro-image>

canvas は position: absolute で img の上に重ねているだけです。JavaScript が動かなければ、ただの画像として普通に表示されます。

`drawImage` だけで完結させた

これが今回いちばん効いた判断です。

Canvas でピクセルを操作するときは getImageData という関数を使うのが普通なのですが、これを使うと canvas が tainted(=汚染された)状態になり、別のドメインから読み込んだ画像では例外を投げて止まります。CDN(=画像配信サーバー)に画像を置いている場合、追加のサーバー設定なしでは動かなくなる。

そこで、描画を drawImage(=画像の一部を切り取って、指定の位置に貼る関数)だけで組み立てました。

  • 走査線: 元画像の上から N 行ぶんを切り取って、同じ位置に貼る
  • インターレース: 1行だけ切り取って、8行分の高さに引き伸ばして貼る
  • プログレッシブ: 縮小した画像を作って、元のサイズに拡大して貼る

全部「切り取って貼る」で表現できます。ピクセルを直接読まないので canvas は汚染されず、CORS の設定が要りません。

縞の粗さは「表示サイズ」で刻む

地味ですが大事な調整です。インターレースの「8行おき」を元画像の実ピクセルで刻むと、いまどきの 4000px ある画像では縞が細かすぎて見えません。当時の画像はせいぜい 300〜600px でしたから。

なので画面に表示されているサイズを基準に行を刻んでいます。忠実さより、当時の見た目の再現を優先しました。


⏳ 当時の待ち時間まで再現する

せっかくなので、回線速度を指定できるようにしました。

<retro-image mode="interlace" speed="28.8k">

こう書くと、画像の実際のファイルサイズから当時の所要時間を計算します。 実効スループットは bps ÷ 10 バイト毎秒で見積もっていて、28.8k なら約 2.8KB/s。当時の体感とだいたい合います。

ちなみに 56k を指定したときの値を 53000bps にしてあるのは、V.90 モデムの下り速度が、アメリカの規制で 53.3kbps に制限されていたからです。「56k」と名乗りながら 56k は出ませんでした。

なお、そのまま使うと 40KB の画像で 14 秒待たされます。実用にならないので、既定では 8 秒で打ち切る設定にしてあります。

下は打ち切りを外した、28.8k モデムそのままの設定です。勝手に始まると邪魔なので、押したときだけ動くようにしてあります。本当に 14 秒かかります。

28.8kモデムの速度で読み込まれる夕日の風景

🔁 30年後、同じ問題が「CLS」という名前で戻ってきた

当時、画像の <img> タグに widthheight を書いておかないと、画像が届くたびにページのレイアウトがガタン、ガタンとずれました。 読んでいた行が突然下に飛んでいって、目で追い直す羽目になる。

この現象、いま名前がついています。CLS(Cumulative Layout Shift = 累積レイアウト変更)。Google が 2020年に Core Web Vitals(=Web サイトの品質指標)の一つとして発表したものです。

そして対策は、30年前とまったく同じ。widthheight を書く。 それだけ。

同じ問題が新しい名前で戻ってきて、対策も変わっていない、ということです。

今回のコンポーネントは、この部分は再現していません。canvas を絶対配置で重ねているので、演出中も高さは 1px も動きません。


📦 置き場所

依存ライブラリなし、JavaScript ファイル 1 つ、MIT ライセンスです。この記事で動いているものが、そのまま実物です。

なお prefers-reduced-motion(=OS の「視差効果を減らす」設定)が有効な環境では自動再生しないようにしてあります。動きに酔いやすい人に無理やり見せるものではないので。


🎬 おわりに

作ってみると、当時のブラウザが何をしていたのかが具体的に分かりました。

ブラインドが閉じるあの動きは、「まだ届いていない行を、直前に届いた行で埋めている」だけだった。ぼやけて見えたのは、周波数の低いほうから届いていたから。当時はただ「そういうもの」として眺めていた画面の裏側が、コードにしてみると全部説明がつく。

役に立つかと言われると、正直そんなに立ちません。面白かったので作った、というだけの話です。

💡 この記事の内容を、業務に組み込んでみませんか?

福岡・東京の二拠点で、中小企業の AI 導入支援・業務自動化・技術顧問をやっています。
議事録の自動化、業務スクリプト、システム開発まで。
30分で「何から始めるべきか」を一緒に整理します。

© 2022-2025 infoHiroki. All rights reserved.