はじめに
- AI対応のため、設計書などは、マークダウンで書く場合が多くなってきましたが、その時によく使われるのが、mermaidでの図の記述です。
- mermaidはいろいろな図を簡単に書けていいのですが、画像になったときのサイズがうまく指定できず、設計書に載せると、文字がすごく小さくなってしまう場合がある。
- また縦横比率が合わずに、伸びたり縮んだり、見づらくなってしまいがちです。
- そんなときのために、今回は設計書Webサイトを構築した時に、HTMLで出力した場合の、拡大縮小ボタンと、全表示ボタンを作りました。
- ホイールでの拡大縮小の対応しています。マウスのドラッグでグリグリ動かせます。
作成した環境
- Github PagesでサイトをMDから構築
- 標準のjekyllで生成
- テーマはjust-the-docs
- MDの中に、mermaidで図を作成した。
コード
_includes\footer_custom.html
<script src="https://unpkg.com/mermaid@11/dist/mermaid.min.js"></script>
<script src="https://unpkg.com/@panzoom/panzoom@4.6.1/dist/panzoom.min.js"></script>
<script>
mermaid.initialize({
startOnLoad: false,
securityLevel: 'loose',
theme: 'default'
});
</script>
<style>
code.language-mermaid {
display: block;
overflow: hidden;
position: relative;
border-radius: 4px;
border: 1px solid #aaa;
}
.language-mermaid svg {
display: block;
border: 1px solid #ddd;
}
.panzoom-controls {
display: flex;
gap: 8px;
padding: 8px;
background: #f5f5f5;
border-bottom: 1px solid #ddd;
position: sticky;
top: 0;
z-index: 10;
}
.panzoom-controls button {
padding: 6px 12px;
font-size: 14px;
cursor: pointer;
border: 1px solid #ccc;
border-radius: 4px;
background: white;
transition: all 0.2s;
}
.panzoom-controls button:hover {
background: #e9e9e9;
border-color: #999;
}
.mermaid-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
animation: fadeIn 0.2s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.mermaid-modal-content {
position: relative;
width: 95vw;
height: 95vh;
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.mermaid-modal-close {
position: absolute;
top: 16px;
right: 16px;
width: 40px;
height: 40px;
background: rgba(255, 255, 255, 0.9);
border: none;
border-radius: 50%;
font-size: 24px;
cursor: pointer;
z-index: 10000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
transition: all 0.2s;
}
.mermaid-modal-close:hover {
background: white;
transform: scale(1.1);
}
.mermaid-modal-content svg {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
border: 1px solid #ddd;
}
</style>
<script>
const initZoomControls = () => {
document.querySelectorAll('.language-mermaid svg').forEach(svg => {
if (svg.dataset.zoomInitialized) return;
svg.dataset.zoomInitialized = true;
const div = document.createElement('div', { className: 'panzoom-controls' });
const container = svg.parentElement;
const button1 = document.createElement('button');
button1.innerHTML = '🔍+';
button1.setAttribute('aria-label', 'Zoom in');
const button2 = document.createElement('button');
button2.innerHTML = '🔍−';
button2.setAttribute('aria-label', 'Zoom out');
const button3 = document.createElement('button');
button3.innerHTML = '↻';
button3.setAttribute('aria-label', 'Reset zoom');
const button4 = document.createElement('button');
button4.innerHTML = '⛶';
button4.setAttribute('aria-label', 'Full screen');
div.prepend(button1, button3, button2, button4);
const wrapper = container.parentElement;
wrapper.prepend(div);
const panzoom = Panzoom(svg, {
step: 0.5,
handleStartEvent: (e) => e.preventDefault()
});
button1.onclick = () => panzoom.zoomIn();
button2.onclick = () => panzoom.zoomOut();
button3.onclick = () => panzoom.reset();
svg.parentElement.addEventListener('wheel', panzoom.zoomWithWheel);
button4.onclick = () => {
const modal = document.createElement('div');
modal.className = 'mermaid-modal-overlay';
const modalContent = document.createElement('div');
modalContent.className = 'mermaid-modal-content';
const closeBtn = document.createElement('button');
closeBtn.className = 'mermaid-modal-close';
closeBtn.innerHTML = '✕';
closeBtn.setAttribute('aria-label', 'Close preview');
const svgClone = svg.cloneNode(true);
modalContent.appendChild(svgClone);
modal.appendChild(closeBtn);
modal.appendChild(modalContent);
document.body.appendChild(modal);
const modalPanzoom = Panzoom(svgClone, {
step: 0.5,
});
svgClone.parentElement.addEventListener('wheel', modalPanzoom.zoomWithWheel);
const closeModal = () => {
modal.remove();
document.body.style.overflow = '';
};
closeBtn.onclick = closeModal;
modal.onclick = (e) => {
if (e.target === modal) closeModal();
};
const escHandler = (e) => {
if (e.key === 'Escape') {
closeModal();
document.removeEventListener('keydown', escHandler);
}
};
document.addEventListener('keydown', escHandler);
document.body.style.overflow = 'hidden';
};
});
};
if (typeof mermaid !== 'undefined') {
document.addEventListener('DOMContentLoaded', () => {
console.log('Initializing mermaid diagrams with zoom controls...');
mermaid.run({
querySelector: '.language-mermaid'
}).then(() => {
console.log('Mermaid diagrams rendered.');
initZoomControls();
}).catch((err) => {
console.error('Mermaid error:', err);
});
});
}
</script>
ポイント
- 作成した環境により、HTMLのカスタムレイアウトでフッターを利用してスクリプトを実行しています。
- 他の場所でも動けば問題ないです。
- mermaidの図の生成と、生成した図に対するズームの設定を行っています。
- HTMLファイルとして記述しています。スタイルとスクリプト一緒に含めています。分けても問題ありません。
-
display: block;とoverflow: hidden;が必ず当たるようにしてください。- はじめは、CSSがテーマで書き換えられていて、うまく動作しなかった。
-
panzoom/panzoomのライブラリを使用して、ズーム等の処理をしています。- ZOOM処理のためボタンのつなぎ込みと、ホイール対応をコードに記述しています。
-
language-mermaidのクラス名で、mermaidが処理しているので、.language-mermaidで全体的に処理しています。環境によっては、クラス名等が環境により違うので変えてください。 - 全表示ボタンもズームを使用しつつ、実装してあります。
-
just-the-docsでのmermaidの設定を消し、このコードで処理するように修正しています。
HTMLの生成例
-
pre-code-svgの階層になっています。うまく動かない場合は、生成する環境で確認してください。
作成されたボタン
最後に
- 設計書の図がだいぶ見やすくなりました。大きな図を書いても問題なしです。
- 他の環境でも、同じ様に処理できれば、うまく表示されるはずです。
- デザインはシンプルに実装したので、変更してください。ボタンの位置など変えてもいいかも。
- 静的なサイトで設計書サイト作成しましたが、コメントとか記述できるようになると、うれしいですね。

