Как заставить работать перетаскивание элементов SVG в Chrome с помощью jQuery без смещения? (Файрфокс подходит)

Я пытаюсь написать код, который позволяет перетаскивать элементы SVG. Это довольно просто с jQuery, jQuery UI и jQuery SVG и отлично работает в Firefox, но в Chrome, когда я перетаскиваю элемент SVG, к его координатам, по-видимому, добавляется смещение. Я не вижу ничего, что я делаю неправильно, или обнаруживаю какую-либо известную ошибку в этой области.

Я построил небольшой пример, который иллюстрирует проблему:

<html>
  <head>
    <title>Foo</title>
    <style type="text/css">
    </style>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js"></script>
    <script type="text/javascript" src="http://keith-wood.name/js/jquery.svg.js"></script>
    <script type="text/javascript">
$(function() {
    var svgOnLoad = function () {
        var svg = $('#canvas').svg('get');
        $('#piece')
            .draggable()
            .bind('drag', function (event, ui) {
                // Update transform manually, since top/left style props don't work on SVG
                var t = event.target.transform.baseVal;
                if (t.numberOfItems == 0) {
                    t.appendItem(svg.root().createSVGTransform());
                }
                t.getItem(0).setTranslate(ui.position.left, ui.position.top);
            });
    }
    $('#canvas').svg({loadURL: "foo.svg", onLoad: svgOnLoad});
});
    </script>
  </head>
  <body>
    <div id="canvas" style="width: 100%; height: 100%;"></div>
  </body>
</html>

где foo.svg просто:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1"
    xmlns="http://www.w3.org/2000/svg"
    width="450" height="450">
    <rect id="piece" width="50" height="50" x="100" y="100" />
</svg>

Онлайн-версию можно найти по адресу:

http://jsfiddle.net/rrthomas/v477X/2/


person Reuben Thomas    schedule 16.05.2012    source источник
comment
Живой пример, +1. Это хорошо как есть, но вы можете счесть полезным использовать jsfiddle в будущем для примеров — люди могут разветвить ваш код и легче попробовать/предложить исправление.   -  person halfer    schedule 16.05.2012
comment
Спасибо за совет, я изменил онлайн-версию на jsFiddle.   -  person Reuben Thomas    schedule 17.05.2012


Ответы (5)


Проблема в том, что вы не начинаете с позиции 0, 0 холста svg. Вы можете добиться желаемого результата, вычитая атрибуты x и y из объекта. Когда вы вычисляете ручное перетаскивание, вы перемещаете весь элемент svg, а не только прямоугольник.

см. здесь: http://jsfiddle.net/v477X/6/

ПРИМЕЧАНИЕ. Похоже, что разные браузеры возвращают разные объекты для элемента, который вы пытаетесь перевести. Это решение работает в браузерах Webkit. На данный момент, кажется, у вас есть 2 варианта. Измените свой селектор, чтобы выбрать тот же элемент (в частности, строку t.getItem(0)), или определите, какой браузер просматривает пользователь, и добавьте смещение, если это webkit. Лично я бы выбрал последнее, так как таким образом вы можете просто установить переменную и проверить ее.

см. здесь, как обнаружить движок браузера: Как обнаружить если браузер Chrome использует jQuery?

person RestingRobot    schedule 16.05.2012
comment
Извините, я ожидал, что меня уведомят об ответах, но по какой-то причине не получил, поэтому я только что увидел это. Спасибо за код (хотя проверка должна быть для браузеров WebKit, а не только для Chrome; по крайней мере, Epiphany тоже не работает). К сожалению, я не очень понимаю, что здесь происходит. - person Reuben Thomas; 18.05.2012
comment
Я не переустанавливаю весь элемент svg, я только устанавливаю преобразование для цели события, то есть прямоугольного элемента. t.getItem(0) относится к преобразованию, которое я создал сам (предложение t.appendItem if срабатывает как в Firefox, так и в Chrome), и является преобразованием в прямоугольнике. Я изменил код, чтобы он ссылался на это, а не на event.target, чтобы уточнить: jsfiddle.net/rrthomas /v477X/7 Так почему же возвращаемое смещение имеет разную базу в Firefox и WebKit? - person Reuben Thomas; 19.05.2012

