はんけトケのロゴタイプ
これは何?
開発ノート

地図の印刷

無視される印刷

ウェブ制作において、印刷(PDF含むプリンターデバイスへの出力)はないがしろにされがちな要素です。 カツカツの制作期間と予算でそこまで手が回らない、といった場合ももちろんあるでしょう。 しかし大抵は単なる認識不足です。

たとえばサイトをレスポンシブに作ったなら、用紙にフィットさせたり、最低でもスクリーンと同じに印刷されるよう調整するくらいたやすいはず。 にもかかわらず崩れて印刷されてしまう、もったいないサイトは大手でもめずらしくありません。 制作工程に印刷の確認が含まれてないのは明らかです。 制作者も注文主も、サイトを印刷する需要があるとは考えていないのでしょう。

MapLibreの印刷不具合

いっぽう地図は、印刷される機会が少しはあると考えられます。 ウェブGISの開発者もその点けっこう気にしているのではと想像しますが、探しても情報がろくに見つからず困りました。 はんけトケが地図表示に使っている、MapLibreの印刷についてです。

わたしたちが地図をブラウザーで見ている間、MapLibreは地図をウインドウやディスプレーに合わせて描いています。 これをそのまま印刷しても、紙の形に合いません。 はみ出したり余白が多すぎたりして体裁がとても悪い。

印刷時に印刷用スタイル(@media printのCSS)を適用すれば済むことだ。 常識ではそうですね。 ところが、MapLibreの地図にはなぜかそれが効かないのです。

前提条件

この現象は、現時点(2025年1月)で最新のMapLibre GL JS v.5とウインドウズ用Chrome 132で確認しました。

現在のブラウザーのシェアは、Edge含むChromium系が圧倒的で計約7割、次いでモバイルSafariが約1割強。 個人的な好みはさておいて、その他のブラウザーの対応を特に求められないかぎり、わたしはこの2者をウェブ開発のターゲットとしています。

モバイルSafari、数的にほぼiPhoneと見なせますが、スマートフォンで印刷する状況は少なそう。 したがって、Chromeで正常に印刷できればまず問題ないでしょう。 逆にいえば、少なくともChromeではちゃんと印刷できるよう努めるべきです。

対策は?

話を戻します。 この問題はなぜ生じて、いかに解決するか。 あれだけ普及しているMapLibreなのだから、議論や対策はネット上にあふれていそうなもの。 ですが、まったく見つかりません。 探し方が悪いのか、だれも印刷に興味がないのか。

ともあれ仕組みの面から素朴に考えれば、印刷が要求されたら用紙のサイズで地図を描き直せばいいだけのはず。 印刷用CSSが適用されない理由はわかりませんが、ならば代わりに、地図を明示的・強制的にリサイズして描き直すようプログラムすればいいのでは。

印刷処理の流れ

印刷を要求すると、ブラウザーは「これから印刷するよ」とJavaScriptに通知してくれます(beforeprintイベント)。 JavaScript側では、その通知を受け取るイベントリスナー内でHTML要素などをいじることができ、それを抜けると印刷(正確には印刷プレビュー)が始まります。

ここに地図を描き直すチャンスがあります。 が、イベントリスナー内でMapLibreにリサイズや再描画を指示してみても変化なし。 関係ありそうなメソッドをいろいろ試しましたが、いずれも効果ありませんでした。

ひょっとすると、地図はワーカーやフレームアニメーション要求などで非同期的に再描画されるが、ブラウザーがその完了を待たずに印刷してしまうのかもしれません。 というのは臆測ですが、印刷開始に非同期待ち合わせの仕組みがないのは確かで、不備ではないかと前々から建議はされていた模様。 いずれにせよ、挙動の詳細はわからず、わかったところでアプリケーション側ではこれ以上手の打ちようがなさそうです。

代替手段

らちが明かないので、あきらめてアプローチを変え、「印刷ビュー」モードをこしらえることにしました。

すなわち用紙サイズの地図を、印刷する前にスクリーン上で描いてしまうのです。 印刷プレビューが二重化するようで冗長ですが、判型やらオプションやらの選択肢を設けて存在意義を強調します。

もともと作ってある印刷用CSSを、ボタンが押されたらスクリーンに適用すればいいだけで、さほど手数はかかりませんでした。

それにしても、地図印刷の需要はあるはずなのに本当にこんな泥臭い手段しかないのか、いまだに釈然としません……(ちなみにMapLibre用印刷プラグインは存在するものの、実装の手間が省けるだけで根本解決ではない)。

余談:ChromeとFirefoxのバグ

印刷の話題ついでに。

Chromeには、ウェブページを開いて初回の印刷内容がなぜか空白になってしまうバグがあります。 だいぶ昔から報告され続けているが放置されている、もはや仕様のような現象です。 実は、上記印刷ビューの開発中にもこのバグに遭遇しました。 もしこれが起きたら、印刷ダイアログを閉じてもう一度開くとか、出力先デバイスを変えるとか、何かしら印刷内容が再描画されるよう仕向けると直るようです。

また、シェアは少ないものの主要ブラウザーのひとつであるFirefoxには、MapLibreの地図をまったく印刷できないバグがあります(おかしな挙動は他にもある)。 MapLibreの前身Mapbox時代からの現象で、対処法は一応判明しているものの、パフォーマンスが低下する副作用があるため採用しかねます。

