Change cursor origin dynamically with javascript? - 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%;
}

Related

How to test if a point is inside an SVG closed path

I'm trying to put together a drag & drop interface which allows a user to drag a div on a page which is constrained to the inside of an irregular closed SVG path.
Here's an example - the orange square is my draggable element, the gray SVG path is what I want to constrain it to on drop:
<div class="drag-parent">
<svg xmlns="http://www.w3.org/2000/svg" width="141.019" height="74.065" viewBox="0 0 141.019 74.065">
<defs>
<style>
.target {
fill: #333;
}
</style>
</defs>
<path id="Path_4569" data-name="Path 4569" class="target" d="M0,0H141.018V74.065h-24.27V37.033H10.88V12.971H0Z"/>
</svg>
<div class="draggable" style="width:20px;height:20px;background:orange;cursor:pointer;"></div>
</div>
What I'd like to do is check the draggable div as it's dragged, to make sure it's completely inside the closed path of my SVG.
I'm using GSAP Draggable to take care of actually dragging the element, but I'm stumped on how to test if it's inside that path or not.
So far I've tried isPointInFill however this seems to return true in chrome nomatter what I give it.
I've also tried using mouseenter / mouseleave events on the path which is a great starting point; but when you're dragging something those events don't fire since the mouse pointer is "ontop" of the dragged item rather than the SVG path.
What would be a good way to enforce bounds of an SVG path - or, is there a much simpler way to enforce irregular bounds on dragged items?
The main idea is to check if all 4 corners of the orange div are over the path. Maybe I've oversimplified the things since the drag_parent has `margin 0; padding:0;``
I hope this is what you were asking.
let D = false,// if D ids true you can drag
m = {},// the mouse position
thePath = document.querySelector("#Path_4569"),
draggable = document.querySelector("#draggable");
draggable.w = draggable.getBoundingClientRect().width;
draggable.h = draggable.getBoundingClientRect().height;
draggable.p0s = [[], [], [], []];//one array for every corner
draggable.delta = {};// distance between the click point and the left upper corner
draggable.addEventListener("mousedown", e => {
D = true;
draggable.delta = oMousePos(draggable, e);
});
drag_parent.addEventListener("mousemove", e => {
if (D == true) {
let counter = 0;// how many corners are in path
m = oMousePos(drag_parent, e);
draggablePoints(m);
draggable.style.left = draggable.p0s[0][0] + 1 + "px";
draggable.style.top = draggable.p0s[0][1] + 1 + "px";
draggable.p0s.map(p => {
if (document.elementFromPoint(p[0], p[1]) && document.elementFromPoint(p[0], p[1]).id == "Path_4569") {
counter++;
}
});
if (counter == 4) {// if all 4 corners are in path
thePath.setAttributeNS(null, "fill", "#777");
} else {
thePath.setAttributeNS(null, "fill", "black");
}
}
});
drag_parent.addEventListener("mouseup", e => {
D = false;
});
drag_parent.addEventListener("mouseleave", e => {
D = false;
});
function oMousePos(elmt, evt) {
var ClientRect = elmt.getBoundingClientRect();
return {
//objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
};
}
function draggablePoints(m) {
//top left
draggable.p0s[0][0] = m.x - draggable.delta.x - 1;
draggable.p0s[0][1] = m.y - draggable.delta.y - 1;
//top right
draggable.p0s[1][0] = m.x - draggable.delta.x + draggable.w + 1;
draggable.p0s[1][1] = m.y - draggable.delta.y + 1;
//bottom right
draggable.p0s[2][0] = m.x - draggable.delta.x + draggable.w + 1;
draggable.p0s[2][1] = m.y - draggable.delta.y + draggable.h + 1;
//bottom left
draggable.p0s[3][0] = m.x - draggable.delta.x + 1;
draggable.p0s[3][1] = m.y - draggable.delta.y + draggable.h + 1;
}
*{margin:0;padding:0}
svg {
outline: 1px solid;
}
#drag_parent {
outline: 1px solid;
min-height: 100vh;
position:relative;
}
#draggable {
position: absolute;
width: 20px;
height: 20px;
background: orange;
cursor: pointer;
}
<div id="drag_parent">
<svg xmlns="http://www.w3.org/2000/svg" width="141.019" height="74.065" viewBox="0 0 141.019 74.065">
<path id="Path_4569" data-name="Path 4569" class="target" d="M0,0H141.018V74.065h-24.27V37.033H10.88V12.971H0Z"/>
</svg>
<div id="draggable"></div>
</div>

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>

Place div on svg track

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>

SVG point animation - works great in Chrome, but nothing else

