Кэширование SVG-спрайта в localStorage

Перевод статьи Osvaldas Valutis «Caching SVG Sprite in localStorage».

Есть два способа использования SVG в HTML через <use>: с внешним источником или заинлайненым. «Элемент use берёт узлы SVG-документа и дублирует их где-нибудь» — MDN. В первом случае вставка SVG обычно выглядит так:

<svg><use xlink:href="sprite.svg#cart"></use></svg>

Плюсы:

  • Файл sprite.svg может быть закэширован браузером.

Минусы:

  • Такой способ не работает в IE11- (к счастью, есть JS-фолбэк).
  • Вы должны повторять путь к файлу при каждой вставке.

Во втором случае, о котором мы будет говорить в этом посте, SVG встроен в страницу. Обратите внимание, используется symbol. Это означает, что достаточно определить viewbox всего один раз. К тому же, определение SVG должно быть выше его использования через use, иначе в некоторых браузерах будут проблемы.

<body>
    <svg style="display: none;">
        <symbol id="svg-cart" viewbox="0 0 50 50"> 
        <path d="..." /></symbol>
    </svg>
    <!-- ... -->
    <svg><use xlink:href="#svg-cart"></use></svg>
    <!-- ... -->
</body>

Плюсы:

  • Работает в IE9+.
  • Не надо постоянно повторять путь к файлу (который, обычно, бывает довольно длинным).
<!-- Это выглядит лучше... -->
<svg><use xlink:href="#svg-cart"></use></svg>
 
<!-- ...чем это -->
<svg><use xlink:href="theme/something/assets/img/sprite.svg#cart"></use></svg>

Минусы:

  • SVG является частью страницы, поэтому не может быть закэширован браузером. Это означает, что его нельзя переиспользовать на других страницах.

А что, если SVG всеит много? 100кб или больше. Это означает, что на каждой странице вам придётся дополнительно загружать 100кб, вместо того, чтобы закэшировать их и переиспользовать. К примеру, если пользователь просмотрит на вашем сайте 11 страниц с мобильника — он потратит лишний мегабайт, что довольно плохо. Итак, можем ли мы закэшировать SVG-спрайт и в то же время избежать постоянного дублирования пути к файлу?

localStorage

Да! localStorage позволяет хранить часть страницы. Хранилище ограничено 5Мб для домена. Этого лостаточно, чтобы хранить спрайт.

Я написал небольшой кусочек JS для этого (~444 байта в минифицированном виде, без зависимостей). Вот как он работает:

При первой загрузке сайта:

  1. Считывает SVG.
  2. Вставляет его на страницу.
  3. Сохраняет его в localStorage.

При последующих загрузках:

  1. Читает информацию из localStorage.
  2. Вставляет SVG на страницу.

Если localStorage не поддерживается, переполнен или отключён, скрипт всё равно считывает SVG-файл и вставляет его на страницу.

;(function(window, document){
    'use strict';
    var file     = 'img/svg.html',
        revision = 1;
    if(!document.createElementNS || !document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect)
        return true;
 
    var isLocalStorage = 'localStorage' in window && window['localStorage'] !== null,
        request,
        data,
        insertIT = function(){
            document.body.insertAdjacentHTML('afterbegin', data);
        },
        insert = function(){
            if( document.body ) insertIT();
            else document.addEventListener('DOMContentLoaded', insertIT);
        };
 
    if(isLocalStorage && localStorage.getItem('inlineSVGrev') == revision){
        data = localStorage.getItem('inlineSVGdata');
        if(data){
            insert();
            return true;
        }
    }
 
    try{
        request = new XMLHttpRequest();
        request.open('GET', file, true);
        request.onload = function(){
            if(request.status >= 200 && request.status < 400){
                data = request.responseText;
                insert();
                if(isLocalStorage){
                    localStorage.setItem('inlineSVGdata',  data);
                    localStorage.setItem('inlineSVGrev',   revision);
                }
            }
        }
        request.send();
    }
    catch(e){}
 
}(window, document) );

