一休.com Developers Blog

一休のエンジニア、デザイナー、ディレクターが情報を発信していきます

愛すべき Image API - 前世紀の技を現代で

この記事は一休.com Advent Calendar 2025の14日目です。

一休.com レストランの開発を担当している恩田 @takashi_onda です。

はじめに

今からご紹介するのは、フロントエンドカンファレンス東京 2025 でお話しようとしていた内容です。直前にコロナに感染してしまい、残念ながら登壇は泣く泣くキャンセルになったのですが、その際にブログであらためてご紹介すると言いながらこの時期となってしまいました。

Image API の特徴

ブラウザの Image API には面白い特徴があります。JavaScript で動的にインスタンスが作られて src がセットされたと同時にロードを開始しはじめるのです。

Image の即時ロード

他の外部リソースを読みこむ要素である HTMLScriptElement と比較してみると、その振舞いの違いがわかりやすいでしょう。

script

script 要素がロードを開始するのは、動的に生成された要素が DOM ツリーに追加された時点です。どちらの例もリソースは外部オリジンからで、取得元の性質に違いはありません。お手元のブラウザで Developer Tool を開いて、挙動の違いをご確認いただければと思います。

加えるならば、Image というコンストラクタを持っているというのも特徴ですね。他の要素1 は、JavaScript で動的に生成するには Document: createElement() という API を利用します。

React や Vue をはじめとする現代の Web フロントエンド開発では、直接 DOM 要素を生成するような場面はほとんどなく、読者の中には馴染みのない方も多いのではないかと思います。

さて、外部リソースを操作するという観点では同じようにみえる HTMLImageElementHTMLScriptElement が、なぜこのような振舞いの違いを持つのでしょうか?

最古の API

端的に言えば、歴史的経緯、で片付けられてしまうわけですが、少しばかり昔語りにお付き合いください。

Image が JavaScript から操作できるようになったのは Netscape Navigator 3.0 に搭載された JavaScript 1.1 からで、そのリリースは 1996年なので文字通り最古の API と呼んでよいと思います。

Internet Explorer 3.0 にも同様の機能が実装され、JavaScript を使う Web サイトも少しずつ登場しはじめました。実際的なユースケースとしては、フォームの送信前に入力値をチェックして window.alert で表示するクライアントサイドバリデーションが挙げられます。

現代に地続きの、本格的なフロントエンド開発に利用できる機能は Internet Explorer 4.0 で登場しました。そう、DOM と CSS です。そして、実際のプロダクトで利用しても大丈夫そうという機運が高まったのは、そのシェアが支配的になった 2000〜2001 年頃だと記憶しています。

閑話休題。

画像に話を戻すと、Image API はどんな場面で利用されていたのでしょうか?

大きく普及したテクニックに画像のロールオーバーがありました。特にボタン画像でよく使われていて、マウスカーソルをあわせるとボタンが浮き上がるような効果を実現していました。CSS がまだ存在せず、見出しやボタンなど装飾したい画面要素には画像を用いるしかありませんでした。

この画像ロールオーバーは、 Image のインスタンスを生成したときに即座に画像をロードしはじめる挙動の効果が、顕著に発揮されるユースケースでもありました。

この頃のインターネット接続にはモデムが使われていて、その速度は 28.8kbps がほとんど、最速の機種でも 33.6kbps という時代2でした。ボタンのような小さな画像であってもロードを待つ必要がありました。

ですが Image API によって画像が先読みされると、ボタンにマウスカーソルをあわせたとき、シームレスに画像が切り替わる UX が提供できていたのです。

当時の書き方を思い出しながら再現すると、以下のようなコードで画像のロールオーバーを実現していました。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
<HEAD>
<TITLE>Image Rollover Sample</TITLE>

<SCRIPT LANGUAGE="JavaScript">
<!--
// 画像をプリロード
btn_on  = new Image();
btn_off = new Image();

btn_on.src  = "button_on.gif";
btn_off.src = "button_off.gif";
// -->
</SCRIPT>
</HEAD>

<BODY BGCOLOR="#FFFFFF">