Кажется, здесь есть ошибка в jQueryUI: в браузерах, отличных от Webkit, объект ui.position использует абсолютные координаты, тогда как в других браузерах это смещение от ui.originalPosition. Я не знаю, что правильно; ошибка в том, что поведение непоследовательно. Я подал отчет об ошибке по адресу:

http://bugs.jqueryui.com/ticket/8335

Таким образом, обходной путь состоит в том, чтобы сохранить исходные координаты элемента верхнего уровня и установить координату (ui.position - ui.originalPosition + origCoords) при настройке преобразования перевода в браузерах Webkit. (В ответе Jlange используются атрибуты x и y прямоугольника, используемого в моем минимальном примере, который отлично работает там, но не работает с более сложными объектами, которые а) могут не иметь атрибутов x и y (например, если элемент верхнего уровня ag) и b) где возвращаемые координаты в любом случае не совпадают с координатами элемента верхнего уровня (боюсь, я не знаю почему).) Далее следует минимальный код:

var svgOnLoad = function () {
var svg = $('#canvas').svg('get');
    $('#piece')
        .draggable()
            .on({
                dragstart: function (event, ui) {
                    var tlist = this.transform.baseVal;
                        if (tlist.numberOfItems == 0) {
                            tlist.appendItem(svg.root().createSVGTransform());
                        }
                        var tm = tlist.getItem(0).matrix;
                        var pos = {left: tm.e, top: tm.f};
                        $.data(this, 'originalPosition', pos);
                    },
                    drag: function (event, ui) {
                        // Update transform manually, since top/left style props don't work on SVG
                        var tlist = this.transform.baseVal;
                        var CTM = this.getCTM();
                        var p = svg.root().createSVGPoint();
                        p.x = ui.position.left + CTM.e;
                        p.y = ui.position.top + CTM.f;
                        if ($.browser.webkit) { // Webkit gets SVG-relative coords, not offset from element
                            var origPos = $.data(this, 'originalPosition');
                            p.x -= ui.originalPosition.left - origPos.left;
                            p.y -= ui.originalPosition.top - origPos.top;
                        }
                        p = p.matrixTransform(CTM.inverse());
                        tlist.getItem(0).setTranslate(p.x, p.y);
                    }});
};
$('#canvas').svg({onLoad: svgOnLoad});

Онлайн-версия по адресу: http://jsfiddle.net/rrthomas/v477X/

person Reuben Thomas    schedule 19.05.2012
comment
Этот ответ неверен: ui.originalPosition означает то, что он говорит, поэтому исправление работает только при перетаскивании из исходного положения. Я работаю, чтобы найти правильное решение; когда я найду его, я заменю этот ответ. - person Reuben Thomas; 20.05.2012
comment
Я действительно не думаю, что это ошибка в jQueryUI. Различные двигатели имеют небольшие различия в методологии позиционирования. Решением вашей проблемы было бы найти, где ваши элементы находятся относительно холста SVG, и сместить их в браузерах Webkit. Для группового элемента это так же просто, как перебрать его дочерние элементы для верхнего левого элемента и определить, является ли он объектом верхнего уровня для начала. Помните, что события всплывают, поэтому, даже если селектор перетаскивания срабатывает, исходное целевое событие могло быть на дочернем элементе. - person RestingRobot; 21.05.2012
comment
Я обновил решение, как и обещал. Есть баг: jQueryUI должен давать одинаковые результаты в разных браузерах, а здесь они систематически разные. Предположительно, один из браузеров нарушает спецификацию SVG, но jQueryUI должен скрывать такие различия. - person Reuben Thomas; 22.05.2012
comment
Обновлено, чтобы справиться с SVG с CTM без идентификации на верхнем уровне. - person Reuben Thomas; 13.06.2012

Недавно я провел пару дней, изучая эту проблему, и нашел решение, которое сработало для меня довольно хорошо. Это определенно взлом, и он не будет работать во всех ситуациях.

Эта ссылка имеет отличное описание того, как решить проблему в целом, и это определенно «лучшее» решение. Однако я хотел продолжать использовать превосходный Draggable API JQuery.

