Place div on svg track - javascript

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>

Related

jQuery & Snap.svg -> circle not following mouse correctly

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>

Change cursor origin dynamically with javascript?

Is there a way to access the cursor via javascript so that you can define the x, y position of the image?
I have a custom data-uri set for the cursor in css, and I want to be able to adjust the offset dynamically without having to redefine the whole thing.
body{
cursor: url('data:image/svg+xml;utf8,<svg fill="none" stroke="black" stroke-width="1" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg"><path d="M 0 0 H 6 L 12 12 V 18"/></svg>') 24 24, auto;
}
How would I set just the x, y via javascript dynamically??
In case you do want to have a look at pointer locking, here's a jQuery-free implementation.
var getPointerLock = () => {
if (document.body.mozRequestPointerLock) {
document.body.mozRequestPointerLock();
} else if (document.body.requestPointerLock) {
document.body.requestPointerLock();
}
};
var x = 0;
var y = 0;
document.body.onclick = (evt) => {
getPointerLock();
x = evt.clientX;
y = evt.clientY;
};
document.body.onmousemove = (evt) => {
if (document.pointerLockElement || document.mozPointerLockElement) {
x += evt.movementX;
y += evt.movementY;
document.body.innerHTML = `${x}, ${y}`;
}
};
html {
height: 100%;
}
body {
height: 100%;
}

How can I make my cursor synced with the control point during a resize?

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>

spawn & drag of SVG elements - approach

