Как заставить перетаскивать SVG-элементы в Chrome с помощью jQuery без смещений? (Firefox в порядке)
Я пытаюсь написать код, который позволяет перемещать 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>
Онлайн-версию можно найти по адресу:
5 ответов
Кажется, здесь есть ошибка в jQueryUI: в не-Webkit-браузерах объект ui.position использует абсолютные координаты, тогда как в других браузерах это смещение от ui.originalPosition. Я не знаю, что правильно; ошибка в том, что поведение противоречиво. Я отправил отчет об ошибке по адресу:
http://bugs.jqueryui.com/ticket/8335
Поэтому обходной путь заключается в сохранении исходных координат элемента верхнего уровня и установке координаты (ui.position - ui.originalPosition + origCoords) при настройке преобразования преобразования в браузерах Webkit. (В ответе Jlange используются атрибуты x и y прямоугольника, использованные в моем минимальном примере, который прекрасно работает там, но не работает с более сложными объектами, которые a) могут не иметь атрибутов 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/
Проблема в том, что вы не начинаете с позиции 0, 0 холста svg. Вы можете достичь желаемого результата, вычитая атрибуты x и y из объекта. Когда вы рассчитываете ручное перетаскивание, вы перемещаете весь элемент SVG, а не только прямоугольник.
смотрите здесь: http://jsfiddle.net/v477X/6/
ПРИМЕЧАНИЕ. Кажется, что разные браузеры возвращают разные объекты для элемента, который вы пытаетесь перевести. Это решение работает в браузерах Webkit. На данный момент, кажется, у вас есть 2 варианта. Измените ваш селектор, чтобы выбрать тот же элемент (в частности, строку t.getItem(0)), или определите, в каком браузере просматривает текущий пользователь, и добавьте смещение, если это webkit. Лично я бы пошел с последним, так как вы можете просто установить переменную и проверить ее.
см. здесь, чтобы узнать, как определить механизм браузера: как определить, является ли браузер Chrome с использованием jQuery?
Использовать этот:
<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>
Недавно я потратил пару дней на решение этой проблемы, и я нашел решение, которое мне помогло. Это определенно взломать, и не будет работать во всех ситуациях.
Эта ссылка имеет отличное описание того, как решить проблему в целом, и это определенно "лучшее" решение. Тем не менее, я хотел продолжать использовать превосходный 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 над целью. В других ситуациях это может быть намного сложнее. Также не слишком сложно использовать опцию "сдерживание", чтобы убедиться, что вы не перетаскиваете фон за пределы рамки... это просто требует некоторой тщательной математики и вам необходимо сбросить содержание между каждым перетаскиванием.
У меня похожая проблема. Я обнаружил, что по какой-то причине Chrome преобразует атрибуты преобразования в свойства стиля CSS. Таким образом, он создает стиль CSS - механизм CSS корректирует координаты - затем механизм SVG затем пытается применить transform:translate. Два затем мешают друг другу.
CSS, кажется, имеет приоритет. Вот кодекс, показывающий это.
http://codepen.io/dax006/pen/ONNWXv
Моя теория состоит в том, что, когда стиль CSS обновляется Chrome, он постоянно разрушается и воссоздается, и иногда между этими моментами SVG крадется в трансформации: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/