以下の内容はhttps://ytyaru.hatenablog.com/entry/2025/04/04/000000より取得しました。


SVGZをブラウザで表示する方法2 クライアント編(成功)

 SVGなら表示できるが、SVGZが表示できない。クライアント側で解決できた。

成果物

SVGZをブラウザで表示する方法

 SVGZはSVGGzip圧縮しただけなので、それを展開してSVGで返却してやれば表示可能なはず。そのための機能をサーバかクライアントで実装すればいいはず。

  1. Webサーバを自作する(前回
    1. Apatch
    2. Python
  2. クライアントで展開する
    1. CompressionStream API
  1. Webサーバを自作する

1. Webサーバを自作する

1. Apatch

 ググったら出てくる方法。

 WebサーバアプリApatch上で.htaccessファイルを配置し、以下内容を追記する。

AddType image/svg+xml .svg .svgz
AddEncoding gzip .svgz

 ただ、私はApatchサーバを作ったことがないので、それ以前に何をどうすればいいかサッパリ判らない。いきなり上記のようなことを言われてもまるで判らない。

2. Python

 前回PythonHTTPSローカルサーバを立てた。これをそのまま使えば表示できるかも? と思ったがダメだった。

 おそらく拡張子svgzファイルをgzip展開して返却するような機能をサーバに実装すれば表示できるようになるだろうが、それをPythonコードでどう実装すればいいのかサッパリ判らない。

 流石にPythonやそのライブラリから勉強し直すのは避けたい。私はただSVGZを表示して欲しいだけなのだから。一応、少しだけコードを書いて試行錯誤したがダメだった。

1. extensions_map

 前回のサーバに以下を挿入した。

Handler.extensions_map['.svgz'] = 'image/svg+xml'

 他にも以下のようなパターンを試してみた。

Handler.extensions_map['.svgz'] = 'image/svg+xml;Content-Encoding=gzip;'
Handler.extensions_map['.svgz'] = 'image/svg+xml;charset=UTF-8;Content-Encoding=gzip;'

 が、ダメだった。SVGZファイルを参照する<img>タグで画像が表示されなかった。念の為、全体のコードを掲載しておく。

run_server.py

import ssl
from http.server import HTTPServer, SimpleHTTPRequestHandler

PORT = 443
CERTFILE = "./localhost.pem"

Handler = SimpleHTTPRequestHandler
Handler.extensions_map['.svgz'] = 'image/svg+xml;charset=UTF-8;Content-Encoding=gzip;'

context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(CERTFILE)

with HTTPServer(("", PORT), Handler) as httpd:
    print("serving at address", httpd.server_address, "using cert file", CERTFILE)
    httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
    httpd.serve_forever()
2. SimpleHTTPRequestHandler

 SimpleHTTPRequestHandlerPythonでHTTPサーバを作る時のクラス。前回コードでもこれを使った。これを改造してSVGZのときはSVGに変換して返すように実装すればいいはず。

 私はサーバの知識がまったくない。でも、適当にググってコードを書いてみた。

import os.path
import zlib
import urllib
from http.server import SimpleHTTPRequestHandler
#https://github.com/ksmith97/GzipSimpleHTTPServer/blob/master/GzipSimpleHTTPServer.py#L85
class SvgzHTTPRequestHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        # 拡張子が.svgzならGzip展開する
        path = self.translate_path(self.path)
        if 'svgz' == os.path.splitext(path):
            with urllib.request.urlopen(path) as f:
                dec = gzip.GzipFile(fileobj=f)
                svg = dec.read().decode("utf-8")
                self.send_header("Content-length", str(len(str(svg))))
                self.send_header("Content-Type", "image/svg+xml")
                self.end_headers()
                self.wfile.write(svg)
                self.wfile.flush()
        else:
            super.do_GET()
        
    def translate_path(self, path):
        path = path.split('?',1)[0]
        path = path.split('#',1)[0]
        path = posixpath.normpath(urllib.unquote(path))
        words = path.split('/')
        words = filter(None, words)
        path = os.getcwd()
        for word in words:
            drive, word = os.path.splitdrive(word)
            head, word = os.path.split(word)
            if word in (os.curdir, os.pardir): continue
            path = os.path.join(path, word)
        return path

 前回のスクリプトで起動し、ブラウザで警告を解除したが、サーバ起動に失敗したようで以下ログが端末に表示された。

----------------------------------------
Exception happened during processing of request from ('127.0.0.1', 44652)
Traceback (most recent call last):
  File "/usr/lib/python3.7/socketserver.py", line 316, in _handle_request_noblock
    self.process_request(request, client_address)
  File "/usr/lib/python3.7/socketserver.py", line 347, in process_request
    self.finish_request(request, client_address)
  File "/usr/lib/python3.7/socketserver.py", line 360, in finish_request
    self.RequestHandlerClass(request, client_address, self)
TypeError: 'module' object is not callable
----------------------------------------

 ググったら書き込みの所で出るエラーらしい。self.wfile.write(svg)の所だと思う。でも、サーバの仕組みを理解していないので、これ以上は読み取れなかった。

 私の技術力ではサーバ側からのアプローチはこれが限界。

 これら失敗コードは以下リポジトリにアップした。

2. クライアントで展開する

 Gzip展開するだけならクライアントでも可能なのでは? わざわざサーバで実装せずともクライアントで実装できるのでは? サーバの勉強せずに済むのでは?

 というわけで試してみた。

1. CompressionStream API

 JavaScriptの標準APICompressionStream APIがある。Chrome 80で実装された機能。

クラス 用途
CompressionStream 圧縮
DecompressionStream 展開

 今回は展開だけ使う。以下のように使うらしい。

const ds = new DecompressionStream("gzip");
const decompressedStream = blob.stream().pipeThrough(ds);

1. 最小コード

 全体の流れは以下のようになるはず。

  1. fetch APIでSVGZファイルのblobデータを取得する
  2. DecompressionStreamGzipを展開する
  3. 2のバイナリデータをTextDecoderUTF-8テキストに変換する
  4. 3をHTMLのbodyに挿入して表示確認する

 これをJavaScriptで実装すると以下。

window.addEventListener('DOMContentLoaded', async(event) => {
    const res = await fetch('asset/image/icon/clipboard-8.svgz');
    const blob = await res.blob();
    const ds = new DecompressionStream('gzip');
    const stream = blob.stream().pipeThrough(ds);
    const decompressed = await new Response(stream).arrayBuffer();
    const svg = (new TextDecoder()).decode(decompressed);
    document.querySelector('main').innerHTML = svg;
});

 ローカルサーバを起動して表示確認すると成功した。異常にデカく表示されたけど。

 とにかくSVGZは単にテキストをGzip圧縮しただけのファイルなので、それを展開してSVGテキストにしてインライン挿入すれば表示できることは確認できた。

2. SVGスプライトをGzip圧縮

 もう少し改善してみる。

 SVGスプライトは複数のSVG画像を<symbol>定義して<use>で参照するもの。これにより複数ヶ所で参照する場合のコードが短縮でき、HTML文書サイズ軽減につながる。

 なら、SVGスプライトをGzip圧縮すればファイルサイズを節約できる。それをGzip展開して使用すれば、HTML文書サイズも節約できる。方法は次のようになる。

  1. SVG画像をSVGスプライト形式にする(<symbol>)
  2. 1のファイルをGzip圧縮して拡張子をSVGZにする
  3. CSSで1文字分のサイズにする
  4. 2のファイルをGzip展開してSVGに戻す
  5. 4を参照するコードを書く(<use>)
sprite.svg

 ここでは二つのアイコン画像を定義した。それぞれのid属性値をclipboard, fileとした。HTML側でアイコン参照するときに使う。

 <symbol>の中にはアイコン画像時<svg>内にあった<path>要素を含める。<svg>要素丸ごと含めても問題ないし、アウトラインデータだけなら<symbol>d属性でもいい。今回は以下のようにした。 

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0">
<symbol id="clipboard" viewBox="0 0 256 256"><path style="opacity:1;fill-opacity:0;stroke:currentColor;stroke-width:16;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M80.08 8.04h95.84v56.658H80.08ZM45.04 36.488V247.96h165.92V36.488h-35.04v28.21H80.08v-28.21Z"/></symbol>
<symbol id="file" viewBox="0 0 256 256"  ><path style="opacity:1;fill-opacity:0;stroke:currentColor;stroke-width:16;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M224 72h-64V8ZM32 8v240h192V72h-64V8Z" fill="none"/></symbol>
</svg>

 上記ファイルを以下コマンドでGzip圧縮し、sprite.svgzファイルを生成した。上記JSコードではこのSVGZファイルをfetch APIで取得している。

gzip -c sprite.svg > sprite.svgz

 CompressionStreamを使い、結果をファイルダウンロードするJSコードを書けば、Gzip圧縮ファイル作成できるはず。でも実装が面倒だったのでLinuxコマンドで済ませた。

main.js

window.addEventListener('DOMContentLoaded', async(event) => {
    const res = await fetch('asset/image/icon/sprite.svgz');
    const blob = await res.blob();
    const ds = new DecompressionStream('gzip');
    const stream = blob.stream().pipeThrough(ds);
    const decompressed = await new Response(stream).arrayBuffer();
    const sprite = (new TextDecoder()).decode(decompressed);
    const css = `<style>.icon{width:1em;height:1em;}</style>`
    const use1 = `<svg class="icon"><use href="#clipboard"></use></svg>`
    const use2 = `<svg class="icon"><use href="#file"></use></svg>`
    document.querySelector('main').innerHTML = sprite + css + use1 + use2;
});

 ローカルサーバを起動して表示確認すると以下。成功!

 ファイルサイズが小さくできた。複数アイコンがあっても、スプライトにまとめたおかげでGZip展開が一度で済む。そして参照コードも<use>のおかげで短くなりHTML文書の短縮になる。(ただし二回以上参照した場合に限る。今回のように同アイコンを一度しか参照していないと逆に冗長になる)

問題
  • 環境
    • HTTPSサーバ上でしか使えない(file:プロトコル上では動作しない(htmlファイルをブラウザで開いただけでは動作しない))
    • Chrome 80以降でないと使えない
    • JavaScript動作環境でないと使えない(JS禁止環境だと動作しない)
  • 用法
    • 対象画像の参照方法は上記コードにせねばならない(<img>要素などによる外部参照が使えない)
    • サイズ調整CSSなどを実装せねばならない

 パフォーマンスの低下が懸念される。

  • SVGZを参照するたびに以下が発生する
    • HTTPSが発生する(メモリ保持して一度だけリクエストするような実装が欲しいが開発コストかかる)
    • Gzip展開処理が実行される
      • JavaScript APIである(ブラウザがC言語等でネイティブ実装してくれたほうが高速なのでは?)

 そもそも他の画像ファイル同様、以下のようにシンプルに表示できるようにして欲しかった。

<img src="clipboard.svgz">

 これに関してはカスタム要素を使えばいくらか改善できるかもしれない。

<img is="svg-z" src="clipboard.svgz">

 たとえば上記のようなHTML要素を書けば、自動的に先述のJSコードを実行して、以下SVG要素に置換してくれるとか。

<svg>...</svg>

 流石にそこまで実装するとなると大変そう。

 そもそも、どのような出力をすべきなのか検討の余地がある。たとえばSVG画像そのままではなく、SVGスプライト形式で出力したほうがいいとか、SVGフォントをWOFF2形式に変換して文字コードとして参照したほうが、もっとHTMLファイルサイズを縮小できるとか。なら実装する必要性なくなるのでは? CSSどうするの? 色は? 他の属性は? ライト/ダークモードに対応できる? 等々。

 アイコンを表示する際、果たして最終的にどのような形式で実装すべきか? どのような方法があって、その違いは何で、戦略的なメリットとデメリットは何か? どのような条件のとき、どのような方法が最善か? それらを調査・理解した上で、実装するか否かを判断したい。

 たかがアイコン表示したいだけなのに、次々と仕事が増えていく……。




以上の内容はhttps://ytyaru.hatenablog.com/entry/2025/04/04/000000より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14