Best Ultimate Guide About Screen Coordinates to SVG Coordinates

Let’s take a look at how we can convert screen coordinates to SVG coordinates. The concept is similar to CSS, where we can use the translate method to move an element on the screen relative to its parent. Are we able to apply the same concept to SVG using JavaScript? Do we have a translation method in SVG? How do we use it? Let’s try it out and find out!

There are many vector coordinate systems, and the most commonly used are the screen coordinate system and the SVG coordinate system. SVG coordinates are more complex than screen coordinates, but they can also be converted to screen coordinates, which is what we will discuss today.

In this article, we will write a script that converts screen coordinates to SVG coordinates. For an overview, find the demo below and edit the code to see the immediate output.

Screen Coordinates to SVG Coordinates demo below

See the Pen Screen coordinates to SVG coordinates by Sten Hougaard (@netsi1964) on CodePen.

HTML Code
<div class="flex">
  <div class="half-width">
    <svg
      width="100%"
      height="300"
      viewBox="0 0 1055 581"
      version="1.1"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink"
    >
      <path stroke="black" fill="none" d="M 117 52 L 483 161 697 168"></path>
      <circle cx="117" cy="52" r="15"></circle>
      <text x="117" y="22">(117,52)</text>
      <circle cx="483" cy="161" r="15"></circle>
      <text x="483" y="131">(483,161)</text>
      <circle cx="697" cy="168" r="15"></circle>
      <text x="697" y="138">(697,168)</text>
      <text id="dynText" x="0" y="-20"></text>
      <circle
        fill="hsla(120,50%,85%,.5)"
        r="15"
        id="svgClientX"
        stroke="hsla(120,50%,45%,.75)"
      />
    </svg>
  </div>
  <div>
    <div class="flex flex--fixed">
      <input type="checkbox" class="ctm" />
      <h2>Use CTM transform</h2>
    </div>
    <div class="usedCode"><em>Try to move mouse over SVG drawing</em></div>
  </div>
  <div class="container"></div>
</div>
CSS Code
.flex {
	display: flex;
	flex-direction: row;
	justify-content: flex-start;
	align-items: flex-start;
}

.flex--vertical {
	flex-direction: column;
}

.row {
	display: flex;
	flex-direction: row;
	justify-content: flex-start;
	align-items: center;
	width: 100%;
	margin: 5px 0;
}

.half-width {
	width: 100%;
	padding: 15px;
}

svg {
	outline: solid 1px black;
}

input {
	padding: 0.5em;
	width: 2em;
	height: 2em;
}

input[type="checkbox"] {
	display: block;
	width: 28px;
	height: 28px;
	border-radius: 50%;
	background: #cca92c;
	cursor: pointer;
	box-shadow: 0 0 0 rgba(204, 169, 44, 0.4);
	animation: pulse 2s infinite;
	align-self: center;
	margin-right: 10px;
}

input[type="checkbox"]:hover,
input[type="checkbox"]:checked {
	animation: none;
}

@-webkit-keyframes pulse {
	0% {
		-webkit-box-shadow: 0 0 0 0 rgba(204, 169, 44, 1);
	}
	70% {
		-webkit-box-shadow: 0 0 0 10px rgba(204, 169, 44, 0);
	}
	100% {
		-webkit-box-shadow: 0 0 0 0 rgba(204, 169, 44, 0);
	}
}

@keyframes pulse {
	0% {
		-moz-box-shadow: 0 0 0 0 rgba(204, 169, 44, 1);
		box-shadow: 0 0 0 0 rgba(204, 169, 44, 0.4);
	}
	70% {
		-moz-box-shadow: 0 0 0 10px rgba(204, 169, 44, 0);
		box-shadow: 0 0 0 10px rgba(204, 169, 44, 0);
	}
	100% {
		-moz-box-shadow: 0 0 0 0 rgba(204, 169, 44, 0);
		box-shadow: 0 0 0 0 rgba(204, 169, 44, 0);
	}
}

@media (max-width: 1000px) {
	.flex:not(.flex--fixed) {
		display: flex;
		flex-direction: column;
	}
	svg {
		height: 120px;
	}
}
JavaScript Code
const optimizedResize = (function () {

	var callbacks = [],
		running = false;

	// fired on resize event
	function resize() {
		if (!running) {
			running = true;
			if (window.requestAnimationFrame) {
				window.requestAnimationFrame(runCallbacks);
			} else {
				setTimeout(runCallbacks, 66);
			}
		}

	}

	// run the actual callbacks
	function runCallbacks() {

		callbacks.forEach(function (callback) {
			callback();
		});

		running = false;
	}

	// adds callback to loop
	function addCallback(callback) {

		if (callback) {
			callbacks.push(callback);
		}

	}

	return {
		// public method to add additional callback
		add: function (callback) {
			if (!callbacks.length) {
				window.addEventListener('resize', resize);
			}
			addCallback(callback);
		}
	}
}());


function onMouseMove(evt) {
	const {
		clientX,
		clientY
	} = evt || {
		clientX: 0,
		clientY: 0
	}
	point.x = clientX;
	point.y = clientY;
	let useCTMTransform = '';
	let transformedCoordinate = `(${Math.round(point.x)}, ${Math.round(point.y)})`;
	if (ctm.checked) {
		point = point.matrixTransform(svg.getScreenCTM().inverse());
		transformedCoordinate = `(${Math.round(point.x)}, ${Math.round(point.y)})`;
		useCTMTransform = `\n  point = point.matrixTransform(eleSvg.getScreenCTM().inverse());
  // point = ${transformedCoordinate}`
	}
	eleDynText.setAttribute('x', point.x + 15);
	eleDynText.setAttribute('y', point.y + 25);
	eleDynText.innerHTML = transformedCoordinate;
	svgClientX.setAttribute('cx', point.x);
	svgClientX.setAttribute('cy', point.y);
	eleCode.innerHTML = `<pre>const eleSvg = document.querySelector('svg');
eleSvg.addEventListener('mousemove', ({clientX, clientY}) => {
  let point = eleSvg.createSVGPoint();
  point.x = clientX; // ${clientX}
  point.y = clientY; // ${clientY}${useCTMTransform}\n})</pre>`
}

var ctm = document.querySelector('.ctm'),
	svg = document.querySelector('svg'),
	ctmcode = document.querySelector('.ctmcode'),
	eleCode = document.querySelector('.usedCode'),
	eleDynText = document.querySelector('#dynText');

svg.addEventListener('mousemove', onMouseMove);
var point = svg.createSVGPoint();
onMouseMove();
Screen Coordinates to SVG Coordinates preview
W3TWEAKS
Latest posts by W3TWEAKS (see all)