Background-image エッジケース一覧

背景画像翻訳の設計議論用ページ

各セクションは docs/superpowers/specs/2026-05-27-background-image-translation-discussion.md の議論項目に対応している。 DevToolsで実際のDOMを確認しながら議論することを想定。

1. URLキーの不整合 (img vs background-image)

同じ画像を img タグと background-image で使った場合、それぞれ異なるキーで保存される。

下の左右は同じ画像ファイル (/cat1.webp) を表示しているが、 SDK内部での扱いが異なる。左は img.src = "/cat1.webp" の文字列をそのままキーとして使う。 右は getComputedStyle が返す絶対URL (http://...../cat1.webp) をキーとして使う。
議論ポイント: 顧客が同じ画像を2箇所で使っている場合、翻訳を2回設定する必要がある。

img タグ (相対URL)

cat as img
<img src="/cat1.webp">
// SDKでのキー: "/cat1.webp"

div with background-image (相対URL)

background-image: url("/cat1.webp");
// getComputedStyle が返す絶対URL:
// "url(\"http://localhost:3000/cat1.webp\")"

2. background-image の値の種類

URL以外にもgradient, image-set, cross-fadeなど多様な layer kind が存在する。

議論ポイント: url() image-set() のみ翻訳対象とし、 gradient/cross-fade/paint() などは passthrough として元のまま保持する。 parser は top-level のカンマで layer を分割する必要がある (gradient 内のカンマと区別)。

単一 url()

background-image: url("/cat1.webp");

複数 url() レイヤー (上に小さい画像、下に大きい画像)

background-image:
  url("/al-feature.png"),   /* layer 1 (top) */
  url("/cat2.webp");        /* layer 2 (bottom) */

url() + linear-gradient() ミックス (写真の上に暗いオーバーレイ)

白いテキスト
background-image:
  linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)),  /* passthrough */
  url("/cat3.webp");                                   /* translatable */

image-set() (DPRに応じた切り替え)

background-image: -webkit-image-set(
  url("/al-feature.png")    1x,
  url("/al-feature-2x.png") 2x
);

gradient のみ (URL なし — 抽出対象外)