I am on my learning curve for Javascript/SVG combo (animating and making interactive SVGs).
I wanted to create a code snippet where menu elements ("inventory") can be dragged over to the main screen ("canvas") while the originating element would remain in its place (as if one would move a copy of it off the original element).
Here I crafted the code snippet as best as I could:
http://codepen.io/cmer41k/pen/f2b5eea274cdde29b0b2dc8a2424a645
So I sort of managed to do something but its buggy:
I could deal with 1 copy and making it draggable, but then I don't know how to deal with IDs for all those spawning elements, which causes dragging issues
I fail to understand how to make it work indefinitely (so that it can spawn any amount of circles draggable to canvas)
Draggable elements in canvas often overlap and I fail to attach the listeners the way they don't overlap so that the listener on the element I am dragging would propagate "through" whatever other elements there;(
Question is basically - can someone suggest logic that I should put into this snippet so that it was not as cumbersome. I am pretty sure I am missing something here;( (e.g. it should not be that hard is it not?)
HTML:
<body>
<svg id="svg"
height="800"
width="480"
viewbox="0 0 480 800"
preserveAspectRatio="xMinYMin meet"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<rect id="canvasBackground" width="480" height="480" x="0" y="0"/>
<rect id="inventoryBackground" width="480" height="100" x="0" y="480"/>
<g id="inventory">
<path id="curve4" class="inventory" d="M60,530 A35,35 0 1,1 60,531" />
<path id="curve3" class="inventory" d="M150,530 A35,35 0 1,1 150,531" />
<path id="curve2" class="inventory" d="M240,530 A35,35 0 1,1 240,531" />
<path id="curve1" class="inventory" d="M330,530 A35,35 0 1,1 330,531" />
</g>
<g id="canvas">
</g>
</svg>
</body>
Javascript:
// define meta objects
var drag = null;
// this stores all "curves"-circles
var curves = {};
var canvas = {};
var inventory = {};
window.onload = function() {
// creates the curve-circles in the object and at their initial x,y coords
curves.curve1 = document.getElementById("curve1");
curves.curve1.x = 0;
curves.curve1.y = 0;
curves.curve2 = document.getElementById("curve2");
curves.curve2.x = 0;
curves.curve2.y = 0;
curves.curve3 = document.getElementById("curve3");
curves.curve3.x = 0;
curves.curve3.y = 0;
curves.curve4 = document.getElementById("curve4");
curves.curve4.x = 0;
curves.curve4.y = 0;
canvas = document.getElementById("canvas");
inventory = document.getElementById("inventory");
// attach events listeners
AttachListeners();
}
function AttachListeners() {
var ttt = document.getElementsByClassName('inventory'), i;
for (i = 0; i < ttt.length; i++) {
document.getElementsByClassName("inventory")[i].onmousedown=Drag;
document.getElementsByClassName("inventory")[i].onmousemove=Drag;
document.getElementsByClassName("inventory")[i].onmouseup=Drag;
}
}
// Drag function that needs to be modified;//
function Drag(e) {
e.stopPropagation();
var t = e.target, id = t.id, et = e.type; m = MousePos(e);
if (!drag && (et == "mousedown")) {
if (t.className.baseVal=="inventory") { //if its inventory class item, this should get cloned into draggable?
copy = t.cloneNode(true);
copy.onmousedown=copy.onmousemove=onmouseup=Drag;
inventory.insertBefore(copy, inventory.firstChild);
drag = t;
dPoint = m;
}
if (t.className.baseVal=="draggable") { //if its just draggable class - it can be dragged around
drag = t;
dPoint = m;
}
}
// drag the spawned/copied draggable element now
if (drag && (et == "mousemove")) {
curves[id].x += m.x - dPoint.x;
curves[id].y += m.y - dPoint.y;
dPoint = m;
curves[id].setAttribute("transform", "translate(" +curves[id].x+","+curves[id].y+")");
}
// stop drag
if (drag && (et == "mouseup")) {
t.className.baseVal="draggable";
drag = null;
}
}
// adjust mouse position to the matrix of SVG & screen size
function MousePos(event) {
var p = svg.createSVGPoint();
p.x = event.clientX;
p.y = event.clientY;
var matrix = svg.getScreenCTM();
p = p.matrixTransform(matrix.inverse());
return {
x: p.x,
y: p.y
}
}
You were close. You had a couple of bugs. Eg.
copy.onmousedown=copy.onmousemove=onmouseup=Drag;
should have been:
copy.onmousedown=copy.onmousemove=copy.onmouseup=Drag;
And drag = t should have been drag = copy (?)
Also you were appending the clones to the inventory section, when I think you intended to append them to the "canvas" section.
But there were also also some less-obvious mistakes that were contributing to the unreliableness. For example, if you attach the mousemove and mouseup events to the inventory and clone shapes, then you will won't get the events if you drag too fast. The mouse will get outside the shape, and the events won't be passed to the shapes. The fix is to move those event handlers to the root SVG.
Another change I made was to store the x and y positions in the DOM for the clone as _x and _y. This makes it easier than trying to keep them in a separate array.
Anyway, here's my modified version of your example which works a lot more reliably.
// define meta objects
var drag = null;
var canvas = {};
var inventory = {};
window.onload = function() {
canvas = document.getElementById("canvas");
inventory = document.getElementById("inventory");
// attach events listeners
AttachListeners();
}
function AttachListeners() {
var ttt = document.getElementsByClassName('inventory'), i;
for (i = 0; i < ttt.length; i++) {
document.getElementsByClassName("inventory")[i].onmousedown=Drag;
}
document.getElementById("svg").onmousemove=Drag;
document.getElementById("svg").onmouseup=Drag;
}
// Drag function that needs to be modified;//
function Drag(e) {
var t = e.target, id = t.id, et = e.type; m = MousePos(e);
if (!drag && (et == "mousedown")) {
if (t.className.baseVal=="inventory") { //if its inventory class item, this should get cloned into draggable?
copy = t.cloneNode(true);
copy.onmousedown = Drag;
copy.removeAttribute("id");
copy._x = 0;
copy._y = 0;
canvas.appendChild(copy);
drag = copy;
dPoint = m;
}
else if (t.className.baseVal=="draggable") { //if its just draggable class - it can be dragged around
drag = t;
dPoint = m;
}
}
// drag the spawned/copied draggable element now
if (drag && (et == "mousemove")) {
drag._x += m.x - dPoint.x;
drag._y += m.y - dPoint.y;
dPoint = m;
drag.setAttribute("transform", "translate(" +drag._x+","+drag._y+")");
}
// stop drag
if (drag && (et == "mouseup")) {
drag.className.baseVal="draggable";
drag = null;
}
}
// adjust mouse position to the matrix of SVG & screen size
function MousePos(event) {
var p = svg.createSVGPoint();
p.x = event.clientX;
p.y = event.clientY;
var matrix = svg.getScreenCTM();
p = p.matrixTransform(matrix.inverse());
return {
x: p.x,
y: p.y
}
}
/* SVG styles */
path
{
stroke-width: 4;
stroke: #000;
stroke-linecap: round;
}
path.fill
{
fill: #3ff;
}
html, body {
margin: 0;
padding: 0;
border: 0;
overflow:hidden;
background-color: #fff;
}
body {
-ms-touch-action: none;
}
#canvasBackground {
fill: lightgrey;
}
#inventoryBackground {
fill: grey;
}
.inventory {
fill: red;
}
.draggable {
fill: green;
}
svg {
position: fixed;
top:0%;
left:0%;
width:100%;
height:100%;
}
<svg id="svg"
height="800"
width="480"
viewbox="0 0 480 800"
preserveAspectRatio="xMinYMin meet"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<rect id="canvasBackground" width="480" height="480" x="0" y="0"/>
<rect id="inventoryBackground" width="480" height="100" x="0" y="480"/>
<g id="inventory">
<path id="curve4" class="inventory" d="M60,530 A35,35 0 1,1 60,531" />
<path id="curve3" class="inventory" d="M150,530 A35,35 0 1,1 150,531" />
<path id="curve2" class="inventory" d="M240,530 A35,35 0 1,1 240,531" />
<path id="curve1" class="inventory" d="M330,530 A35,35 0 1,1 330,531" />
</g>
<g id="canvas">
</g>
</svg>

