问题
I am trying to write some code that allows SVG elements to be dragged around. This is pretty easy with jQuery, jQuery UI and jQuery SVG, and works fine in Firefox, but in Chrome when I drag the SVG element, an offset is apparently added to its coordinates. I can't see anything I'm doing wrong, or discover any known bug in this area.
I've constructed a small example that illustrates the problem:
<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>
where foo.svg is just:
<?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>
An online version can be found at:
http://jsfiddle.net/rrthomas/v477X/2/
回答1:
The problem is that you are not starting at position 0, 0 of the svg canvas. You can achieve the desired result by subtracting the x and y attributes from the object. When you calculate your manual drag you are re-positioning the entire svg element, not just the rectangle.
see here: http://jsfiddle.net/v477X/6/
NOTE: The different browsers seem to be returning different objects for the element you are trying to translate. This solution works in Webkit browsers. At this point, it seems you have 2 options. Modify your selector to choose the same element, (specifically the line t.getItem(0)), or determine which browser the user is current viewing on, and add the offset if it is webkit. Personally, I would go with the latter, as that way you can just set a variable and check it.
see here for how to detect browser engine: How to detect if a browser is Chrome using jQuery?
回答2:
There seems to be a bug in jQueryUI here: in non-Webkit browsers, the ui.position object uses absolute coordinates, whereas in other browsers it's an offset from ui.originalPosition. I don't know which is correct; the bug is that the behavior is inconsistent. I've filed a bug report at:
http://bugs.jqueryui.com/ticket/8335
The workaround is therefore to store the original coordinates of the top-level element, and set the coordinate to (ui.position - ui.originalPosition + origCoords) when setting the translate transform in Webkit browsers. (Jlange's answer uses the x and y attributes of the rect used in my minimal example, which works fine there, but does not work with more complex objects which a) may not have x and y attributes (e.g. if the top-level element is a g element) and b) where the coordinates returned seem not to be those of the top-level element anyway (I'm afraid I don't know why).) Minimal code follows:
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});
Live version at: http://jsfiddle.net/rrthomas/v477X/
回答3:
I recently spent a couple days hammering at this problem, and I came up with a solution that worked fairly well for me. It's definitely a hack, and won't work in all situations.
This link has an excellent description of how to solve the problem in general, and that is definitely the "better" solution. However, I wanted to keep using JQuery's excellent Draggable API.
You can see a quick mock-up of what I did in a fiddle here. The key idea is to use a proxy div which you keep hovering exactly over the svg element you want to drag. Then you change the svg element's x and y coordinates as you drag the proxy div. Something like this:
$('#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}
);
});
In my case the SVG element I wanted to drag would always fill a certain square on the screen, so it was very easy to position the proxy div over the target. In other situations it could be much more difficult. It's also not too hard to use the 'containment' option to make sure you don't drag the background outside the frame...it just takes some careful math and you have to reset the containment in between each drag.
回答4:
use this:
<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>
回答5:
I have a similar issue. I have found that for some reason, Chrome converts transform attributes into CSS style properties. So It creates a CSS style - the CSS engine adjusts the coordinates - then the SVG engine then tries to apply the transform:translate. The two then interfere with each other.
The CSS seems to have priority. Here is a codepen showing this.
http://codepen.io/dax006/pen/ONNWXv
My theory is that when the CSS style is being updated by Chrome, it is constantly being destroyed and recreated, and sometimes between those moments the SVG sneaks in a tranform:translate(). Chrome then grabs the new transform value and applies it to the CSS, and now your transform won't work correctly because the CSS is in the way.
In theory the transform:translate should be destroyed at the exact same moment the CSS is created, but it is not. Simply inspect the element and you will see both the existence of a CSS style changing the coordinates, and a transform attribute.
<rect y="100" x="100" height="50" width="50" id="piece" style="position: relative; left: -15px; top: -13px;" transform="translate(-15, -13)"/>
Be sure to check out this article and the links at the bottom. https://css-tricks.com/svg-animation-on-css-transforms/
来源:https://stackoverflow.com/questions/10623127/how-do-i-make-dragging-svg-elements-in-chrome-with-jquery-work-without-getting-o