<!-- 画像のロードが終わっていれば即座に切り替わる -->
<A HREF="next.html"
   onMouseOver="document.btn.src=btn_on.src"
   onMouseOut="document.btn.src=btn_off.src">
  <IMG SRC="button_off.gif"
       NAME="btn"
       BORDER="0">
</A>

</BODY>
</HTML>

現代のアレンジ

ここで話が終わってしまえば、インターネット老人会の思い出話に過ぎません。

ですが、上記でご紹介した事前ロードのテクニックは今でも有効です。実際に私が開発を手がけている一休.com レストランでも現役で活躍しています。

Image API の効果

具体的には上記の効果を実現するために Image API を利用しています。

もちろん、実装にも現代的な味付けを加えています。

回線状態が悪い状況を想定しているので、ハイドレーション前の SSR された HTML だけで機能する必要があります。React や Vue のようなフレームワークに制御が渡る前、それも DOM が逐次的に組み立てられていく中で動作するのが理想です。

完全な画像がロードされる前に表示させておく極小のプレースホルダー画像は、Image CDN を利用して動的にオリジナル画像から生成しています。

では、具体的に実装を見ていきましょう。

<img
  src="image.jpg?auto=compress,format&lossless=0&fit=crop&w=3&h=3"
  data-full-src="image.jpg?auto=compress,format&lossless=0&fit=crop&w=176&h=176"
/>

SSR 時には、3x3 サイズで雰囲気だけが伝わる最小の画像を Image CDN で動的に生成しています。サイズは 300 byte 程度なので、回線状態がよくないときでもほとんど待つことなく表示されます。

画像のプリロード処理と置き換えは、<head> 内にインラインで埋め込んだ、 MutationOveserver を使ったミニマムな JavaScript で実現しています。

<script>
  (function () {
    function replace(img) {
      const fullSrc = img.dataset.fullSrc;
      if (!fullSrc) {
        return;
      }
      if (img.src === fullSrc) {
        return;
      }

      const full = new Image();
      full.src = fullSrc;
      full.onload = function () {
        img.src = fullSrc;
      };
    }

    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mr) => {
        if (mr.type === 'childList') {
          mr.addedNodes.forEach((node) => {
            if (node.nodeType === 1 && node.tagName === 'IMG') {
              replace(node);
            }
          });
        }
      });
    });

    observer.observe(document.querySelector('body'), {
      childList: true,
      attributes: false,
      characterData: false,
      subtree: true,
    });

    globalThis.initialImageObserver = observer;
  })();
</script>

ストリームで届く HTML がブラウザによって逐次的にパーズされ、順番に DOM が構築されていく過程を MutationObserver で監視します。

新しい img ノードがあらわれると、new Image でプリロード用の HTMLImageElement インスタンスを生成します。src には最終的に表示したいフルサイズ画像の URL を data 属性から取得してセットします。

最初に紹介したように、このタイミングで画像のロードを即座に開始します。動的に生成した HTMLImageElement は DOM に追加しないので、表示にはなんの影響も与えません。

画像のロードが完了した段階で、その load イベントで元の img ノードの src を差し替えて、プレースホルダー画像からフルサイズ画像になめらかに表示を切り替えています。

おわりに

ここまで読んでいただきありがとうございました。

思い出話を交えながら、最古の Image API がその特徴を活かしながら、現代でもままある回線状態が悪い場面でもユーザー体験を改善している事例をご紹介しました。

今日の視点で Web を構成する技術をみたとき、歪に見えることもあると思います。ですが、過去の制約や工夫の積み重ねが、今もなお、私たちに思わぬヒントを与えてくれるのが Web の面白いところだと感じています。


一休では、ユーザーにより良い体験をともに届けるエンジニアを募集しています。

www.ikyu.co.jp

まずはカジュアル面談からお気軽にご応募ください!

job.persona-ats.com


  1. 厳密には、他にも HTMLOptionElement がコンストラクタ Option を持っています。
  2. 最速のサイトとよくネタにされる阿部寛さんのホームページですが、この頃の一般的なつくりのまま残っている貴重なサイトで、Developer Tool でネットワーク速度を制限して表示すると、当時の雰囲気が体感できます。