いずれもこれを書いている現在の情報です。 将来解消されるかもしれません。

【追記】 印刷方法が判明

MapLibre GL JS v.5とウインドウズ用Chrome 132において、地図をスクリーンサイズから用紙サイズに直接描き直して印刷する方法がわかったので追記します。

beforeprintイベントリスナー内で、地図のコンテナー要素のwidth・heightスタイルを指定すれば良いようです。 サイズが親要素で決まる構成だったとしても、必ずこのコンテナー要素にサイズ指定をします。

コード例を示します。

<html>
<head>
<title>MapLibre GL JS v.5 & Chrome 132 地図の簡単な印刷処理例</title>
<script src="https://unpkg.com/[email protected]/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/[email protected]/dist/maplibre-gl.css" rel="stylesheet"/>
<style>
#map { width: 100%; height: 100% }

/* この印刷用スタイルがなぜか効かない!? */
@media print {
	#map {
		width: 210mm !important;
		height: 297mm !important;
	}
}
</style>
</head>
<body>
<div id="map"></div>
<script>
const	map = new maplibregl.Map({
	container: 'map',
	style: 'https://demotiles.maplibre.org/style.json',
	center: [139.73879, 35.65811],
	zoom: 4
});

// 印刷開始イベントリスナー
window.addEventListener('beforeprint', (e) => {
	// 地図コンテナー要素を用紙サイズに変更(ここでは仮にA4縦)
	const	container = map.getContainer();
	container.style.width  = '210mm';
	container.style.height = '297mm';
});

// 印刷終了イベントリスナー
window.addEventListener('afterprint', (e) => {
	// 地図コンテナー要素のサイズを元に戻す(ここでは仮に100%)
	const	container = map.getContainer();
	container.style.width  = '100%';
	container.style.height = '100%';
});
</script>
</body>
</html>

印刷用CSSで同様の指定をしても効果がなかったため、印刷要求時にはこの要素のリサイズや、それに伴うMapLibreの再描画はされないものと理解していました。 が、ふと思い立ってこれを試してみたら効いたのです。

わかってしまえば簡単なことでした。 しかし直感的にも論理的にも不可解な、これは現Chromeへの対症療法にすぎません。 こんな方法はいずれ不要になるかもしれないし、しても効かなくなるかもしれません。

別の問題

ちなみに、こうして用紙サイズへ直接印刷できるようになって気づきましたが、地図タイルが一部欠けて印刷されてしまうことがあります。

スクリーンでは未表示だった地図の領域が用紙サイズでは表示される場合、タイルのロードが間に合わないためです。 印刷開始にはやはり非同期待ち合わせの仕組みが必要そうですね。

結局、印刷ビューを経て印刷するのが最も無難なようです。 あれの実装が無駄にならずに済み喜ぶべきでしょうか。

余談:モバイルSafariのバグ

ついでにモバイルSafariについて。

前提条件に述べたとおり、モバイルSafariの印刷機能は無視してもいいのですが、なにしろ日本ではシェアが大きく、不具合があると気になります。

ただ、ウェブ開発者ならご存じのとおりSafariは固有の挙動が多く、かつてのIE対応のごとき不毛さがあり深入りしたくありません。 わたしの方針としては、あいまいな線引きながら「“簡単”にできる」ところまではとりあえず対処することにしています。

詳しくはいちいち書きませんが、今回もやはりモバイルSafari固有の問題がいくつか生じ、そのためだけにCSS構成を変えるくらいまでは手をかけました。

しかしまだおかしい。 が、アップルの尻拭いに無償奉仕する義理は(これ以上)なく、あとはSafariの責任と割り切って放置します。 そのうち自然に直るかもしれないし、もっと悪化するかもしれません。

印刷は鬼門

スクリーンや印刷シミュレーターではちゃんと効く、標準準拠かつありふれたスタイル指定が効かない時点で、印刷レンダラーの不具合・瑕疵かしは明らかです。 長々書きましたが、要はその瑕疵の回避に苦労した、というだけの記事でした。

冒頭の話に改めて戻ると、地図だのWebGLだの使わない一般サイトではここまで酷くないながら、ヤブヘビなので印刷に触れたくない気持ちも理解できます。 そうしてだれも触れないため改善も進まない、悪循環ですね。

beforeprint・afterprintイベントのタイミングはブラウザーによりまちまちとか聞くし、Firefoxはいまだにちゃんと印刷してくれないし、印刷機能は不遇すぎて鬼門と化しています。 ブラウザーの印刷周りの開発ペースはかなり遅く、今回調べたことが今日明日役に立たなくなってしまうことはないと思います。 それでも本来無用なはずのことに労力を費やしたわけで、まさにIE対応のごとき徒労感を覚えた一件でした。

用語・注釈

イベント
イベント駆動型プログラミング(発生した事象に応じて動く受動的作法)において、ある事象の発生を知らせる信号。
イベントリスナー
イベントを寝て待つ関数。あらかじめ登録しておくと、イベントが発生したときに呼び出される。
ワーカー(ウェブワーカー)
JavaScriptにおけるバックグラウンド実行スレッド。
IE対応
非標準の仕様・挙動への対処をやむなく迫られること。