Вы можете увидеть быстрый макет того, что я сделал на скрипке здесь. Основная идея состоит в том, чтобы использовать прокси-div, который вы продолжаете наводить точно на элемент svg, который хотите перетащить. Затем вы меняете координаты x и y элемента svg при перетаскивании прокси-элемента div. Что-то вроде этого:

$('#proxy').on('drag', function(e)
    {
        t = $('#background');
        prox = $('#proxy');
        t.attr('x', t.attr('x')*1
                   + prox.css('left').slice(0,-2)*1
                   - prox.data('position').left)
            .attr('y', t.attr('y')*1
                      + prox.css('top').slice(0,-2)*1
                      - prox.data('position').top);
        prox.data('position',{top : prox.css('top').slice(0,-2)*1,
                              left: prox.css('left').slice(0,-2)*1}
                  );
    });

В моем случае элемент SVG, который я хотел перетащить, всегда заполнял определенный квадрат на экране, поэтому было очень легко разместить прокси-элемент div над целью. В других ситуациях это может быть намного сложнее. Также не слишком сложно использовать опцию «сдерживание», чтобы убедиться, что вы не перетаскиваете фон за пределы кадра… это просто требует осторожного расчета, и вы должны сбрасывать сдерживание между каждым перетаскиванием.

person galdre    schedule 21.06.2013

использовать это:

<html>
<head>
    <title>Foo</title>
    <style type="text/css">
    </style>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script type="text/javascript" src="http://code.jquery.com/ui/1.8.18/jquery-ui.min.js"></script>
    <script type="text/javascript" src="http://keith-wood.name/js/jquery.svg.js"></script>
    <script type="text/javascript">
        $(function ()
            {
            var svg = $('#canvas').svg().svg('get');
            $('#piece')
                    .draggable()
                    .on({
                        dragstart: function (event, ui)
                            {
                            var t = this.transform.baseVal;
                            if (t.numberOfItems == 0)
                                { t.appendItem(svg.root().createSVGTransform()); }
                            var tm = t.getItem(0).matrix;
                            var dx = tm.e - ui.position.left;
                            var dy = tm.f - ui.position.top;
                            $.data(this, 'dragTo', function dragTo(newPos)
                                {
                                tm.e = newPos.left + dx;
                                tm.f = newPos.top + dy;
                                });
                            },
                        drag: function (event, ui)
                            { $.data(this, 'dragTo')(ui.position); }});
            });
    </script>
</head>
<body>
<div id="canvas" style="width: 100%; height: 100%;">
    <svg version="1.1"
         xmlns="http://www.w3.org/2000/svg"
         width="450" height="450">
        <rect id="piece" width="50" height="50" x="0" y="0" style="position:absolute"/>
    </svg>
</div>
</body>
</html>
person user3744156    schedule 19.02.2014

У меня похожая проблема. Я обнаружил, что по какой-то причине Chrome преобразует атрибуты преобразования в свойства стиля CSS. Итак, он создает стиль CSS — механизм CSS корректирует координаты — затем механизм SVG затем пытается применить преобразование: перевод. Затем они мешают друг другу.

CSS, кажется, имеет приоритет. Вот codepen, показывающий это.
http://codepen.io/dax006/pen/ONNWXv

Моя теория заключается в том, что когда стиль CSS обновляется Chrome, он постоянно уничтожается и создается заново, и иногда между этими моментами SVG прокрадывается в tranform:translate(). Затем Chrome получает новое значение преобразования и применяет его к CSS, и теперь ваше преобразование не будет работать правильно, потому что CSS мешает.

Теоретически transform:translate должен быть уничтожен в тот же момент, когда создается CSS, но это не так. Просто осмотрите элемент, и вы увидите как наличие стиля CSS, изменяющего координаты, так и атрибут преобразования.

<rect y="100" x="100" height="50" width="50" id="piece" style="position: relative; left: -15px; top: -13px;" transform="translate(-15, -13)"/>

Обязательно ознакомьтесь с этой статьей и ссылками внизу. https://css-tricks.com/svg-animation-on-css-transforms/

person john ktejik    schedule 09.03.2016