How to select child elements after using groupSVGElements

I am loading an svg into Fabric which has child elements. I'm grouping them using groupSVGElements().
I need to be able select each child element - that is an onclick event that allows me to select a child object.
I've thrown together this fiddle http://jsfiddle.net/AnQW5/2/
Although I can list out the child objects of the group via getObjects, theres no way that I can see to determine which object was clicked. So :
canvas.observe('object:selected', function(e) {
console.log(e.target.getObjects());
// ???
});
Any ideas?
You can use
canvas.add.apply instead groupSVGElements, so you can :
have each element to manipulate
var shapesSvg = '<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg version="1.1" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="320" height="240" viewBox="0 0 320 240"><rect id="rect" x="5" y="50" width="100" height="100" style="fill: rgb(255,0,0);"></rect> <circle id="circle" cx="165" cy="100" r="50" style="fill: rgb(0,255,0);"/></svg>';
var canvas = new fabric.Canvas('canvas');
canvas.setHeight($(window).height());
canvas.setWidth($(window).width());
fabric.loadSVGFromString(shapesSvg, function(objects, options) {
canvas.add.apply(canvas, objects);
canvas.renderAll();
});
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js"></script>
<canvas id="canvas"></canvas>
Description
The following code detects any object on the screen by it's color, not the best thought.
Code
var $canvas = {
activePaths: []
};
// Bind to `onMouseDown` event.
function onMouseDown = function (options) {
var mousePos = canvas.getPointer(options.e);
mousePos.x = parseInt(mousePos.x);
mousePos.y = parseInt(mousePos.y);
var width = canvas.getWidth();
var height = canvas.getHeight();
var pixels = canvas.getContext().getImageData(0, 0, width, height);
var pixel = (mousePos.x + mousePos.y * pixels.width) * 4;
var activePathsColor = new fabric['Color']('rgb(' + pixels.data[pixel] + ',' + pixels.data[pixel + 1] + ',' + pixels.data[pixel + 2] + ')');
var colorHex = '#' + activePathsColor.toHex().toLowerCase();
var activeObject = canvas.getActiveObject();
var activePath = [];
// Check if active objects are type of `group`, if so push the selected path.
if(activeObject.type == 'group') {
for (var i = 0; i < activeObject.getObjects().length; i++) {
var path = activeObject.getObjects()[i];
if (path.getFill().toLowerCase() == colorHex) {
$canvas.activePaths.push(path);
}
}
}
}
References
Events: http://fabricjs.com/events/

Categories

Resources