Markdownの画像2枚を前後比較スライダーに変換できるようにした

Markdownの画像2枚を前後比較スライダーに変換できるようにした
約3分で読めます

やりたかったこと

レタッチ記事を書くとき、加工前と加工後の画像をただ縦に並べるだけだと差分が追いにくいことがありました。
同じ位置を見比べられるようにしたかったので、記事本文のMarkdownだけで before/after 比較スライダーを作れるようにしました。

完成形はこんな感じです。

レタッチ後

使い方

書き方はかなり単純で、連続した2枚の画像にそれぞれ compare:beforecompare:after を付けるだけです。

![レタッチ前](./vrchat-retouch/images/before.png "compare:before")
![レタッチ後](./vrchat-retouch/images/after.png "compare:after")

これ以外の通常画像は、今までどおり普通の画像として表示されます。
専用コンポーネントを呼ぶ必要がないので、記事を書く側の負担を増やさずに済みます。

実装方針

実装は rehype でMarkdownのHTML ASTを見て、条件に合う画像ペアだけを比較スライダー用のHTMLへ変換する形にしました。

やっている判定は次の通りです。

  • 段落の中に意味のあるノードがちょうど2つあること
  • その2つがどちらも img 要素であること
  • タイトル属性に compare:beforecompare:after が1枚ずつ付いていること

条件を満たしたときだけ、figure 要素に組み替えて比較UIを生成します。
比較用ではない画像に影響を与えたくなかったので、変換条件はかなり限定しています。

rehype 側の処理

タイトル属性の判定には単純な正規表現を使っています。

const COMPARE_TITLE_PATTERN = /^compare:(before|after)$/i;
function parseCompareSide(title) {
const normalizedTitle = title.trim();
const match = COMPARE_TITLE_PATTERN.exec(normalizedTitle);
return match?.[1]?.toLowerCase() ?? null;
}

比較対象として認識できたら、画像2枚を次のような構造へ変換します。

<figure class="before-after-compare" data-before-after-compare="true">
<div class="before-after-compare__viewport">
<img data-compare-side="after" />
<div class="before-after-compare__overlay" aria-hidden="true">
<img data-compare-side="before" />
</div>
<input
class="before-after-compare__range"
data-before-after-range="true"
type="range"
min="0"
max="100"
step="1"
value="50"
/>
</div>
</figure>

ベースは after 側を全面表示して、その上に before 側をクリップして重ねる形です。
レンジ入力の値を CSS カスタムプロパティに反映して、見えている幅だけを変えるようにしています。

初期化スクリプト

クライアント側では、レンジ入力の値を --before-after-position に反映するだけの小さな初期化処理を入れています。

const COMPARE_SELECTOR = "[data-before-after-compare]";
const RANGE_SELECTOR = "[data-before-after-range]";
export const initializeBeforeAfterCompare = () => {
const compares = Array.from(document.querySelectorAll(COMPARE_SELECTOR));
compares.forEach((compare) => {
if (!(compare instanceof HTMLElement)) {
return;
}
const range = compare.querySelector(RANGE_SELECTOR);
if (!(range instanceof HTMLInputElement)) {
return;
}
const updatePosition = () => {
const position = Number.parseInt(range.value, 10);
compare.style.setProperty("--before-after-position", `${position}%`);
};
updatePosition();
if (compare.dataset.beforeAfterInitialized !== "true") {
range.addEventListener("input", updatePosition);
compare.dataset.beforeAfterInitialized = "true";
}
});
};

Astroの画面遷移後にも効いてほしいので、astro:after-swap でも再初期化するようにしています。

CSS 側でやっていること

見た目の本体はCSSです。
before 側のオーバーレイに対して表示幅を var(--before-after-position) で与え、中央には区切り線とハンドルを載せています。

この構成にすると、JavaScript側は値の同期だけで済みます。
DOMを毎回作り直したり、座標計算を増やしたりしなくてよいので比較的扱いやすいです。

実装してよかった点

  • Markdownだけで使える
  • 通常画像には影響しない
  • レタッチ記事やUI比較記事にそのまま流用できる
  • before/after の差分がかなり見やすい

特に良かったのは、記事を書くときの記法を増やしすぎずに済んだことです。
画像タイトルに意味を持たせるやり方は少し雑ではあるのですが、運用コストはかなり低いです。

今後

今の実装でも用途としては十分なのですが、今後触るならこのあたりは改善余地があります。

  • キャプション対応
  • キーボード操作時の見た目調整
  • モバイルでのハンドル視認性向上
  • 3枚以上の比較には反応しないことを明示するドキュメント追加

ひとまず、レタッチ記事の前後比較を気持ちよく見せるという目的は達成できました。
画像の変化を説明したい記事では、今後もこの形式を使っていくつもりです。

関連記事

麻雀で親の役満に振り込んで派手に消し飛んださにゃんちゃん
/ 1 min read

麻雀で親の役満に振り込んで派手に消し飛んださにゃんちゃん

午前2時にお酒を飲んでる状態で麻雀を打ってはいけない

VRChatのSSをしっとり寄りにレタッチしてみた
/ 3 min read

VRChatのSSをしっとり寄りにレタッチしてみた

Adobe Photoshop 2026で、暗さを残しつつ湿度と血色を足す感じのレタッチ手順をまとめました。

ぼかし処理おためし
/ 1 min read

ぼかし処理おためし

記事内に表示される画像にぼかしを入れることができるようになった!

次に読む

シリーズ一覧

すべて見る

このシリーズ情報は未設定です。シリーズ一覧ページから他の連載を探せます。