I'm trying to re-size, rotate and drag SVG elements using transformations. If I rotate an object, then re-size and rotate again, on this last rotation the element gets displaced.
Code used for resizing
var matrix = SVG.createSVGMatrix()
.translate(x,y)
.scaleNonUniform(sx,sy)
.translate(-x,-y);
var newMatrix = SVG.createSVGTransformFromMatrix(ctm.multiply(matrix));
element.transform.baseVal.initialize(newMatrix);
Code used for rotation
var matrix = SVG.createSVGMatrix()
.translate(cx,cy)
.rotate(angle)
.translate(-cx,-cy);
var newMatrix = SVG.createSVGTransformFromMatrix(ctm.multiply(matrix));
element.transform.baseVal.initialize(newMatrix);
Is there any idea to resolve this issue?
I use a combination of getBBox for the element to identify its center no matter which transforms are applied. Also, to track the element's transformed center, I enclose it in an svg wrapper, and get its bounding box. I use matrix transforms and consolidate() on each transform.
The following is an example on how this is used to move the element to a desired point:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Native Center Transforms with Wrapper</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body style='padding:10px;font-family:arial'>
<center>
<h4>Native Center Transforms with Wrapper</h4>
<div style='width:90%;background-color:gainsboro;text-align:justify;padding:10px;border-radius:6px;'>
The <b>Native Center</b> is the center of an element's bounding box. This remains constant no matter which transforms has been applied to an element.
Therefore this center point there can be used as the <b>reference point</b> for stable rotation, scale, and skew transforms. By use of an svg <b>wrapper</b>, the element can be translated so its
center point moves to a target point.
</div>
<table><tr>
<td>
<div style="padding:10px;width:400px;text-align:justify">
<b>Scenerio:</b><br />
The rect element has been translated from its original location (dashed rect).
Further scale, rotate, and skew transforms can use the rect's
original bounding box center (black circle): <b>NativeCenterX, NativeCenterY</b>.<br /><br />
Also, folowing scale. rotate, skew..<br />
The center of the element can be <b>moved to</b> a specific target point (maroon circle) by using the center point of it's svg <b>wrapper</b>
<br /><br />
<i>Click the buttons one or more times to transform the rect.</i>
</div>
</td>
<td>
<div id="svgDiv" style='background-color:lightgreen;width:400px;height:400px;'>
<svg id="mySVG" width="400" height="400">
<rect id="myRect" fill=red stroke="none" x="10" y="60" width=150 height=80 transform="translate(110,50)" />
<rect id="bbRect" fill=none stroke="black" stroke-width="1" stroke-dasharray="10 10" />
<circle id=nativeCenter r=5 fill="black" stroke="none" />
<circle id=moveToTarget r=5 fill="maroon" cx=240 cy=330 stroke="none" />
</svg>
</div>
<center>
<button onClick=rotateRect()>rotate</button>
<button onClick=scaleRect()>scale</button>
<button onClick=skewXRect()>skewX</button>
<button onClick=skewYRect()>skewY</button>
then...<button onClick=moveToRect()>Move To</button>
</center>
</td>
</tr></table>
<br />SVG Source:<br />
<textarea id=svgSourceValue style='font-size:110%;font-family:lucida console;width:90%;height:200px'></textarea>
<br />Javascript:<br />
<textarea id=jsValue style='border-radius:26px;font-size:110%;font-weight:bold;color:midnightblue;padding:16px;background-color:beige;border-width:0px;font-size:100%;font-family:lucida console;width:90%;height:400px'></textarea>
</center>
<div id='browserDiv' style='padding:5px;position:absolute;top:5px;left:5px;background-color:gainsboro;'>OK in:IE11/CH32/FF23<br /></div>
<script id=myScript>
var NativeCenterX
var NativeCenterY
var TransformRequestObj
var TransformList
//---translate target---
var TargetX
var TargetY
//---onload---
function initBBox()
{
var bb=myRect.getBBox()
var bbx=bb.x
var bby=bb.y
var bbw=bb.width
var bbh=bb.height
NativeCenterX=bbx+.5*bbw
NativeCenterY=bby+.5*bbh
bbRect.x.baseVal.value=bbx
bbRect.y.baseVal.value=bby
bbRect.width.baseVal.value=bbw
bbRect.height.baseVal.value=bbh
nativeCenter.cx.baseVal.value=NativeCenterX
nativeCenter.cy.baseVal.value=NativeCenterY
TargetX=moveToTarget.cx.baseVal.value
TargetY=moveToTarget.cy.baseVal.value
//--- transform myRect Objs---
TransformRequestObj=mySVG.createSVGTransform()
var animTransformList=myRect.transform
TransformList=animTransformList.baseVal
}
//---button---
function rotateRect() //---15 degrees
{
TransformRequestObj.setRotate(15,NativeCenterX,NativeCenterY)
TransformList.appendItem(TransformRequestObj)
TransformList.consolidate()
svgSourceValue.value=svgDiv.innerHTML
}
function scaleRect() //---.8---
{
TransformRequestObj.setTranslate(NativeCenterX,NativeCenterY )
TransformList.appendItem(TransformRequestObj)
TransformList.consolidate()
TransformRequestObj.setScale(.8,.8)
TransformList.appendItem(TransformRequestObj)
TransformList.consolidate()
TransformRequestObj.setTranslate(-NativeCenterX,-NativeCenterY )
TransformList.appendItem(TransformRequestObj)
TransformList.consolidate()
svgSourceValue.value=svgDiv.innerHTML
}
function skewXRect()
{
TransformRequestObj.setTranslate(NativeCenterX,NativeCenterY )
TransformList.appendItem(TransformRequestObj)
TransformList.consolidate()
TransformRequestObj.setSkewX(10)
TransformList.appendItem(TransformRequestObj)
TransformList.consolidate()
TransformRequestObj.setTranslate(-NativeCenterX,-NativeCenterY )
TransformList.appendItem(TransformRequestObj)
TransformList.consolidate()
svgSourceValue.value=svgDiv.innerHTML
}
function skewYRect()
{
TransformRequestObj.setTranslate(NativeCenterX,NativeCenterY )
TransformList.appendItem(TransformRequestObj)
TransformList.consolidate()
TransformRequestObj.setSkewY(10)
TransformList.appendItem(TransformRequestObj)
TransformList.consolidate()
TransformRequestObj.setTranslate(-NativeCenterX,-NativeCenterY )
TransformList.appendItem(TransformRequestObj)
TransformList.consolidate()
svgSourceValue.value=svgDiv.innerHTML
}
var NS="http://www.w3.org/2000/svg"
//---move transformed rect to a specific point(TargetX,TargetY)--
function moveToRect()
{
//---build temp wrapper---
var wrapper=document.createElementNS(NS,"svg")
mySVG.appendChild(wrapper)
//---temp place rect in wrapper---
wrapper.appendChild(myRect)
var bb=wrapper.getBBox()
//---return myRect to previous location---
mySVG.insertBefore(myRect,bbRect)
//---remove temp wrapper---
mySVG.removeChild(wrapper)
var bbx=bb.x
var bby=bb.y
var bbw=bb.width
var bbh=bb.height
//---center of svg srapper---
var Wcx=bbx+.5*bbw
var Wcy=bby+.5*bbh
//---bind target point to current matrix---
var pnt = myRect.nearestViewportElement.createSVGPoint();
pnt.x = TargetX;
pnt.y = TargetY;
var sCTM = myRect.getCTM();
PNT1 = pnt.matrixTransform(sCTM.inverse());
//---bind wrapper center to current matrix--
var pnt = myRect.nearestViewportElement.createSVGPoint();
pnt.x = Wcx;
pnt.y = Wcy ;
var sCTM = myRect.getCTM();
PNT2 = pnt.matrixTransform(sCTM.inverse());
//---translate rect's center to target---
var transX=PNT1.x-PNT2.x
var transY=PNT1.y-PNT2.y
TransformRequestObj.setTranslate(transX,transY)
TransformList.appendItem(TransformRequestObj)
TransformList.consolidate()
svgSourceValue.value=svgDiv.innerHTML
}
</script>
<script>
document.addEventListener("onload",init(),false)
function init()
{
initBBox()
svgSourceValue.value=svgDiv.innerHTML
jsValue.value=myScript.text
}
</script>
</body>
</html>
Related
I have a div containing an SVG image of 300x300 px and a viewbox of 1000x1000.
The image describes a blue rectangle on top of a red one.
When I move the mouse a circle is following the mouse position inside the image:
Everything is perfect except that when I apply a transformation changing perspective and rotation, the mouse pointer and circle center are not anymore matching:
Code is here:
$(function() {
$('#image').mousemove(function(event) {
var svg = document.querySelector('svg');
var pt = svg.createSVGPoint();
pt.x = event.clientX;
pt.y = event.clientY;
pt = pt.matrixTransform(svg.getScreenCTM().inverse());
overlay = document.getElementById('overlay');
$('#overlay').html(
"<circle cx='" + pt.x + "' cy='" + pt.y + "' r='50' stroke='#8f00ff' fill='transparent' stroke-width='10' /></svg>"
);
refresh = $("#overlay").html();
$("#overlay").html( refresh )
});
});
function Transform() {
$('#image').css({
transformOrigin: '500px 500px',
transform: 'perspective(100px) rotateX(5deg)'
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='image' tabindex='0' >
<svg id='svgmap' width='300' height='300' xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 1000 1000'>
<rect x='0' y='0' width='1000' height='1000' fill='red' />
<rect x='250' y='250' width='500' height='500' stroke='yellow' fill='blue' stroke-width='10' />
<g id='overlay'></g>
</svg>
</div>
<button onclick='Transform()'>Transform</button>
My goal is to preserver matching between the purple circle center and the mouse pointer, even when a transformation is applied to the object.
Is there a way to do it?
In your code #image is a div. In order to make it work you need to apply the transformation to the svg element (#svgmap) and the transformation must be an svg transformation.
$(function() {
$('#svgmap').mousemove(function(event) {
var svg = document.querySelector('svg');
var pt = svg.createSVGPoint();
pt.x = event.clientX;
pt.y = event.clientY;
pt = pt.matrixTransform(svg.getScreenCTM().inverse());
overlay = document.getElementById('overlay');
$('#overlay').html(
"<circle cx='" + pt.x + "' cy='" + pt.y + "' r='50' stroke='#8f00ff' fill='transparent' stroke-width='10' /></svg>"
);
refresh = $("#layer_wafer").html();
$("#layer_wafer").html( refresh )
});
});
function Transform() {
svgmap.setAttributeNS(null,"transform", "skewX(-20) translate(100)");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='image' tabindex='0' >
<svg id='svgmap' width='300' height='300' xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 1000 1000' transform="">
<rect x='0' y='0' width='1000' height='1000' fill='red' />
<rect x='250' y='250' width='500' height='500' stroke='yellow' fill='blue' stroke-width='10' />
<g id='overlay'></g>
</svg>
</div>
<button onclick='Transform()'>Transform</button>
I understand that you are needing a 3D css transformation but this (at least for now) doesn't work.
This is an article where you can read more about 3d transforms in svg: https://oreillymedia.github.io/Using_SVG/extras/ch11-3d.html In the article you can read: All the 3D transformation functions described in this section should be considered “future”
I've solved the issue in the ugliest possible way. Simply hiding the mouse cursor over the div.
Ugly. But effective.
$(function() {
$('#image').mousemove(function(event) {
var svg = document.querySelector('svg');
var pt = svg.createSVGPoint();
pt.x = event.clientX;
pt.y = event.clientY;
pt = pt.matrixTransform(svg.getScreenCTM().inverse());
overlay = document.getElementById('overlay');
$('#overlay').html(
"<circle cx='" + pt.x + "' cy='" + pt.y + "' r='50' stroke='#8f00ff' fill='transparent' stroke-width='10' /></svg>"
);
refresh = $("#overlay").html();
$("#overlay").html( refresh )
});
});
function Transform() {
$('#image').css({
transformOrigin: '500px 500px',
transform: 'perspective(100px) rotateX(5deg)'
});
}
div#image {
cursor: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='image' tabindex='0' >
<svg id='svgmap' width='300' height='300' xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 1000 1000'>
<rect x='0' y='0' width='1000' height='1000' fill='red' />
<rect x='250' y='250' width='500' height='500' stroke='yellow' fill='blue' stroke-width='10' />
<g id='overlay'></g>
</svg>
</div>
<button onclick='Transform()'>Transform</button>
There's a little bit of optimization that I have to do to reduce the mouse cursor position gap between the DIV over and any other page element, but it's acceptable to me.
I have a SVG-document that contains a circle. I would like to place an absolutely positioned html-element on top of this circle. The circle contains a cx and cy and there are several transforms on parent elements. How can I translate the position inside the SVG to the coordinate space of the parent html-element (normal pixels)?
The SVG is generated by a program, so I will have little control over it, meaning that I will need a general solution that can handle any number of transforms on parent element.
I'm not using d3 or any similar library, so I'm looking for a way to solve this using plain JavaScript. This is a simplified example of my problem:
<head>
<style>
#box {
background-color: greenyellow;
position: absolute;
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<div id="root">
<svg width="1274.2554" height="692.35712" id="svg">
<g id="viewport" transform="matrix(0.655937472561484,0,0,0.655937472561484,162.2578890186666,57.23719435833714)">
<g id="layer1" transform="translate(-5.49584,-171.51931)">
<g id="elem" transform="translate(34.862286,232.62127)">
<circle id="center" cx="19.952429" cy="19.90885" style="fill:#ffcc33" r="20"></circle>
</g>
</g>
</g>
</svg>
<div id="box" ></div>
</div>
<script>
function positionBoxAtCenter() {
var box = document.getElementById('box');
var center = document.getElementById(('center'));
// TODO: get x, y from 'center' so that 'box' can be placed on top of center
var x = 0, y = 0;
box.setAttribute('style', 'top: ' + y + 'px; left: ' + x + 'px');
}
</script>
</body>
JSFiddle: https://jsfiddle.net/p9pf56sz/
Using getBoundingClientRect():
var pos = center.getBoundingClientRect();
You can get the center of the circle:
var x = pos.left + pos.width/2, y = pos.top + pos.height/2;
Here is the demo:
function positionBoxAtCenter() {
var box = document.getElementById('box');
var center = document.getElementById(('center'));
var pos = center.getBoundingClientRect();
var x = pos.left + pos.width/2, y = pos.top + pos.height/2;
box.setAttribute('style', 'top: ' + y + 'px; left: ' + x + 'px');
}
positionBoxAtCenter();
#box {
background-color: greenyellow;
position: absolute;
width: 100px;
height: 100px;
}
<body>
<div id="root">
<svg width="1274.2554" height="692.35712" id="svg">
<g id="viewport" transform="matrix(0.655937472561484,0,0,0.655937472561484,162.2578890186666,57.23719435833714)">
<g id="layer1" transform="translate(-5.49584,-171.51931)">
<g id="elem" transform="translate(34.862286,232.62127)">
<circle id="center" cx="19.952429" cy="19.90885" style="fill:#ffcc33" r="20"></circle>
</g>
</g>
</g>
</svg>
<div id="box" ></div>
</div>
</body>
However, as you can see in the demo, you are moving the top/left corner of the rectangle (its origin) to the center of the circle. So, you'll have to calculate the center of the rectangle as well (and subtract its difference to the origin).
I need to do my custom triangle progress bar using SVG and Angular JS. But it seems to be hard to control the green color bar. Can anyone help me?
Here my codes. You may adjust the value in the textbox.
var app = angular.module('ProgressBar', []);
app.controller('ProgressBarCtrl', function($scope) {
$scope.A=365;
$scope.B=275;
$scope.C=33;
$scope.D=276;
$scope.E=366;
$scope.F=157;
});
.bar-content{fill:#D1D3D4;}
.bar-frame{fill:#69BD45;}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="ProgressBar" ng-controller="ProgressBarCtrl">
<input type="number" ng-model="A" />
<input type="number" ng-model="B" />
<input type="number" ng-model="C" />
<input type="number" ng-model="D" />
<input type="number" ng-model="E" />
<input type="number" ng-model="F" />
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 595.3 841.9" style="enable-background:new 0 0 595.3 841.9;" xml:space="preserve">
<polygon id="XMLID_1_" class="bar-content" points="535,275 36,275 535,97 "/>
<polygon id="XMLID_2_" class="bar-frame" points="{{A}},{{B}} {{C}},{{D}} {{E}},{{F}} "/>
</svg>
</div>
Haven't touched Angular before, so I can't help you there. I can however present an approach suitable for use with either SVG or Canvas. (The canvas implementation is faster, I imagine since it's GPU accelerated)
Since your initial image has an aspect ratio of 2.85 : 1, I chose to use a height of 100px and a width of 285px - I've used the same dimensions for each.
function byId(id){return document.getElementById(id)}
function allByClass(clss){return document.getElementsByClassName(clss)}
function allByTag(tag,parent){return (parent = undefined ? document : parent).getElementsByTagName(tag)}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
byId('slider').style.width = byId('volume').width + 'px';
setVolume(50);
byId('slider').addEventListener('input', onSliderChanged, false);
byId('slider').addEventListener('input', onSlider2Changed, false);
}
function onSliderChanged(evt)
{
var value = this.value;
setVolume(value);
}
function onSlider2Changed(evt)
{
var value = this.value;
setVolumeSVG(value);
}
function setVolumeSVG(percent)
{
var svg = byId('mSvg');
var barWidth = (percent/100) * svg.width.baseVal.value;
var barHeight = (percent/100) * svg.height.baseVal.value;
var msg = "0,"+svg.height.baseVal.value + " "
+ barWidth + "," + (svg.height.baseVal.value-barHeight) + " "
+ barWidth + "," + svg.height.baseVal.value;
allByClass('barSlider')[0].setAttribute('points', msg);
}
//
//
// (2)
//
//
//
//
// (1) (3)
function setVolume(percent)
{
var can = byId('volume');
var ctx = can.getContext('2d');
ctx.fillStyle = "rgba(0,0,0,0)";
ctx.fillRect(0,0,can.width,can.height);
ctx.fillStyle = "#d1d3d4";
ctx.moveTo(0,can.height);
ctx.beginPath();
ctx.lineTo(can.width, 0);
ctx.lineTo(can.width,can.height);
ctx.lineTo(0,can.height);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "#69bd45";
ctx.moveTo(0,can.height);
ctx.lineTo( (percent/100)*can.width, can.height - ( (percent/100)*can.height) );
ctx.lineTo( (percent/100)*can.width, can.height );
ctx.lineTo(0,can.height);
ctx.fill()
}
<canvas width=285 height=100 id='volume'></canvas><br>
<input type='range' min='0' max='100' step='1' value=50 id='slider'/>
<hr/>
<svg id='mSvg' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 285 100" width=285 height=100>
<g>
<polygon class="barFrame" points="0,100 285,100 285,0"></polygon>
<polygon class='barSlider' points="0,100 143,100 143,50"></polygon>
</g>
<style>
.barFrame{ fill: #d1d3d4; }
.barSlider{ fill: #69bd45; }
</style>
</svg>
jsFiddle: http://jsfiddle.net/az6Ug/
Using Snap.svg library, I want drag on the SVG and get the mouse position. I need the mouse position on the internal matrix, not the browser mouse position.
I have this working with one problem, which is if there is any scroll on the window that the SVG belongs to, the calculated mouse position is offset by the amount of scroll on the scrollbar.
For example, with no scroll then it works fine. Or with 10px of vertical scroll on the scrollbar, the mouse position is calculated as: true position + 10px.
It's the same deal if there's any horizontal scroll: offset by the amount of scroll.
In the jsFiddle, I use a rectangle to show the calculared mouse position while dragging.
As you can see if there's no scroll, then the rectangle stays with the mouse cursor (upper left corner). But with some scroll, the rectangle is offset from the mouse cursor.
Although I'm using the Snap.svg library, I only use this to get the drag event, the mouse calculation is pure Javascript. A possible solution is to subtract the X and Y by the amount of scroll but I think there will be a better way using the SVG's tranform functions.
var S;
var pt;
var svg
var box;
window.onload = function(){
svg = $('#mysvg')[0];
S = Snap(svg);
pt = pt = svg.createSVGPoint(); // create the point
// add the rectangle
box = S.rect(10, 10, 50, 50);
box.attr({ fill : 'red', stroke : 'none' });
S.drag(
function(dx, dy, posX, posY, e){
//onmove
pt.x = posX;
pt.y = posY;
// convert the mouse X and Y so that it's relative to the svg element
var transformed = pt.matrixTransform(svg.getScreenCTM().inverse());
box.attr({ x : transformed.x, y : transformed.y });
},
function(){
//onstart
},
function(){
//onend
}
);
}
Below is an example in Javascript of what I use across all browsers and all viewPorts. It uses svg matrix transforms. The events are attached to each element to be dragged. In the example I've included mouse position readout both for html and svg.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>E - Universal Drag/Drop</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body style='padding:10px;font-family:arial'>
<center>
<h4>Universal Drag/Drop</h4>
<div style='width:90%;background-color:gainsboro;text-align:justify;padding:10px;border-radius:6px;'>
This example uses matrix transforms, with object methods, not strings. It can seamlessly drag/drop elements that have previously been transformed and reside it different viewPorts. It employs <b>getScreenCTM</b>, <b>createSVGTransform</b> and binds the element to a <b>transform List</b>
</div>
<table>
<tr><td align=left>
Scenerio:<br />
A 400x400 DIV contains an SVG with viewBox=0 0 330 330.<br />
1.) The blue rect element is contained in a <g>.<br />
2.) The <g> element has been transformed.<br />
3.) The maroon rect resides in a different viewPort.<br />
4.) The orange circle has been transformed.<br />
5.) Drag/Drop the circles and rectangles.<br />
</td>
<td align=left>
<div id="svgDiv" style='background-color:lightgreen;width:400px;height:400px;'>
<svg id="mySVG" width="100%" height="100%" viewBox="0 0 300 300" onmousemove="svgCursor(evt)">
<circle onmousedown=startDrag(evt) onmousemove=drag(evt) onmouseup=endDrag() id="redCircle" cx="120" cy="180" r="40" fill="red" stroke="black" stroke-width="2" />
<circle onmousedown=startDrag(evt) onmousemove=drag(evt) onmouseup=endDrag() id="orangeCircle" cx="200" cy="200" r="40" fill="orange" stroke="black" stroke-width="2" />
<svg viewBox="0 100 800 800">
<rect onmousedown=startDrag(evt) onmousemove=drag(evt) onmouseup=endDrag() id="maroonRect" x="220" y="250" width="60" height="60" fill="maroon" stroke="black" stroke-width="2" />
</svg>
<g id="myG" >
<rect onmousedown=startDrag(evt) onmousemove=drag(evt) onmouseup=endDrag() id="blueRect" x="220" y="250" width="60" height="60" fill="blue" stroke="black" stroke-width="2" />
</g>
</svg>
</div>
</td>
<td align=left>
<b>HTML Page Values:</b><br />
<input type=text id=htmlMouseXValue size=1 />: mouse X<br />
<input type=text id=htmlMouseYValue size=1 />: mouse Y<br />
<br />
<b>SVG Image Values:</b><br />
<input type=text id=svgXValue size=1 />: svg X<br />
<input type=text id=svgYValue size=1 />: svg Y<br />
</td>
</tr></table>
<br />SVG Source:<br />
<textarea id=svgSourceValue style='font-size:110%;font-family:lucida console;width:90%;height:200px'></textarea>
<br />Javascript:<br />
<textarea id=jsValue style='border-radius:26px;font-size:110%;font-weight:bold;color:midnightblue;padding:16px;background-color:beige;border-width:0px;font-size:100%;font-family:lucida console;width:90%;height:400px'></textarea>
</center>
<div id='browserDiv' style='padding:3px;position:absolute;top:5px;left:5px;background-color:gainsboro;'></div>
<script id=myScript>
var TransformRequestObj
var TransList
var DragTarget=null;
var Dragging = false;
var OffsetX = 0;
var OffsetY = 0;
//---mouse down over element---
function startDrag(evt)
{
if(!Dragging) //---prevents dragging conflicts on other draggable elements---
{
DragTarget = evt.target;
//---reference point to its respective viewport--
var pnt = DragTarget.ownerSVGElement.createSVGPoint();
pnt.x = evt.clientX;
pnt.y = evt.clientY;
//---elements transformed and/or in different(svg) viewports---
var sCTM = DragTarget.getScreenCTM();
var Pnt = pnt.matrixTransform(sCTM.inverse());
TransformRequestObj = DragTarget.ownerSVGElement.createSVGTransform()
//---attach new or existing transform to element, init its transform list---
var myTransListAnim=DragTarget.transform
TransList=myTransListAnim.baseVal
OffsetX = Pnt.x
OffsetY = Pnt.y
Dragging=true;
}
}
//---mouse move---
function drag(evt)
{
if(Dragging)
{
var pnt = DragTarget.ownerSVGElement.createSVGPoint();
pnt.x = evt.clientX;
pnt.y = evt.clientY;
//---elements in different(svg) viewports, and/or transformed ---
var sCTM = DragTarget.getScreenCTM();
var Pnt = pnt.matrixTransform(sCTM.inverse());
Pnt.x -= OffsetX;
Pnt.y -= OffsetY;
TransformRequestObj.setTranslate(Pnt.x,Pnt.y)
TransList.appendItem(TransformRequestObj)
TransList.consolidate()
}
}
//--mouse up---
function endDrag()
{
Dragging = false;
svgSourceValue.value=svgDiv.innerHTML
}
//---onload---
function initTransforms()
{
//---place some transforms on the elements---
//--- transform orange circle---
var transformRequestObj=mySVG.createSVGTransform()
var animTransformList=orangeCircle.transform
var transformList=animTransformList.baseVal
//---translate---
transformRequestObj.setTranslate(130,-300)
transformList.appendItem(transformRequestObj)
transformList.consolidate()
//----scale---
transformRequestObj.setScale(.5,.9)
transformList.appendItem(transformRequestObj)
transformList.consolidate()
//----skewY---
transformRequestObj.setSkewY(52)
transformList.appendItem(transformRequestObj)
transformList.consolidate()
//--init Transform on myG---
var transformRequestObj=mySVG.createSVGTransform()
var animTransformList=myG.transform
var transformList=animTransformList.baseVal
//---translate---
transformRequestObj.setTranslate(-50,-120)
transformList.appendItem(transformRequestObj)
transformList.consolidate()
//----skewX---
transformRequestObj.setSkewX(15)
transformList.appendItem(transformRequestObj)
transformList.consolidate()
//----skewY---
transformRequestObj.setSkewY(20)
transformList.appendItem(transformRequestObj)
transformList.consolidate()
//---rotate---
transformRequestObj.setRotate(30,200,200)
transformList.appendItem(transformRequestObj)
transformList.consolidate()
}
document.onmousemove = htmCursor
//---event is the html event object---
function htmCursor(event)
{
var event = event || window.event;
myMouseX=event.clientX;
myMouseY=event.clientY;
myMouseX = myMouseX + document.documentElement.scrollLeft;
myMouseY = myMouseY + document.documentElement.scrollTop;
htmlMouseXValue.value=myMouseX
htmlMouseYValue.value=myMouseY
}
//---evt is the svg event object--
function svgCursor(evt)
{
var rect = svgDiv.getBoundingClientRect();
svgXValue.value=evt.clientX-rect.left
svgYValue.value=evt.clientY-rect.top
}
</script>
<script>
document.addEventListener("onload",init(),false)
function init()
{
initTransforms()
svgSourceValue.value=svgDiv.innerHTML
jsValue.value=myScript.text
}
</script>
</body>
</html>
Maybe you could just use getCTM rather than getScreenCTM, so...
var transformed = pt.matrixTransform(svg.getCTM().inverse());
and I think you may need to adjust with something like
pt.x = posX - S.node.offsetLeft;
pt.y = posY - S.node.offsetTop;
jsfiddle here
Update: as above doesn't work in Firefox, you could take the clientX/Y with the original getScreenCTM, so
pt.x = e.clientX
pt.y = e.clientY;
fiddle here
I'm guessing you need more than just the example here though (in which case you could just use Snaps own drag funcs), so its difficult to tell if this solution will work for other cases you need.
Please see the jsfiddle
sample output:
offset based on svg x:12 y:34
mouse click based on screen x:22 y:38
mouse coord based on svg x:10 y:4
The above sample output is generated when I click on the rectangle on the top-left corner.
As far as I understood, getScreenCTM interface provides the transformation matrix for the element (svg here). I got it as the first line. Second line indicate the mouse coordinate based on the screen coordinate. When I apply the transformation matrix to the mouse click, I expect the point will be translated to svg coordinate. That value is the 3 rd line above. I am not sure that it is correct. The rectangle has a y coordinate 10, and the click event is only availbake within the rectangle. So how could the mouse coord based on svg go below 10??
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE HTML><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
</head>
<body>
<meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8" />
<h1>sdsd</h1>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" height="200">
<g fill="none" stroke="black" stroke-width="1" >
<!-- Draw the axes of the original coordinate system -->
<line x1="0" y1=".5" x2="400" y2=".5" />
<line x1=".5" y1="0" x2=".5" y2="150" />
</g>
<g >
<rect class="drag resize" x="10" y="10" width="100" height="50" fill="#c66" />
</g>
</svg>
<h2 id="op"></h2>
<script type="text/javascript" src="vb.js"></script>
</body>
</html>
var svg = document.getElementsByTagName('svg')[0];
var svgNS = svg.getAttribute('xmlns');
var pt = svg.createSVGPoint();
var el1 = document.getElementsByTagName('rect')[0];
var log_svgcursorPoint,
log_mouseclick,
log_mousecoord;
function svgcursorPoint(evt){
pt.x = evt.clientX; pt.y = evt.clientY;
var a = svg.getScreenCTM();
log_svgcursorPoint = "offset based on svg"+ " x:" + a.e +" y:" + a.f;
var b = a.inverse();
return pt.matrixTransform(b);
};
(function(el){
el.addEventListener('mousedown',function(e){
log_mouseclick = "mouse click based on screen"+ " x:" + e.clientX +" y:" + e.clientY ;
var svgmouse = svgcursorPoint(e);
log_mousecoord = "mouse coord based on svg"+ " x:" + svgmouse.x +" y:" +svgmouse.y;
document.getElementById('op').innerHTML = log_svgcursorPoint + "<br>" + log_mouseclick + "<br>" + log_mousecoord;
},false);
})(el1);
seems like a defect, depending on the browser zoom level. Raised an issue http://code.google.com/p/chromium/issues/detail?id=81995