• Техногрет
  • Корректное масштабирование SVG-элементов

    HTML и CSSXSLTJavaScriptИзображенияСофтEtc
    Влад Яковлев Анатоль Латотин

    11 декабря 2009


    Задача.

    Правильно масштабировать SVG-элементы в браузерах на движке WebKit при масштабировании страницы.

    Ошибки бывают и в браузерах на движке WebKit: при масштабировании страницы Safari и Chrome неправильно изменяет размеры SVG-элементов (Firefox и Opera этой проблемы не имеют). Увидеть это можно на примере (в режиме масштабирования всей страницы, а не только текста).

    Код примера:

    01 
    02 
    03 
    04 
    05 
    06 
    07 
    08 
    09 
    10 
    11 
    12 
    13 
    14 
    15 
    16 
    17 
    18 
    19 
    20 
    21 
    22 
    23 
    24 
    25 
    26 
    27 
    28 
    29 
    30 
    31 
    32 
    33 
    34 
    35 
    $(function() {
    
        var
            coords = [0, 0, 300, 300],
            svgEl,
            width = 300,
            height = 300,
            svgNs = 'http://www.w3.org/2000/svg';
    
        // Ему тут делать нечего…
        if ($.browser.msie) return;
    
        createSVG();
    
        function createSVG() {
            var rootEl = $('.svg_container1');
    
            svgEl = document.createElementNS(svgNs, 'svg');
            svgEl.setAttribute('version', '1.1');
            svgEl.setAttribute('class', 'shape');
            svgEl.setAttribute('viewBox', '0 0 300 300');
            svgEl.setAttribute('width', width);
            svgEl.setAttribute('height', height);
            rootEl.append(svgEl);
    
            var lineEl = document.createElementNS(svgNs, 'line');
            lineEl.setAttribute('id', 'lineEl');
            lineEl.setAttribute('x1', coords[0]);
            lineEl.setAttribute('y1', coords[1]);
            lineEl.setAttribute('x2', coords[2]);
            lineEl.setAttribute('y2', coords[3]);
            lineEl.setAttribute('stroke', '#ff0000');
            svgEl.appendChild(lineEl);
        }
    });

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

    При изменении значения коэффициента всем элементам с тегом svg нужно менять атрибуты:

    — ширину и высоту умножать на коэффициент;
    — размеры viewBox делить на коэффициент.

    Посмотрим, что получилось:

    Код примера:

    01 
    02 
    03 
    04 
    05 
    06 
    07 
    08 
    09 
    10 
    11 
    12 
    13 
    14 
    15 
    16 
    17 
    18 
    19 
    20 
    21 
    22 
    23 
    24 
    25 
    26 
    27 
    28 
    29 
    30 
    31 
    32 
    33 
    34 
    35 
    36 
    37 
    38 
    39 
    40 
    41 
    $(function() {
    
        var
            coords = [0, 0, 300, 300],
            svgEl,
            width = 300,
            height = 300,
            svgNs = 'http://www.w3.org/2000/svg';
    
        // Ему тут делать нечего…
        if ($.browser.msie) return;
    
        createSVG();
    
        webkitSvgFix.bind(function(factor) {
            svgEl.setAttribute('width', width * factor);
            svgEl.setAttribute('height', height * factor);
            svgEl.setAttribute('viewBox', [coords[0], coords[1], coords[2] / factor, coords[3] / factor].join(' '));
        });
    
        function createSVG() {
            var rootEl = $('.svg_container2');
    
            svgEl = document.createElementNS(svgNs, 'svg');
            svgEl.setAttribute('version', '1.1');
            svgEl.setAttribute('class', 'shape');
            svgEl.setAttribute('viewBox', '0 0 300 300');
            svgEl.setAttribute('width', width);
            svgEl.setAttribute('height', height);
            rootEl.append(svgEl);
    
            var lineEl = document.createElementNS(svgNs, 'line');
            lineEl.setAttribute('id', 'lineEl');
            lineEl.setAttribute('x1', coords[0]);
            lineEl.setAttribute('y1', coords[1]);
            lineEl.setAttribute('x2', coords[2]);
            lineEl.setAttribute('y2', coords[3]);
            lineEl.setAttribute('stroke', '#ff0000');
            svgEl.appendChild(lineEl);
        }
    });

    Скрипт для исправления проблемы:

    01 
    02 
    03 
    04 
    05 
    06 
    07 
    08 
    09 
    10 
    11 
    12 
    13 
    14 
    15 
    16 
    17 
    18 
    19 
    20 
    21 
    22 
    23 
    24 
    25 
    26 
    27 
    28 
    29 
    30 
    31 
    32 
    33 
    34 
    35 
    36 
    37 
    38 
    39 
    40 
    41 
    42 
    43 
    44 
    45 
    46 
    47 
    48 
    49 
    50 
    51 
    52 
    53 
    54 
    55 
    56 
    57 
    58 
    59 
    60 
    61 
    62 
    63 
    64 
    65 
    66 
    67 
    68 
    69 
    70 
    71 
    72 
    73 
    74 
    75 
    76 
    77 
    78 
    79 
    80 
    81 
    82 
    83 
    84 
    85 
    86 
    87 
    88 
    89 
    90 
    91 
    var webkitSvgFix = (function() {
    
        var
            /**
             * Коэффициент изменения размеров.
             */
            factor = 1,
            /**
             * Элемент-родитель для проверки.
             * @type {jQuery}
             */
            rootEl,
            /**
             * SVG-элемент для проверки.
             * @type {Element}
             */
            svgEl,
            /**
             * Привязанные к изменению коэффициента обработчики.
             */
            funcs = [],
            svgNs = 'http://www.w3.org/2000/svg',
            /**
             * Интервал мониторинга, в мс.
             */
            timeout = 100;
    
        /**
         * Следит за размерами контрольных блоков.
         * При изменении коэффициента запускаются подписанные функции.
         */
        function check() {
    
            // Вычисляем коэффициент.
            var newFactor = rootEl.width() / svgEl.clientWidth;
    
            // Если коэффициент изменился, запускаем обработчики.
            if (newFactor != factor) {
                factor = newFactor;
    
                $.each(funcs, function() {
                    this(factor);
                });
            }
    
            // Мониторим дальше.
            setTimeout(check, timeout);
        }
    
        /**
         * Инициализирует элементы для проверки масштабирования страницы.
         */
        function init() {
            var
                width = 1000,
                height = 1;
    
            // Создаем дивчик такой, чтобы его никто не увидел.
            rootEl = $('<div></div>').css({
                height: height,
                left: -10000,
                margin: 0,
                padding: 0,
                position: 'absolute',
                top: -10000,
                visibility: 'hidden',
                width: width
            }).appendTo('body');
    
            // И кладем в него SVG-элемент.
            svgEl = document.createElementNS(svgNs, 'svg');
            svgEl.setAttribute('version', '1.1');
            svgEl.setAttribute('width', width);
            svgEl.setAttribute('height', height);
            svgEl.style.position = 'absolute';
            rootEl.append(svgEl);
    
            // Начинаем мониторинг.
            check();
        }
    
        return {
            bind: function(func) {
                if ($.browser.safari) {
                    // Инициализация только по первому привязанному обработчику.
                    rootEl || init();
                    funcs.push(func);
                }
            }
        };
    })();