matrixTransform on svg: unexpected behaviour

Viewed 450

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:

enter image description here

Everything is perfect except that when I apply a transformation changing perspective and rotation, the mouse pointer and circle center are not anymore matching:

enter image description here

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?

2 Answers

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.

Related