background-image: linear-gradient(135deg, #667eea, #764ba2);
// URLがないため、SDKは何も登録しない

3. 特殊なURLスキーム (data:, blob:, #fragment)

非HTTP(S) のURLスキームをどう扱うか。

議論ポイント: 既存の img タグの処理に合わせて、特殊URLも他のURL同様に抽出するか、 スキームでフィルタするか。data: URL は base64 で巨大になりがちで、translation mapに入れる際の懸念がある。

data: URL (インライン SVG)

background-image: url("data:image/svg+xml;utf8,<svg ...><circle cx='12' cy='12' r='10' fill='%23ef4444'/></svg>");
// SDKでのキー: data: URL の文字列全体 (長い)

SVG フラグメント参照 (#fragment)

background-image: url("/sample.svg#some-fragment");
// SVG sprite の特定要素を参照するパターン

blob: URL は URL.createObjectURL() で生成され、 ページ存続期間のみ有効。デモは省略 (Inspect DOM で挙動だけ議論)。

4. 疑似要素のスコープ

spec が background-image を許可するすべての疑似要素を対象とする。

議論ポイント: サポートする疑似要素の数 × 全要素数 でgetComputedStyle の呼び出し回数が決まる。コスト vs カバレッジ。
下記の各例で ::before, ::after 等は実DOMには現れないが、 DevToolsの「Computed」タブで背景画像が確認できる。

::before に背景画像 (アイコンとして)

左にアイコン (::beforeで背景画像)
.al-demo-before::before {
  content: "";
  position: absolute;
  width: 32px; height: 32px;
  background-image: url("/al-feature.png");
}

::after に背景画像

右にアイコン
.al-demo-after::after {
  content: "";
  background-image: url("/cat1.webp");
}

::before と ::after の両方

.al-demo-both::before { background-image: url("/cat1.webp"); }
.al-demo-both::after  { background-image: url("/cat2.webp"); }

host + ::before + ::after の3つ全部

.al-demo-all-three           { background-image: url("/cat3.webp"); }  /* 本体の背景 */
.al-demo-all-three::before   { background-image: url("/cat1.webp"); }  /* 左の帯 */
.al-demo-all-three::after    { background-image: url("/cat2.webp"); }  /* 右の帯 */

::backdrop (<dialog> 要素)

DevToolsで ::backdrop の background-image を確認

dialog::backdrop {
  background-image: url("/cat3.webp");
}

::first-letter

Aで始まる文章。最初の文字に背景画像が適用される。

p::first-letter {
  background-image: url("/al-feature.png");
  background-clip: text;
}

::file-selector-button

input[type="file"]::file-selector-button {
  background-image: url("/cat2.webp");
}

::-webkit-scrollbar-thumb (Chromeのみ)

スクロールしてスクロールバーを確認:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

.al-demo-scrollbar::-webkit-scrollbar-thumb {
  background-image: url("/cat1.webp");
}

5. Hide のセマンティクス (複数レイヤー)

「非表示にする」=要素ごと隠す。レイヤー単位の非表示はサポートしない。

議論ポイント: 下の要素は3レイヤー (gradient + 2 images) で構成されている。 顧客が「badge を非表示にしたい」と思っても、SDKは要素全体を al-hidden で隠す。 レイヤー単位の非表示は modal UI とサーバ契約に大幅な追加が必要なため v1 では対応しない。

3レイヤーの背景 (gradient + badge + 写真)

テキスト + 3レイヤー背景
background-image:
  url("/al-feature.png"),                              /* layer 1: badge */
  linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0.4)),   /* layer 2: overlay */
  url("/cat3.webp");                                   /* layer 3: photo */

// "Hide" を選ぶと → 要素ごと al-hidden で消える

6. CSP (Content Security Policy)

strict CSP は inline style 書き込みと <style> ブロック注入を両方ブロックする。

議論ポイント: 顧客向けドキュメントに記載 + ランタイム警告なし。 下記のCSP設定の顧客は背景画像翻訳が無音で失敗する。

# 問題のあるCSP例
Content-Security-Policy: style-src 'self';
# → element.style.setProperty(...) も <style id="al-bg-image-rules">{...} も無効

# 必要な変更 (どちらか)
Content-Security-Policy: style-src 'self' 'unsafe-inline';
# または nonce/hash ベース

このページではCSPは設定していないので、ブラウザのDevToolsで挙動を確認できる。 実際のブロック挙動を見るには、サーバ側でCSPヘッダを追加して再現する必要がある。

7. 動的な state (hover, media query, etc.)

SDK は resting state のみ翻訳する。state-dependent な背景は元言語のまま。

議論ポイント: hover / focus / アニメーション / メディアクエリ / prefers-color-scheme は SDK の inline style override では追従しない。 元のCSSが state ごとに別の背景を持つ場合、SDK の inline !important は resting state のみカバーする。

:hover で背景画像が切り替わる

.al-demo-hover { background-image: url("/cat1.webp"); }
.al-demo-hover:hover { background-image: url("/cat2.webp"); }
// SDK は resting state (cat1.webp) のみ翻訳する

@media (min-width) で切り替わる (ウィンドウ幅700px以上で別画像)

.al-demo-media-query { background-image: url("/cat1.webp"); }
@media (min-width: 700px) {
  .al-demo-media-query { background-image: url("/cat2.webp"); }
}
// SDK が抽出した時点で適用されている方のみ翻訳できる
// ウィンドウリサイズ後に再抽出するかは未決

prefers-color-scheme でダーク時に切り替わる

@media (prefers-color-scheme: dark) {
  .al-demo-color-scheme { background-image: url("/cat3.webp"); }
}
// OS設定をdarkに変更したときに再抽出するかは未決

8. 顧客JSとの衝突

data-al-bg-id 属性、managed <style> ブロック、!important などの衝突可能性。

議論ポイント: SDKは pseudo override 用に data-al-bg-id 属性 + <style id="al-bg-image-rules"> を使う。命名の衝突可能性は低いが、 防御的に長い prefix にするかどうかが論点。

顧客が既に data-al-bg-id を使っているケース (架空)

この要素は元から data-al-bg-id="customer-existing-value" 属性を持つ。 SDKが pseudo override で同じ属性名を使うと衝突する。
<div data-al-bg-id="customer-existing-value">

// SDK が pseudo override で
//   element.setAttribute("data-al-bg-id", "42")
// するとこの値が上書きされる
//
// 防御策候補:
//   data-autolingual-bg-id (より長い prefix)
//   data-al-bg-id-<projectId> (project スコープ)

顧客の !important ルールが SDK の override を上回るケース

/* 顧客のCSS */
.al-demo-customer-important {
  background-image: url("/cat1.webp") !important;
}

// SDK は host element には inline style + !important で書き込むので勝つ
// しかし pseudo override の場合、selector specificity 次第で負ける可能性
Cookie 同意状態: 未決定