Вы можете вставить этот код в любое место на странице, так как SVG-файл считыватся асинхронно. Но если вы вставите его в тег head вашего сайта, иконки загрузятся чуть быстрее. Протестируйте, чтобы выбрать оптимальный для вас вариант.

Файл и его версия

Есть две строки, которые вам нужно поправить под себя:

var file     = 'img/svg.html',
    revision = 1;

file — это путь к SVG-файлу. Это может быть любой svg-файл, но я лично не сохраняю свой спрайт как SVG, а вместо этого использую html. Мой sprite.html обычно выглядит так:

<svg style="display: none;" aria-hidden="true">
    <symbol id="svg-plane" viewbox="0 0 510 510"><path d="..." /></symbol>
    <symbol id="svg-close" viewbox="0 0 357 357"><path d="..." /></symbol>
    <symbol id="svg-fav" viewbox="0 0 510 510"><path d="..." /></symbol>
    <symbol id="svg-share" viewbox="0 0 459 459"><path d="..." /></symbol>
    <symbol id="svg-cart" viewbox="0 0 510 510"><path d="..." /></symbol>
    <symbol id="svg-tick" viewbox="0 0 510 510"><path d="..." /></symbol>
</svg>

revision — это версия спрайта, которая говорит скрипту нужно ли заново загрузить спрайт и обновить его в localStorage. Если вы обновили спрайт, то вы просто обновляете и его версию, чтобы изменения применились на сайте.

Можете обновлять его вручную или автоматически, через PHP (Perl, Python или любой другой серверный язык, который вы используете). Если вы вставили этот JS-код прямо в HTML (который находится в php-файле), то вы можете использовать PHP-функцию, которая возвращает дату модификации файла (в формате Unix timestamp):

revision = <?=filemtime( 'img/svg.html' )?>;
<html>
    <head>
        <script>var INLINE_SVG_REVISION = <?=filemtime( 'img/svg.html' )?>;</script>
    </head>
<body>
<!-- ... -->
<script src="common.js"></script>
</body>
</html>

Не поддерживается SVG?

Если браузер не поддерживает SVG, то мы можем положиться на растровые изображения и JS-фолбэк. Нам нужен атрибут data-img, чтобы указать путь к растровой картинке:

<li><svg><use xlink:href="#svg-cart" data-img="img/cart.png"></use></svg></li>

Фолбэк сконвертирует эту строку в это:

<li><img src="img/cart.png" alt="" /></li>

Затем, если вы не используете html5shiv, вы создадите элементы svg и use, которые позднее будет использовать фолбэк. Этот код лучше всего расположить в начале тега head:

if( !document.createElementNS || !document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ).createSVGRect )
{
    document.createElement( 'svg' );
    document.createElement( 'use' );
}

И наконец, сам фолбэк, который можно вставить в конце страницы (я позаимствовал некоторый код из svg4everybody):

;( function( window, document )
{
    if( document.createElementNS && document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ).createSVGRect ) return true;
    var uses = document.getElementsByTagName( 'use' ), use;
    while( ( use = uses[ 0 ] ) )
    {
        var svg = use.parentNode, img = new Image();
        img.src = use.getAttribute( 'data-img' );
        svg.parentNode.replaceChild( img, svg );
    }
}( window, document ) );

JS отключён?

Самое худшее то, что вы не сможете использовать спрайт, если в браузере отключён JS. Но, как и в случае с отсутствием поддержки SVG, вы можете положиться на растровые картинки:

<li><svg><use xlink:href="#svg-cart"></use></svg><noscript><img src="img/cart.png" alt="" /></noscript></li>

Демо

Я соединил всё вышесказанное демо, ссылка на которую находится чуть ниже. Вы можете просмотреть код и использовать этот способ в своей работе.

P.S. В этой статье я сфокусировался на кэшировании SVG и не рассказал о том, как сделать ваш спрайт доступным. Я призываю вас рассмотреть советы по улучшению доступности, прежде чем использовать этот код в реальной жизни.

Комментариев ещё нет. Оставьте первый!