I am playing around with Snap.svg and jQuery a little bit, and I am creating this bitmoji and trying to make his eyes follow the mouse cursor.
It's all going pretty well except for the eyes. They are transforming and rotating when moving the cursor but not 100% correctly and I can't figure out why.
Here is my code in a JSFiddle: http://jsfiddle.net/bmp5j4x9/1/
Resize the result box, make it bigger and move around your mouse, I guess you'll see what I mean. Or take a look at http://dante-c.be.
This is the jQuery part:
var s = Snap(420, 420).attr({ viewBox: "0 0 120 120" });
$(s.node).appendTo(".button");
var image = s.paper.image('https://render.bitstrips.com/v2/cpanel/10220069-circle-357822728_5-s4-v1.png?palette=1', 0, 0, 1, 1);
image = image.pattern().attr({
patternContentUnits: "objectBoundingBox",
patternUnits: "",
width: "100%", height: "100%", viewBox: ""
});
var bitmojiCircle = s.circle(60, 60, 39).attr({ fill: image });
var circleX = 50, circleY = 63, circleRadius = 4.5;
var bigEyeCircle = s.circle(circleX, circleY, circleRadius);
var L1 = s.path("M "+circleX+" "+circleY +"L 0 0").attr({stroke: "blue"});
bigEyeCircle.attr({
fill: "#bada55",
stroke: "#000",
strokeWidth: 1
});
var smallEyeCircle = s.circle(0,0,3.5).attr({ fill: "red" });
var opacityCircle = s.circle(60, 60, 39).attr({ fill: "rgba(255,255,255,0.7)" });
var menuButton = s.path("M58.486 56.324H57.19c-.48 0-.866.387-.866.865v1.29c0 .48.387.86.865.86h1.29c.48 0 .86-.39.86-.87v-1.29c0-.48-.39-.87-.87-.87zm-4.324 0h-1.297c-.478 0-.865.387-.865.865v1.29c0 .48.387.86.865.86h1.297c.478 0 .865-.39.865-.87v-1.29c0-.48-.387-.87-.865-.87zM58.486 52H57.19c-.48 0-.866.387-.866.865v1.297c0 .478.387.865.865.865h1.29c.48 0 .86-.387.86-.865v-1.297c0-.478-.39-.865-.87-.865zm-4.324 0h-1.297c-.478 0-.865.387-.865.865v1.297c0 .478.387.865.865.865h1.297c.478 0 .865-.387.865-.865v-1.297c0-.478-.387-.865-.865-.865zm12.973 4.324h-1.297c-.478 0-.865.387-.865.865v1.29c0 .48.387.86.865.86h1.297c.478 0 .865-.39.865-.87v-1.29c0-.48-.387-.87-.865-.87zm-4.324 0h-1.29c-.48 0-.86.387-.86.865v1.29c0 .48.39.86.87.86h1.3c.48 0 .87-.39.87-.87v-1.29c0-.48-.38-.87-.86-.87zM67.14 52h-1.3c-.48 0-.866.387-.866.865v1.297c0 .478.387.865.865.865h1.29c.48 0 .86-.387.86-.865v-1.297c0-.478-.39-.865-.87-.865zm-4.324 0H61.52c-.48 0-.865.387-.865.865v1.297c0 .478.386.865.865.865h1.297c.48 0 .866-.387.866-.865v-1.297c0-.478-.386-.865-.864-.865zM58.49 64.973h-1.3c-.48 0-.866.387-.866.865v1.297c0 .478.387.865.865.865h1.29c.48 0 .86-.387.86-.865v-1.297c0-.478-.39-.865-.87-.865zm-4.325 0h-1.297c-.478 0-.865.387-.865.865v1.297c0 .478.387.865.865.865h1.297c.478 0 .865-.387.865-.865v-1.297c0-.478-.388-.865-.866-.865zm4.324-4.324h-1.3c-.48 0-.87.38-.87.86v1.29c0 .48.38.86.86.86h1.29c.48 0 .86-.39.86-.87V61.5c0-.48-.39-.864-.87-.864zm-4.33 0h-1.3c-.48 0-.87.38-.87.86v1.29c0 .48.38.86.86.86h1.29c.472 0 .86-.39.86-.87V61.5c0-.48-.39-.864-.867-.864zm12.97 4.32h-1.29c-.48 0-.87.38-.87.86v1.29c0 .48.38.86.86.86h1.29c.48 0 .86-.39.86-.87v-1.29c0-.48-.387-.87-.865-.87zm-4.33 0h-1.29c-.48 0-.87.38-.87.86v1.29c0 .48.38.86.86.86h1.3c.48 0 .862-.39.862-.87v-1.29c0-.48-.39-.87-.867-.87zm4.32-4.33h-1.3c-.48 0-.87.38-.87.86v1.3c0 .48.384.86.862.86h1.3c.476 0 .863-.39.863-.87V61.5c0-.48-.388-.864-.866-.864zm-4.33 0H61.5c-.48 0-.864.38-.864.86v1.3c0 .48.387.86.866.86H62.8c.48 0 .87-.39.87-.87V61.5c0-.48-.383-.864-.86-.864z").attr({
class: "menu-button",
fill: "#9B9B9B",
fillRule: "nonzero"
});
var c1 = s.circle(60, 60, 53).attr({ stroke: "#9B9B9B", transform: "rotate(90 60 60)" });
var c2 = s.circle(60, 7, 2).attr({ fill: "#9B9B9B" });
var c3 = s.circle(60, 113, 2).attr({ fill: "#9B9B9B" });
var c4 = s.circle(113, 60, 2).attr({ fill: "#9B9B9B" });
var c5 = s.circle(7, 60, 2).attr({ fill: "#9B9B9B" });
var outerCircles = s.group(c1, c2, c3, c4, c5).attr({ class: "outer-circle" });
var fullSVG = s.group(bitmojiCircle, bigEyeCircle, L1, smallEyeCircle, opacityCircle, menuButton, outerCircles).attr({ fill: "none", fillRule: "evenodd" });
function OnMouseMove(evt) {
L1.attr({ d: "M "+circleX+" "+circleY +"L "+evt.clientX+" "+evt.clientY });
var totalLength = L1.getTotalLength();
if (totalLength < circleRadius) {
smallEyeCircle.attr({ cx: evt.clientX , cy: evt.clientY });
} else {
var PAL = L1.getPointAtLength(circleRadius);
smallEyeCircle.attr({ cx: PAL.x , cy: PAL.y });
}
}
document.onmousemove = OnMouseMove;
Edit
Tried to throttle/debounce it, as Nikos said, by replacing the OnMouseMove function with the following code:
var pageX = 0,
pageY = 0;
var moveIt = function() {
L1.attr({ d: "M "+circleX+" "+circleY +"L "+pageX+" "+pageY });
var totalLength = L1.getTotalLength();
if (totalLength < circleRadius) {
smallEyeCircle.attr({ cx: pageX, cy: pageY });
} else {
var PAL = L1.getPointAtLength(circleRadius);
smallEyeCircle.attr({ cx: PAL.x , cy: PAL.y });
}
setTimeout(moveIt, 1000/25);
};
$(document).on('mousemove', function(e) {
pageX = e.pageX;
pageY = e.pageY;
}).one('mousemove', moveIt);
This does not seem to work.
Update
I found a better solution, but it's still not 100% functional, the area for the eyeball to move in, is too big, but I don't know how to get it smaller.
Here is the updated fiddle: http://jsfiddle.net/bmp5j4x9/3/
As I've commented you are detecting the mouse position relative to the document and you are using those coordinates to draw inside an SVG canvas whose size is 120/120. This can not work.
Next comes an example (Javascript) where the the line is following the mouse correctly
let m = {}
test.addEventListener("mousemove",(e)=>{
// draw the line on mousemove
m=oMousePosSVG(e);
_line.setAttributeNS(null,"x2",m.x);
_line.setAttributeNS(null,"y2",m.y);
})
function oMousePosSVG(e) {
// a function to detect the mouse position inside an SVG
var p = test.createSVGPoint();
p.x = e.clientX;
p.y = e.clientY;
var ctm = test.getScreenCTM().inverse();
var p = p.matrixTransform(ctm);
return p;
}
<svg id="test" viewBox="0 0 120 120" width="100vw" height="100vh">
<circle cx="60" cy="60" r="20" fill="#d9d9d9" />
<line id="_line" x1="55" y1="60" stroke="blue" />
</svg>
Yet an other solution would be letting things as you have them but recalculating the mouse position in function of the document size:
let w = window.innerWidth;
let h = window.innerHeight;
let m = {}
document.addEventListener("mousemove",(e)=>{
//get the mouse position
m=oMousePos(e);
//calculate the x2 and y2 for the line in function of the size of the window
let x2 = map(m.x, 0, w, 0, 120)
let y2 = map(m.y, 0, h, 0, 120)
// set the attributes x2 and y2 for the line
_line.setAttributeNS(null,"x2",x2);
_line.setAttributeNS(null,"y2",y2);
})
function init(){
// a function to get the size of the window on resize
w = window.innerWidth;
h = window.innerHeight;
}
// you call the init on resize
setTimeout(function() {
init();
addEventListener('resize', init, false);
}, 15);
// a function to get the mouse position
function oMousePos(evt) {
return {
x: evt.clientX,
y: evt.clientY
}
}
function map(n, a, b, _a, _b) {
let d = b - a;
let _d = _b - _a;
let u = _d / d;
return _a + n * u;
}
svg {
border: 1px solid;
position: absolute;
margin: auto;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
<svg id="test" viewBox="0 0 120 120" width="240" >
<circle cx="60" cy="60" r="20" fill="#d9d9d9" />
<line id="_line" x1="55" y1="60" stroke="blue" />
</svg>
I hope it helps.
Update
The OP comments that in fact they want the small red circle to follow the mouse. In this case you need to calculate the angle between the center of the eye and the mouse, and you draw the red circle using this angle:
let m = {}
let c = {x:55,y:60}// the center of the eye
let r = whitecircle.getAttribute("r") - redcircle.getAttribute("r") - .5;
// where .5 is 1/2 stroke-width
test.addEventListener("mousemove",(e)=>{
// draw the line on mousemove
m=oMousePosSVG(e);
//_line.setAttributeNS(null,"x2",m.x);
//_line.setAttributeNS(null,"y2",m.y);
var angle = getAngle(m,c)
//this are the coordinates for the center of the red circle
var x2 = c.x + r * Math.cos(angle);
var y2 = c.y + r * Math.sin(angle);
redcircle.setAttributeNS(null,"cx",x2);
redcircle.setAttributeNS(null,"cy",y2);
})
function oMousePosSVG(e) {
// a function to detect the mouse position inside an SVG
var p = test.createSVGPoint();
p.x = e.clientX;
p.y = e.clientY;
var ctm = test.getScreenCTM().inverse();
var p = p.matrixTransform(ctm);
return p;
}
function getAngle(p1,p2){
// a function to calculate the angle between two points p1 and p2
var deltaX = p1.x - p2.x;
var deltaY = p1.y - p2.y;
return Math.atan2(deltaY, deltaX);
}
<svg id="test" viewBox="0 0 120 120" width="100vw" height="100vh">
<circle cx="60" cy="60" r="20" fill="#d9d9d9" />
<circle id="whitecircle" cx="55" cy="60" r="5" fill="#fff" stroke="black" />
<circle cx="55" cy="60" r="3" fill="#f00" id="redcircle" />
<!--<line id="_line" x1="55" y1="60" stroke="blue" />-->
</svg>
Related
Refering to codepen demo here, I created this zoom in and zoom out function which works fine.
May I ask the code to zoom in and out of the area which my cursor points using the mouse scroll?
For example, if my cursor is moved to the green area in my picture and when I scoll the mouse, it will zoom into and out of the specific area? Any help will be very much appreicated :)
<div onclick="zoomin()" style="display: block;float: left;border: 1px solid;cursor: pointer">
ZoomIn
</div>
<div onclick="zoomout()" style="display: block;float: left;border: 1px solid;cursor: pointer;margin-left: 10px">
ZoomOut
</div>
var svg=document.getElementById('mainsvg');
var gnode=document.getElementById('gnode');
var zoomPercentage=0.25;
var MAXIMUM_ZOOM_HEIGHT = 1400;
var baseBox={};
var level=0;
var widthRatio,heightRatio;
var clientheight = document.documentElement.clientHeight;
var clientwidth = document.documentElement.clientWidth;
function setup(){
var
baseX,
baseY,
baseWidth,
baseHeight,
percentageDifference,
heightDifference;
svg.setAttribute('height', clientheight);
svg.setAttribute('width', clientwidth);
var boundry=document.getElementById('boundry');
boundry.setAttribute('height', clientheight-1);
boundry.setAttribute('width', clientwidth-1);
var centernode=document.getElementById('centernode');
centernode.setAttribute('cy', clientheight/2);
centernode.setAttribute('cx', clientwidth/2);
if (svg.height.baseVal.value >= MAXIMUM_ZOOM_HEIGHT)
baseHeight = MAXIMUM_ZOOM_HEIGHT;
else
baseHeight = Math.round(gnode.getBBox().height) + 60;
baseY = (svg.height.baseVal.value - baseHeight) / 2;
percentageDifference = baseHeight / svg.height.baseVal.value;
baseWidth = percentageDifference * svg.width.baseVal.value;
baseX = (svg.width.baseVal.value - baseWidth) / 2;
baseBox.x = baseX;
baseBox.y = baseY;
baseBox.width = baseWidth;
baseBox.height = baseHeight;
level = 0;
heightDifference = MAXIMUM_ZOOM_HEIGHT - baseHeight;
zoomPercentage = (heightDifference / 10) / heightDifference;
setViewBox(baseBox);
}
function setViewBox(viewBox) {
svg.viewBox.baseVal.x = Math.round(viewBox.x);
svg.viewBox.baseVal.y = Math.round(viewBox.y);
svg.viewBox.baseVal.width = Math.round(viewBox.width);
svg.viewBox.baseVal.height = Math.round(viewBox.height);
setRatios();
}
function setRatios () {
widthRatio = svg.viewBox.baseVal.width / svg.width.baseVal.value;
heightRatio = svg.viewBox.baseVal.height / svg.height.baseVal.value;
}
function calculateViewBox(level) {
var
height = baseBox.height - (zoomPercentage * level * baseBox.height),
y = baseBox.y + (baseBox.height - height) / 2,
width = baseBox.width - (zoomPercentage * level * baseBox.width),
x = baseBox.x + (baseBox.width - width) / 2,
viewBox = {
x: x,
y: y,
width: width,
height: height
}
return viewBox;
}
function zoomin(){
level++;
if(level>5)
level=5;
var
x,
y,
paperViewBox = svg.viewBox.baseVal,
previousViewBox = calculateViewBox(level - 1),
newViewBox = calculateViewBox(level);
//callback = this.afterZoom;
if (Math.round(paperViewBox.x) > Math.round(newViewBox.x))
/**
* is panned left
*/
x = paperViewBox.x - (previousViewBox.width - newViewBox.width) / 2;
else if (Math.round(paperViewBox.x) < Math.round(previousViewBox.x) - (Math.round(newViewBox.x) - Math.round(previousViewBox.x)))
/**
* is panned right
*/
x = paperViewBox.x + (previousViewBox.width - newViewBox.width) + (previousViewBox.width - newViewBox.width) / 2;
else
x = newViewBox.x;
if (Math.round(paperViewBox.y) > Math.round(newViewBox.y))
/**
* is panned up
*/
y = paperViewBox.y - (previousViewBox.height - newViewBox.height) / 2;
else if (Math.round(paperViewBox.y) < Math.round(previousViewBox.y) - (Math.round(newViewBox.y) - Math.round(previousViewBox.y)))
/**
* is panned down
*/
y = paperViewBox.y + (previousViewBox.height - newViewBox.height) + (previousViewBox.height - newViewBox.height) / 2;
else
y = newViewBox.y;
var data = {
viewBox: {
x: x,
y: y,
width: newViewBox.width,
height: newViewBox.height
}
}
SetZoomViewBox(data);
}
function SetZoomViewBox(data){
var viewBox = data.viewBox;
svg.viewBox.baseVal.x = Math.round(viewBox.x);
svg.viewBox.baseVal.y = Math.round(viewBox.y);
svg.viewBox.baseVal.width = Math.round(viewBox.width);
svg.viewBox.baseVal.height = Math.round(viewBox.height);
setRatios();
}
function zoomout(){
level--;
if(level<0)
level=0;
var
x,
y,
paperViewBox = svg.viewBox.baseVal,
previousViewBox = calculateViewBox(level + 1),
newViewBox = calculateViewBox(level);
if (Math.round(paperViewBox.x) > Math.round(previousViewBox.x) + (Math.round(previousViewBox.x) - Math.round(newViewBox.x)))
/**
* is panned left
*/
x = paperViewBox.x - (newViewBox.width - previousViewBox.width);
else if (Math.round(paperViewBox.x) < Math.round(previousViewBox.x))
/**
* is panned right
*/
x = paperViewBox.x;
else
x = newViewBox.x;
if (Math.round(paperViewBox.y) > Math.round(previousViewBox.y) + (Math.round(previousViewBox.y) - Math.round(newViewBox.y)))
/**
* is panned up
*/
y = paperViewBox.y - (newViewBox.height - previousViewBox.height);
else if (Math.round(paperViewBox.y) < Math.round(previousViewBox.y))
/**
* is panned down
*/
y = paperViewBox.y;
else
y = newViewBox.y;
var data = {
viewBox: {
x: x,
y: y,
width: newViewBox.width,
height: newViewBox.height
}
}
SetZoomViewBox(data);
}
setup();
<div onclick="zoomin()" style="display: block;float: left;border: 1px solid;cursor: pointer">
ZoomIn
</div>
<div onclick="zoomout()" style="display: block;float: left;border: 1px solid;cursor: pointer;margin-left: 10px">
ZoomOut
</div>
<svg id="mainsvg" width="600px" height="500px" viewBox="0 0 600 500">
<g id="gnode">
<rect id="boundry" x="0" y="0" width="599" height="499" fill="none" stroke='black'/>
<circle id="centernode" cx="300" cy="250" r="5" fill="red" stroke="none" />
<rect id="selected" x="450" y="100" width="50" height="50" fill="blue" stroke='none'/>
</g>
</svg>
You can add the following code to handle zoom in and zoom out based on mouse wheel event:
svg.addEventListener("wheel", function(event) {
if (event.deltaY < 0) {
zoomin();
} else if (event.deltaY > 0) {
zoomout();
}
});
Place this code after the setup function. It will handle the zoom in and zoom out events when the mouse wheel is scrolled over the svg element.
I am trying to have an svg with internal javascript, see here <"https://jsfiddle.net/melvinT/boaus7k1/76/">jsfiddle of svg draggable triangle path it is three circles that drag a triangle. But there is just a bit that stubbornly stop working when I move it into the svg, and I can not figure out why?
The section that mysteriously stops moving when I drag it into the svg is the following (it is at the end) Why? How can I move it into the svg?
var elements = [p1, p2, p3,];
for (i = 0; i < elements.length; i++) {
elements[i].addEventListener('mousedown', onMouseDown);
}
If you put the script element at the bottom of your SVG then it should work because then it will run after the circle elements have been loaded.
<svg id="figure1" width="500" height="500">
<style>
#figure1 {
border: 1px solid blue;
}
</style>
<path d="M170 150 20 110 250 60 z" stroke="pink" fill="none" id="controlPath"/>
<circle cx="170" cy="150" r="8" id="p1" fill="red" style="cursor: move;"/>
<circle cx="20" cy="110" r="8" id="p2" fill="black" style="cursor: move;"/>
<circle cx="250" cy="60" r="8" id="p3" fill="lightgrey" style="cursor: move;"/>
<script type="text/javascript">
<![CDATA[
var svg = document.getElementById('figure1');
svg.addEventListener('mousemove', onMouseMove);
svg.addEventListener('mouseup', onMouseUp);
var controlPath = document.getElementById('controlPath');
var dragElem = null;
var p1 = document.getElementById('p1');
var p2 = document.getElementById('p2');
var p3 = document.getElementById('p3');
var elements = [p1, p2, p3,];
for (i = 0; i < elements.length; i++) {
elements[i].addEventListener('mousedown', onMouseDown);
}
function onMouseMove(ev) {
var p;
if (!dragElem) {
return;
}
p = getClientPointInSVG(ev);
dragElem.setAttribute('cx', p.x - mouseOffsetX);
dragElem.setAttribute('cy', p.y - mouseOffsetY);
controlPath.setAttribute('d',
'M' + p1.getAttribute('cx') + ' ' + p1.getAttribute('cy') +
' ' + p2.getAttribute('cx') + ' ' + p2.getAttribute('cy') +
' ' + p3.getAttribute('cx') + ' ' + p3.getAttribute('cy') +
' ' + 'z' );
}
function onMouseDown(ev) {
var p;
dragElem = ev.target;
p = getClientPointInSVG(ev);
mouseOffsetX = p.x - dragElem.getAttribute('cx');
mouseOffsetY = p.y - dragElem.getAttribute('cy');
}
function getClientPointInSVG(ev) {
var p, m;
p = svg.createSVGPoint();
p.x = ev.clientX;
p.y = ev.clientY;
m = dragElem.getScreenCTM();
return p.matrixTransform(m.inverse());
}
function onMouseUp(ev) {
dragElem = null;
}
var mouseOffsetX, mouseOffsetY;
]]>
</script>
</svg>
please check this demo .
https://codepen.io/wasiabbas/pen/JQmXmE
when curly braces if coming from left i need to make it fit in height and when it rotates then i need to fit the curly braces in window width. how can i do it?
var winWidth = window.innerWidth,
winHeight = window.innerHeight;
const bracesContainer = document.querySelector('.braces-container');
const braces = document.getElementById('braces');
const bracesbg = document.getElementById('bracesbg');
const leftbraces = document.getElementById('leftcb');
const topbraces = document.getElementById('topcb');
function loadAnimation(){
$(braces).css({
backgroundColor: "#000",
width: winWidth,
height: winHeight
});
TweenMax.to(braces, 0.9, {
right:0
});
TweenMax.to(leftbraces, 0.9, {
opacity: 0,
rotation: -90,
y: '60%',
x: '10%',
transformOrigin: 'center center',
delay: .5
});
TweenMax.to(topbraces, 1.2, {
opacity:1,
delay: 1
});
}
$(document).ready(function(){
loadAnimation();
});
Here's a snippet with a simple solution (using D3.js):
const svg = d3.select('svg');
const width = parseInt(svg.attr('width'));
const height = parseInt(svg.attr('height'));
svg.select('path')
.transition()
.delay(500)
.duration(1000)
.attr('transform', `translate(0, ${width}) rotate(-90) scale(${width/height})`)
svg {
background-color: #000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width='250' height='200'>
<path d='M 0,0 H 100 Q 125,0 125,25 V 75 Q 125,100 150,100
Q 125,100 125,125 V 175 Q 125,200 100,200 H 0 z' fill='#fff' />
</svg>
I'm trying to place a 25px * 25px div on a certain percentage of an svg track. This is the way i'm doing it now:
getCheckpointPercent();
//Get the readers from the Json file, and add the percentage to the array.
function getCheckpointPercent(){
$.getJSON("readers.json", function (data) {
var total = 0;
var semitotal = 0;
var currentdistance = 0;
$.each(data, function (index, value) {
total = total + value['distanceToNext'];
});
console.log(total);
$.each(data, function (index, value) {
value['percent'] = semitotal / total * 100;
semitotal = semitotal + value['distanceToNext'];
});
console.log(data);
placeCheckpoints(data);
});
}
//Place the checkpoints on the track, on the spot the corresponds to their percentage
function placeCheckpoints(readers){
$.each(readers, function (index, value) {
var punt = getPointOnTrack(value['percent']);
$('#cps').append('<div class="checkpoint" id="'+index+'"></div>');
$( "#"+index ).css('left',punt.x);
$( "#"+index ).css('top',punt.y);
$( "#"+index ).css('position','absolute');
});
}
//Get coords of point on track using percentage
function getPointOnTrack(prcnt){
var track = $( '#track' );
var trackLength = document.getElementById( 'track' ).getTotalLength();
var part = trackLength * prcnt / 100;
pt = document.getElementById( 'track' ).getPointAtLength(part);
return pt;
}
With the following result:
https://imgur.com/a/bW0KuCN
As you can see it's off by some pixels. How can i place them correctly?
EDIT: SVG track:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink"
width="100%" height="100%" viewBox="0 0 1783 903" id="svg">
<path d="M1697.17,63.491c-105.906,-119.003 -609.921,-29.945 -876.794,34.426c-164.703,39.726 -224.547,269.311 -335.753,272.609c-214.672,6.366 -258.259,-379.064 -345.329,-337.073c-178.323,85.998 -184.301,834.002 13.654,836.966c177.382,2.655 251.631,-254.971 409.655,-235.198c181.21,22.674 152.502,168.163 391.991,209.317c228.308,39.232 223.472,-183.574 312.715,-193.699c73.817,-8.375 276.248,275.455 417.573,244.156c130.744,-28.956 112.095,-279.189 12.288,-326.222c-157.212,-74.083 -693.907,-55.006 -724.395,-117.798c-54.001,-111.215 464.9,-139.592 415.502,-226.446c-53.998,-94.941 428.86,-26.236 308.893,-161.038Z" stroke="#000000" stroke-width="2" fill="none" id="track"/>
</svg>
I'm not very sure that this is what you want: the div #label's position is the position of the mouse over the div #SVG The text content of the #label is the value of the x and y attributes of the point on the path.
Please read the comments in the code.
const SVG_NS = "http://www.w3.org/2000/svg";
// the total length of the path
let l = test.getTotalLength();
// the array of the points on the path
let points = [];
for (let i = 0; i < l; i += l / 4) {
// test is the id for the path
let point = test.getPointAtLength(i);
points.push(point);
}
// for every point I draw a circle
let circles = [];
for (let i = 0; i < points.length; i++) {
let o = { cx: points[i].x, cy: points[i].y, r: 30, fill: "blue" };
// create a new circle and save the circle in the circles array
circles.push(drawCircle(o, svg));
}
// a function to draw a circle
function drawCircle(o, parent) {
let circle = document.createElementNS(SVG_NS, "circle");
for (var name in o) {
if (o.hasOwnProperty(name)) {
circle.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(circle);
return circle;
}
// for each circle there is an event listener that calculate the position of the div #label
circles.forEach(c => {
c.addEventListener("mouseenter", evt => {
let x = parseInt(c.getAttribute("cx"));
let y = parseInt(c.getAttribute("cy"));
label.innerHTML = `x: ${x} <br> y: ${y}`;
let m = oMousePos(svg, evt);
label.style.cssText = `top:${m.y}px; left:${m.x}px; opacity:1`;
});
});
//when the mouse is not over the circle, the label's opacity is 0
divSVG.addEventListener("mouseover", () => {
label.style.opacity = 0;
});
// a function that gets the position of the mouse over an HTML element
function oMousePos(elmt, evt) {
var ClientRect = elmt.getBoundingClientRect();
return {
//objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
};
}
path {
stroke: black;
fill:none;
}
#label {
position: absolute;
top: 0;
left: 0;
width: 50px;
height: 50px;
opacity: 0;
background:white;
pointer-events:none;
transition: all 0.5s;
}
.svg {
position: relative;
}
<div class="svg" id="divSVG">
<svg id="svg" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink"
width="100%" height="100%" viewBox="0 0 1783 903" id="svg">
<path id="test" d="M1697.17,63.491c-105.906,-119.003 -609.921,-29.945 -876.794,34.426c-164.703,39.726 -224.547,269.311 -335.753,272.609c-214.672,6.366 -258.259,-379.064 -345.329,-337.073c-178.323,85.998 -184.301,834.002 13.654,836.966c177.382,2.655 251.631,-254.971 409.655,-235.198c181.21,22.674 152.502,168.163 391.991,209.317c228.308,39.232 223.472,-183.574 312.715,-193.699c73.817,-8.375 276.248,275.455 417.573,244.156c130.744,-28.956 112.095,-279.189 12.288,-326.222c-157.212,-74.083 -693.907,-55.006 -724.395,-117.798c-54.001,-111.215 464.9,-139.592 415.502,-226.446c-53.998,-94.941 428.86,-26.236 308.893,-161.038Z" stroke="#000000" stroke-width="2" fill="none" id="track"/>
</svg>
<div id="label" ></div>
</div>
I am creating an editor.
I would like the basic functions on my objects which are rotate/resize and translate.
I've managed to do the three of them but the only problem is now my mouse position doesn't follow the control points (the problem gets even worst for the other control points not shown below)..
You'll find below an example for the right middle resize with a rotation angle of 30 degrees with no mouseY position, note that the mouse follows perfectly my control point when the rotation angle equals 0.
Is there a way easily solve this problem, am I going the wrong way?
Here's the jsfiddle link where you can change the rotate angle in the code to see by yourself JSiddle link. (Just click and drag the black control point to resize the object)
//convert value of range amin to amax to the range bmin to bmax;
function imap(value, amin, amax, bmin, bmax)
{
if ((amax - amin))
return (value - amin) * (bmax - bmin) / (amax - amin) + bmin;
return (0);
};
//get mouse coordinates from the SVG element
function getMouse(el, e)
{
var pt = el.createSVGPoint();
pt.x = e.clientX;
pt.y = e.clientY;
var cursorpt = pt.matrixTransform(el.getScreenCTM().inverse());
return({x: cursorpt.x, y: cursorpt.y})
};
var controlPoint = document.getElementById("c"); //My control point element
var mouseX;
var mouseXClicked = 0;
var scaleX = 1;
var scaleY = 1;
var scaleXClicked = 1;
var control = false; // sets if resizeRightMiddle() should be executed
var rectWidth = 100; //is normally tooken with a getBBox() function
var scale = document.getElementById("scale");
function resizeRightMiddle()
{
//convert difference between original mouse X postion on click and actual X mouse position into a scale factor
plusX = imap(mouseX - mouseXClicked, 0, rectWidth, 0, 1);
//add converted scale factor to the original x scale value
resX = scaleXClicked + plusX;
scale.setAttribute('transform', 'scale(' + resX + ',' + scaleY + ')');
scaleX = resX;
}
var svg = document.getElementById("main");
// save Scale and X mouse coordinate on click
svg.addEventListener("mousedown", function(e){
var coord = getMouse(svg, e);
mouseXClicked = coord.x;
scaleXClicked = scaleX;
});
svg.addEventListener("mousemove", function(e){
//get mouse coordinates
var coord = getMouse(svg, e);
mouseX = coord.x;
// resize if control element has been clicked
if (control)
resizeRightMiddle();
});
// desactivate resize
svg.addEventListener("mouseup", function(e){
control = false;
});
//activate resize
controlPoint.addEventListener("mousedown", function(){
control = true;
});
svg {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
<div>
<svg id="main" width="1000" height="300">
<g transform="translate(66, 56)">
<g id="rotate" transform-origin="center" transform="rotate(30)">
<g id="scale">
<path fill="red" stroke="red" d="M 0 0 L 0 100 L 100 100 L 100 0Z" />
<rect id="c" fill="black" stroke="black" x=95 y=45 width=10 height=10 />
</g>
</g>
</g>
</svg>
</div>
The code below calculates how much the mouse moves in the direction of the rectangle's orientation on each mousemove event, instead of from the beginning of the mousedown to the current mousemove. It then updates updatedRectWidth and uses that to calculate the current desired scale.
var controlPoint = document.getElementById("c");
var control = false;
var origRectWidth = 100;
var scale = document.getElementById("scale");
var relevantMouseMoveDist = 0;
var updatedRectWidth = origRectWidth;
var mouseDownPt = {};
var rotateDiv = document.getElementById("rotate");
var rotateString = rotateDiv.getAttribute('transform');
var rectangleAngle = parseInt(rotateString.slice(rotateString.indexOf("(") + 1)) * Math.PI / 180; // retrieve the angle from the DOM
var relevantMouseMoveDist;
var newMousePosn;
var oldMousePosn;
function resizeRightMiddle()
{
updatedRectWidth += relevantMouseMoveDist;
xScale = updatedRectWidth/origRectWidth;
scale.setAttribute('transform', 'scale(' + xScale + ',1)');
}
var svg = document.getElementById("main");
svg.addEventListener("mousemove", function(e){
if (newMousePosn) {
// the former mouse pos'n
oldMousePosn = {x: newMousePosn.x, y: newMousePosn.y};
// the new mouse pos'n
newMousePosn = {x: e.clientX, y: e.clientY};
// the change in the mouse pos'n coordinates since the last move event
var deltaMouseMove = {
x: newMousePosn.x - oldMousePosn.x,
y: newMousePosn.y - oldMousePosn.y
};
// the dir'n of this movement
var angleOfMouseMovement = Math.atan2(deltaMouseMove.y, deltaMouseMove.x);
// the absolute distance the mouse has moved
var mouseMoveDist = Math.sqrt(
deltaMouseMove.x * deltaMouseMove.x +
deltaMouseMove.y * deltaMouseMove.y
);
// the difference in direction between the mouse movement and orientation of the rectangle
var angleDifference = angleOfMouseMovement - rectangleAngle;
// the portion of the mouse movement that is in the direction of the rectangle's orientation
relevantMouseMoveDist = mouseMoveDist * Math.cos(angleDifference);
// resize the rectangle if necessary
if (control) resizeRightMiddle();
} else {
// establish the mouse pos'n during the first mousemove event
newMousePosn = {x: e.clientX, y: e.clientY};
}
});
svg .addEventListener("mouseup" , function(e){control = false;});
controlPoint.addEventListener("mousedown", function(e){control = true ;});
<div>
<svg id="main" width="1000" height="300">
<g transform="translate(66, 56)">
<g id="rotate" transform-origin="center" transform="rotate(40)">
<g id="scale">
<path fill="red" stroke="red" d="M 0 0 L 0 100 L 100 100 L 100 0Z" />
<rect id="c" fill="black" stroke="black" x=95 y=45 width=10 height=10 />
</g>
</g>
</g>
</svg>
</div>