Search code examples
javascripthtmlsvg

Can not translate screen x,y coordinates to svg coordinates


I am trying to translate the position taken from the click event to the position which should be attached to the element which I want to append to my <svg>. Every time I click I want to add an element in the same place where I have clicked, let's say for example circle. I have read that point.matrixTransform(svgRoot.getScreenCTM().inverse()); should do the trick and translate screen coordinates to SVG coordinates, but in my case it seems not to work (coordinates are not translated at all). This issue appears only if I add viewBox attribute to my <svg>. I do not know what I am doing wrong here. Maybe this cannot work for my case and I need to do the conversion manually?

Example:

const drawingArea = document.querySelector('#drawing-area');
drawingArea.addEventListener('click', (e) => {
  const { left, top } = drawingArea.getBoundingClientRect();
  drawCircle({
    x: e.clientX - left,
    y: e.clientY - top});
});

const drawCircle = ({ x, y }, r = 10) => {
  const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
  circle.setAttribute('cx', x.toString());
  circle.setAttribute('cy', y.toString());
  circle.setAttribute('r', r.toString());
  drawingArea.appendChild(circle);
}

const translateToSVGCoordinates = (x, y) => {
  const point = drawingArea.createSVGPoint();
  point.x = x;
  point.y = y;
  point.matrixTransform(drawingArea.getScreenCTM().inverse());
  return { x: point.x, y: point.y };
}      
body, html {
    width: 100%;
    height: 100%;
    margin: 0;
}
<svg id="drawing-area" width="100%" height="100%" viewBox="0 0 1920 1080" preserveAspectRatio="xMinYMin meet"></svg>

If I wasn't enough clear what result I am expecting, just remove viewBox attribute from <svg> element


Solution

  • You are making it overly complex, both with SVG code and Web Component code...

    Try this JSWC JavaScript Web Component to add <circle> to an existing SVG on every click

    <svg-draw-board></svg-draw-board>
    <style>
      svg-draw-board {
        width      : 100vw;
        height     : 180px;
        display    : inline-block;
        background : pink;
        cursor     : pointer;
      }
    </style>
    <script>
    customElements.define('svg-draw-board', class extends HTMLElement {
      constructor() {
        super()
           .attachShadow({mode:'open'})
           .innerHTML = `<svg width="100%" height="100%" viewBox="0 0 800 800"></svg>`;
        this.svg = this.shadowRoot.querySelector('svg');
        this.svg.onclick = (evt) => { // convert clientXY to SVG XY
          let CTM = this.svg.getScreenCTM();
          let x = (evt.clientX - CTM.e) / CTM.a;
          let y = (evt.clientY - CTM.f) / CTM.d;
          this.drawCircle( x, y , evt.ctrlKey ? 50 : 30 );
        };
      }
      drawCircle(cx, cy, r, fill = 'red') {
        this.svg.append( this.createSVGElement('circle', { cx, cy, r, fill }) );
      }
      createSVGElement( tag, attrs={} ) {
        const el = document.createElementNS('http://www.w3.org/2000/svg',tag);
        Object.entries(attrs).map(([name, val]) => el.setAttribute(name, val));
        return el;
      }
    });
    </script>