For a project, I need to create a set of masked images connected to eachother by a single point on each image, and the position of that point is animated randomly (within a restricted range) on scroll.
I have managed to get this working well in Chrome, however Firefox is very buggy and Safari doesn't like the mask at all, let alone the animation. Don't even get me started on Edge/IE11. Any way around this?
Codepen
HTML
<div class="c-nodes-bg c-nodes-bg--set-of-3">
<img id="nodes-bg-3i-1" width="100%" src="http://via.placeholder.com/1500x2800" style="clip-path: url("#clipPolygon");" class="moving">
<img id="nodes-bg-3i-2" width="100%" src="http://via.placeholder.com/1500x2800" style="clip-path: url("#clipPolygon2");" class="moving">
<img id="nodes-bg-3i-3" width="100%" src="http://via.placeholder.com/1500x2800" style="clip-path: url("#clipPolygon3");" class="moving">
</div>
<svg width="0" height="0" >
<clipPath id="clipPolygon" clipPathUnits="objectBoundingBox">
<polygon id="poly1" points="0.3 0.35,1 0.28,1 0, 0.6 0">
<animate id="poly1Anim" attributeName="points" dur="600ms" to="" fill="freeze" />
</polygon>
</clipPath>
<clipPath id="clipPolygon2" clipPathUnits="objectBoundingBox">
<polygon id="poly2" points="0.0 0.26, 0.3 0.35, 0.6 0.7, 0.0 0.70">
<animate id="poly2Anim" attributeName="points" dur="600ms" to="" fill="freeze" />
</polygon>
</clipPath>
<clipPath id="clipPolygon3" clipPathUnits="objectBoundingBox">
<polygon id="poly3" points="0.3 1, 1 1, 1 0.7, 0.6 0.7">
<animate id="poly3Anim" attributeName="points" dur="600ms" to="" fill="freeze" />
</polygon>
</clipPath>
</svg>
CSS
.c-nodes-bg {
position: absolute;
top: 0px;
right: 0;
left: 0;
z-index: 10;
/**
Since the background images are positioned absolute as they sit behind site content,
add in a spacer to force the height of the site to be the same as the images
*/
}
.c-nodes-bg--contact #map-canvas {
width: 100%;
height: 50vw;
}
.c-nodes-bg__spacer {
width: 100%;
height: calc(100% - 22px);
display: block;
}
.c-nodes-bg--set-of-3 img {
display: block;
width: 100%;
}
.c-nodes-bg--set-of-3 img:nth-child(2), .c-nodes-bg--set-of-3 img:nth-child(3) {
position: absolute;
top: 0;
left: 0;
}
--webkit-clip-path: url("#clipPolygon");
clip-path: url("#clipPolygon");
--webkit-clip-path: url("#clipPolygon2");
clip-path: url("#clipPolygon2");
--webkit-clip-path: url("#clipPolygon3");
clip-path: url("#clipPolygon3");
JS
var animPoints = {
init: function() {
var isScrolling;
var scrollingDone = false;
// Init backgrounds on scroll, but wait for a period of time after scroll starts before init
window.addEventListener('scroll', function ( event ) {
window.clearTimeout( isScrolling );
isScrolling = setTimeout(function() {
scrollingDone = true;
}, 300);
// Only start the animation if the previous one has finished
if(scrollingDone === true){
animPoints.randomiseBackgrounds();
scrollingDone = false;
}
}, false);
},
randomiseBackgrounds: function(){
var poly1 = document.getElementById("poly1"); // Points element within SVG
var poly1Points = poly1.getAttribute("points"); // Points attribute containing position values
var poly1PointsArr = poly1Points.split(","); // Convert points into array for ease of manipulation
var poly1PointPair1Arr = poly1PointsArr[0].split(" "); // We only need the first point's coordinates
var poly1Anim = document.getElementById("poly1Anim"); // Get the animation element within the SVG
var poly2 = document.getElementById("poly2"); // Points element within SVG
var poly2Points = poly2.getAttribute("points"); // Points attribute containing position values
var poly2PointsArr = poly2Points.split(" "); // We only need the second point's coordinates
var poly2Anim = document.getElementById("poly2Anim"); // Get the animation element within the SVG
var poly3 = document.getElementById("poly3"); // Points element within SVG
var poly3Points = poly3.getAttribute("points"); // Points attribute containing position values
var poly3PointsArr = poly3Points.split(" "); // We only need the fourth point's coordinates
var poly3Anim = document.getElementById("poly3Anim"); // Get the animation element within the SVG
// Update X axis for point 1
poly1PointPair1Arr[0] = Math.random() * (0.35 - 0.25) + 0.25;
// ..and also assign that to a point on the second poly so they are connected
poly2PointsArr[2] = poly1PointPair1Arr[0];
poly2PointsArr[4] = Math.random() * (0.63 - 0.56) + 0.56;
// ..and assign point 4 (X axis) on polygon 3 to be equal to point 3 on polygon 2 so they are connected also
poly3PointsArr[6] = poly2PointsArr[4];
// Update Y axis and also assign that to a point on the second poly so they are connected
poly1PointPair1Arr[1] = Math.random() * (0.36 - 0.32) + 0.32;
// ..and also assign that to a point on the second poly so they are connected
poly2PointsArr[3] = poly1PointPair1Arr[1];
poly2PointsArr[5] = Math.random() * (0.73 - 0.66) + 0.66;
// ..and assign point 4 (Y axis) on polygon 3 to be equal to point 3 on polygon 2 so they are connected also
poly3PointsArr[7] = poly2PointsArr[5];
// Throw the updated coordinates back into their respective attribute arrays
poly1PointsArr[0] = poly1PointPair1Arr.join();
// Convert modified array of points back to string, and update var
poly1Points = poly1PointsArr.join();
poly2Points = poly2PointsArr.join();
poly3Points = poly3PointsArr.join();
// Update the 'to' attribute with the new values
poly1Anim.setAttribute('to', poly1PointsArr);
poly2Anim.setAttribute('to', poly2Points);
poly3Anim.setAttribute('to', poly3Points);
// Play the animation!
poly1Anim.beginElement();
poly2Anim.beginElement();
poly3Anim.beginElement();
resetPoints = setTimeout(function() {
poly1.setAttribute('points', poly1PointsArr);
poly2.setAttribute('points', poly2Points);
poly3.setAttribute('points', poly3Points);
}, 600);
},
};
animPoints.init();
var scrollTimer = -1;

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>

Categories

Resources