Snap to parent grid on start of drag - javascript

I'm fairly new to JavaScript & JQuery, so apologies if I'm missing a trick or two.
I've figured out how to get JQuery UI draggable objects to use the grid option, and, once the page has loaded, "snap to an imaginary grid" which all draggable objects have reference to (explained in code comments). However, I can't figure out how to get this behaviour to occur .on("dragstart").
HTML:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<script type="text/javascript" src="jquery-1.10.2.js"></script>
<script type="text/javascript" src="jquery-ui.js"></script>
<script type="text/javascript" src="dragger.js"></script>
</head>
<body>
<div id="parent">
<svg width="300" height="100" class="draggable" id="number1">
<rect x="0" y="0" rx="10" ry="10" width="300" height="100" style="fill:rgb(121,0,121);stroke-width:3;stroke:rgb(0,0,0);">
</svg>
<svg width="300" height="100" class="draggable" id="letterA">
<rect x="0" y="0" rx="100" ry="10" width="300" height="100" style="fill:rgb(0,121,121);stroke-width:3;stroke:rgb(255,0,0);">
</svg>
</div>
</body>
</html>
Note: There is a white gap between the two rectangles, via the second JavaScript below, this disappears once the block has been snapped to the grid. Alternatively, the 2 rectangles should be draggable onto each other and line up flush against one another to be considered snapped onto the grid.
JavaScript (dragger.js):
var roundedRemainder = function(numer, denom) {
return numer - (Math.round(numer / denom) * denom);
}
var snapPosition = function(obj, granularity) {
obj.position({
my: "left top", // Unchanging reference point on draggable object
at: "left top", // Unchanging reference point on reference object
of: "#parent", // The object that you want to move items with respect to.
using: function(position, data) {
var newPositions = {
// Get the difference between the "imaginary grid" and the current grid
left: function() {
return roundedRemainder(position.left, granularity);
},
top: function() {
return roundedRemainder(position.top, granularity);
}
}
// Move to the imaginary grid
$(this).css(newPositions);
return newPositions;
}
});
}
$(function() {
var gridSize = 50;
$(".draggable")
// Enable grid usage
.draggable({
grid: [gridSize, gridSize]
})
.on("dragstart", function(event, ui) {
var newPos = snapPosition(ui.helper, gridSize);
})
});
Proof the code in snapPosition works:
var roundedRemainder = function(numer, denom) {
return numer - (Math.round(numer / denom) * denom);
}
$(function() {
var gridSize = 50;
$(".draggable")
// Enable grid usage
.draggable({
grid: [gridSize, gridSize]
})
.position({
my: "left top", // Unchanging reference point on draggable object
at: "left top", // Unchanging reference point on reference object
of: "#parent", // The object that you want to move items with respect to.
using: function(position, data) {
var newPositions = {
// Get the difference between the "imaginary grid" and the current grid
left: function() {
return roundedRemainder(position.left, granularity);
},
top: function() {
return roundedRemainder(position.top, granularity);
}
}
// Move to the imaginary grid
$(this).css(newPositions);
}
})
});
The first JavaScript is trying to change the position of the block once dragging starts, to snap it to the imaginary grid. The second does this automatically upon loading of the page, but never again. If I were to change the imaginary grids granularity from 50 to 79, for instance, dragging would not bring the objects back onto the grid as desired.
Is there somewhere I could look to learn how to do this? Is it doable?
To clarify:
JQuery = 1.10.2 (Same as in JQuery UI demo's)
JQuery UI = 1.11.4 (Same as in JQuery UI demo's)
Browser = Firefox on Ubuntu 14.04, everything up to date
I've already been through Google, but either terms akin to "start", "drag" and "position" aren't unique enough to narrow things down, or I haven't found the right place. I've also scoured through the JQuery (UI) archives.
Many thanks in advance!

Okay, so turns out there were a few examples of ignorance on my behalf. I'll go through them below to help others as well, but if you're just after a solution, then look no further:
var roundedRemainder = function(numer, denom) {
if (denom > 1) // Only calculate when there is a chance the draggable isn't on the grid.
return numer - (Math.round(numer / denom) * denom); // Note: denom = 0 is invalid.
else
return 0; // If denom is invalid, or 1, then assume the draggable is on the grid.
}
$(function() {
var gridSize = 79;
var _this = this;
$(".draggable")
.draggable({
// Enable grid constraints
grid: [gridSize, gridSize],
// At start of dragging (aka, only do once at the beginning)
// snap the draggable object onto its parents grid.
drag: function(event, ui) {
var gridOffsetLeft;
var gridOffsetTop;
ui.helper.position({
my: "left top", // For the top left of the draggable object
at: "left top", // Link to the top left of the reference object
of: $(this).parent(), // Make the reference object the parent of the draggable object
// Calculate the grid offset from where the object ORIGINATED from
using: function(position, data) {
gridOffsetLeft = roundedRemainder(position.left, gridSize);
gridOffsetTop = roundedRemainder(position.top, gridSize);
}
});
// Calculate the total offset based off of the current
// location of the draggable object, and the previously
// calculated offset.
gridOffsetLeft -= roundedRemainder(ui.position.left, gridSize);
gridOffsetTop -= roundedRemainder(ui.position.top, gridSize);
// Apply offsets & hence snap the draggable onto the
// parents grid.
ui.position.left += gridOffsetLeft;
ui.position.top += gridOffsetTop;
}
})
});
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<script type="text/javascript" src="jquery-1.10.2.js"></script>
<script type="text/javascript" src="jquery-ui.js"></script>
<script type="text/javascript" src="dragger.js"></script>
</head>
<body>
<div id="parent">
<svg width="300" height="100" class="draggable" id="number1">
<rect x="0" y="0" rx="10" ry="10" width="300" height="100" style="fill:rgb(121,0,121);stroke-width:3;stroke:rgb(0,0,0);">
</svg>
<svg width="300" height="100" class="draggable" id="letterA">
<rect x="0" y="0" rx="100" ry="10" width="300" height="100" style="fill:rgb(0,121,121);stroke-width:3;stroke:rgb(255,0,0);">
</svg>
</div>
</body>
</html>
Bugs:
The first bug was in the way I was trying to separate the positioning function away from the dragging function. Specifically, when trying to send then over. I've not really understood this yet which is why it isn't in the solution, however I did read (and lost the link) a stack overflow which mentioned using var _then = then;, or using a binding method. If I find the link I'll edit the answer.
The next bug relates to my specification of "at start". What I wanted as a method to only snap to the parent grid at the beginning of each drag. Thus, is sounded logical to use the start: or .on("dragstart", ...) functionality, as per the documentation. This lead into a preventDefault issue whereby either the start command was ignored, hence no snapping, but I could drag, or if I used event.preventDefault at the beginning of start, it would snap but no longer drag. Turns out that the drag: functionality only runs at the beginning of the drag once (please correct me if I'm wrong). By putting the snapping function back in there, another bug was solved.
Finally, my original logic for snapping to the grid was faulty. It worked when using it just the once, but as soon as it was run every time the object was dragged, it would "creep" a little bit on every drag. This is because the "snap" functionality worked from the ORIGINAL location of the dragged object to the parent. As such, just adding that difference on every time, even when the object is already snapped, caused an error. To resolve this I simply took the difference from the original to current location of the object, to an identical grid centered on the original location of the object.
(To be clear, there are 3 reference locations, the parent, the original location of the dragged object, and the current location of the dragged object).

Related

How to make SVG Elements draggable [duplicate]

I am trying to follow the html5 drag and drop tutorial here. I could not get the dragstart event to be registered on rect element. If I change the event from draggable to mousedown it calls the handleDragStart handler. Please ignore the additional blank registration in the code.
JSFiddle here
<!DOCTYPE html>
<html><head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<style type="text/css" media="screen">
svg rect { cursor: move; }
</style>
</head><body>
<h1>SVG/HTML 5 Example</h1>
<svg id="cvs">
<rect draggable="true" x="0" y="10" width="100" height="80" fill="#69c" />
<rect x="50" y="50" width="90" height="50" fill="#c66" />
</svg>
<script type="text/javascript" src="loc.js"></script>
</body></html>
loc.js
$(document).ready(function() {
function handleDragStart(e) {
log("handleDragStart");
this.style.opacity = '0.4'; // this ==> e.target is the source node.
};
var registercb = function () {
$("#cvs > rect").each(function() {
$(this).attr('draggable', 'true');
});
$("#cvs > rect").bind('dragstart', handleDragStart);
$(window).mousedown(function (e) {
});
$(window).mousemove(function (e) {
});
$(window).mouseup(function (e) {
log("mouseup");
});
};
function log() {
if (window.console && window.console.log)
window.console.log('[XXX] ' + Array.prototype.join.call(arguments, ' '));
};
registercb();
});
I know this is an old question, I arrived here from this other question that was marked as a duplicate of this one, and wanted to add a possible solution that doesn't require jQuery or any libraries, and that works in all major browsers. It is based on this tutorial recommended by #AmirHossein Mehrvarzi.
This small solution doesn't use the drag events, just the mousedown, mouseup and mousemove. This is how it works:
When the mouse is down on the rectangle, it saves the mouse position and the active element.
When the mouse moves, the rectangle coordinates are updated with the new mouse position.
When the mouse is up, it resets the active element.
From the code in the question above:
var selectedElement = null;
var currentX = 0;
var currentY = 0;
$(document).ready(function() {
function handleDragStart(e) {
log("handleDragStart");
this.style.opacity = '0.4'; // this ==> e.target is the source node.
};
var registercb = function () {
$("#cvs > rect").mousedown(function (e) {
// save the original values when the user clicks on the element
currentX = e.clientX;
currentY = e.clientY;
selectedElement = e.target;
}).mousemove(function (e) {
// if there is an active element, move it around by updating its coordinates
if (selectedElement) {
var dx = parseInt(selectedElement.getAttribute("x")) + e.clientX - currentX;
var dy = parseInt(selectedElement.getAttribute("y")) + e.clientY - currentY;
currentX = e.clientX;
currentY = e.clientY;
selectedElement.setAttribute("x", dx);
selectedElement.setAttribute("y", dy);
}
}).mouseup(function (e) {
// deactivate element when the mouse is up
selectedElement = null;
});
};
function log() {
if (window.console && window.console.log)
window.console.log('[XXX] ' + Array.prototype.join.call(arguments, ' '));
};
registercb();
});
rect { cursor: move; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<h1>SVG/HTML 5 Example</h1>
<svg id="cvs">
<rect x="0" y="10" width="100" height="80" fill="#69c" />
<rect x="50" y="50" width="90" height="50" fill="#c66" />
</svg>
You can also see it on this JSFiddle: http://jsfiddle.net/YNReB/61/
If you want to add drop functionality, you can modify the mouseup function to read the element on the cursor position (with document.elementFromPoint(e.clientX, e.clientY)) and then you can perform actions on the original element and the one where it was dropped.
This behavior may be caused by several reasons:
HTML 5 drag and drop is a mess, according to this article. I know it's a bit older, but the issue still seems not to be solved
jQuery does not support the SVG DOM-model. Therefore some parts of it may work, others don't (like offset() or width()).
I'd definitely not rely on HTML5 drag & drop support right now, but rather use a library to handle this issue. If you want to work with SVG, you could try Raphaël. If you need jQuery too, maybe the SVG plugin is the way to go. Note that both projects are not actively developed at the moment.
I know this is not really a satisfactory answer, but I had to learn too, that jQuery and SVG do not go that well together. Hopefully someone proves me wrong ;).

placement in a stack according to position -help figure out function

I am trying to figure out a case where I have a vertical green line that represents a stack of layers and a red "plank" that represents currently focused layer out of the stack.
I need to make sure when top most layer is selected - the plank is at the top of the green line. When first (deepest) layer is selected the plank is at the bottom of the green line.
Using the function below I can achieve the #1 condition, but whenever I select deepest layer - I can't get red plank placed at the bottom.
I know this should be school level math exercise but I cant figure it out;/
UPDATE: so in the snippet if I will do current Layer 1 and total N/whatever. I cant "land" the plank onto the bottom of the line
UPDATE2: OK I added some more logic to the function and now the result is what I need. PRoblem is I am not sure I achieved this through proper logic. At least I don't really understand what I did and why it now works.
// trying to figure out better function to indicate which is current layer:
// height of the green line is 280.
var currentItemLayer = 3;
var totalLayers = 4;
var plank = document.getElementById("plank");
setPlankPositionAccordingToLayer = function(currentItemLayer, totalLayers) {
var blockCount = Math.max(1, totalLayers - 1);
var blockSize = 280*(1/blockCount);
var layer;
if (blockCount === 1) {
layer = 100;
} else {
layer = 380-(currentItemLayer*blockSize);
}
plank.setAttribute("y", layer);
console.log(layer);
};
setPlankPositionAccordingToLayer(currentItemLayer, totalLayers)
<body>
<svg id="mainSVG"
viewBox="0 0 480 480"
preserveAspectRatio="xMidYMin"
width="100%"
height="100%"
background-color="white"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<line x1="240" y1="100" x2="240" y2="380" style="stroke: green"/>
<rect id="plank" x="230" y="240" width="20" height="2"/>
</svg>
</body>

Fabric.js animate sprites and text field

about month ago i asked how to quee animatations for objects in Fabric.js to which i got response from #nickvans. This is the code. Based on this I changed the code and created with HTML5 drag and drop, aplication which allows you to give to each object on canvas its own set of different commands. Basicly creating moving scene. Mine 1st question is :if it is possible to also use sprites. So instead of the triangle in the exmaple below it would be animated sprite that would also change its position. And 2nd question is it possible to somehow add text field that would follow the object during its movement? Something like those comics bubles.
Thanks in advance for any tips
function startAnimationQueue(animationQueue){
// queues up the animations for the shape
var runningDuration = 0; // variable that adds up the animationQueue durations
for (var i=0; i<animationQueue.length; i++){
var animationDefinition = animationQueue[i];
// Create a closure around the animationDefiniton so that each setTimeout gets sequential animationDefinition inputs
var fn = (function(animationDefinition){
return function(){
triangle.animate('left', animationDefinition.left, {duration: animationDefinition.duration, onChange:canvas.renderAll.bind(canvas)})
triangle.animate('top', animationDefinition.top, {duration: animationDefinition.duration, onChange:canvas.renderAll.bind(canvas)})
// Note: you can animate additional attributes here if you want, just add additional attributes to the objects in
// the animationQueue list. You could also have one of those inputs be the object to be animated in case you want
// to animate multiple objects using the same queue.
};
})
// Create the timeout that will apply the transformations sequentially
// If you want you could set the window.setTimeout to a variable that you could destroy if you need
// to interrupt the animation queue after you create it (but before it finishes)
window.setTimeout(fn(animationDefinition), runningDuration);
// set the next setTimeout duration to be .duration later than the previous one
// this makes the second animation fire after the first one completes
runningDuration += animationDefinition.duration;
}
}
document.onreadystatechange = function () {
if (document.readyState == "complete") {
// I put the canvas init stuff in here because of (I think) a failed race condition or something that caused
// your original method to fail in Chrome
window.canvas = new fabric.Canvas('scene');
window.triangle = new fabric.Triangle({
width: 30
, height: 30
, fill: 'red'
, left: 30
, top: 0
});
window.canvas.add(window.triangle);
window.canvas.renderAll();
// Create a list of animations to apply
var animationQueue = [
{"left": "+=0", "top": "+=100", "duration": 1000},
{"left": "+=55", "top": "+=0", "duration": 2000}
]
// Apply the animations in sequence using window.setTimeout
startAnimationQueue(animationQueue);
}
}
and HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.0/fabric.min.js"></script>
</head>
<body>
<canvas id="scene" width="400" height="400" />
</body>
</html>

How to Drag & Drop SVG elements? [duplicate]

I am trying to follow the html5 drag and drop tutorial here. I could not get the dragstart event to be registered on rect element. If I change the event from draggable to mousedown it calls the handleDragStart handler. Please ignore the additional blank registration in the code.
JSFiddle here
<!DOCTYPE html>
<html><head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<style type="text/css" media="screen">
svg rect { cursor: move; }
</style>
</head><body>
<h1>SVG/HTML 5 Example</h1>
<svg id="cvs">
<rect draggable="true" x="0" y="10" width="100" height="80" fill="#69c" />
<rect x="50" y="50" width="90" height="50" fill="#c66" />
</svg>
<script type="text/javascript" src="loc.js"></script>
</body></html>
loc.js
$(document).ready(function() {
function handleDragStart(e) {
log("handleDragStart");
this.style.opacity = '0.4'; // this ==> e.target is the source node.
};
var registercb = function () {
$("#cvs > rect").each(function() {
$(this).attr('draggable', 'true');
});
$("#cvs > rect").bind('dragstart', handleDragStart);
$(window).mousedown(function (e) {
});
$(window).mousemove(function (e) {
});
$(window).mouseup(function (e) {
log("mouseup");
});
};
function log() {
if (window.console && window.console.log)
window.console.log('[XXX] ' + Array.prototype.join.call(arguments, ' '));
};
registercb();
});
I know this is an old question, I arrived here from this other question that was marked as a duplicate of this one, and wanted to add a possible solution that doesn't require jQuery or any libraries, and that works in all major browsers. It is based on this tutorial recommended by #AmirHossein Mehrvarzi.
This small solution doesn't use the drag events, just the mousedown, mouseup and mousemove. This is how it works:
When the mouse is down on the rectangle, it saves the mouse position and the active element.
When the mouse moves, the rectangle coordinates are updated with the new mouse position.
When the mouse is up, it resets the active element.
From the code in the question above:
var selectedElement = null;
var currentX = 0;
var currentY = 0;
$(document).ready(function() {
function handleDragStart(e) {
log("handleDragStart");
this.style.opacity = '0.4'; // this ==> e.target is the source node.
};
var registercb = function () {
$("#cvs > rect").mousedown(function (e) {
// save the original values when the user clicks on the element
currentX = e.clientX;
currentY = e.clientY;
selectedElement = e.target;
}).mousemove(function (e) {
// if there is an active element, move it around by updating its coordinates
if (selectedElement) {
var dx = parseInt(selectedElement.getAttribute("x")) + e.clientX - currentX;
var dy = parseInt(selectedElement.getAttribute("y")) + e.clientY - currentY;
currentX = e.clientX;
currentY = e.clientY;
selectedElement.setAttribute("x", dx);
selectedElement.setAttribute("y", dy);
}
}).mouseup(function (e) {
// deactivate element when the mouse is up
selectedElement = null;
});
};
function log() {
if (window.console && window.console.log)
window.console.log('[XXX] ' + Array.prototype.join.call(arguments, ' '));
};
registercb();
});
rect { cursor: move; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<h1>SVG/HTML 5 Example</h1>
<svg id="cvs">
<rect x="0" y="10" width="100" height="80" fill="#69c" />
<rect x="50" y="50" width="90" height="50" fill="#c66" />
</svg>
You can also see it on this JSFiddle: http://jsfiddle.net/YNReB/61/
If you want to add drop functionality, you can modify the mouseup function to read the element on the cursor position (with document.elementFromPoint(e.clientX, e.clientY)) and then you can perform actions on the original element and the one where it was dropped.
This behavior may be caused by several reasons:
HTML 5 drag and drop is a mess, according to this article. I know it's a bit older, but the issue still seems not to be solved
jQuery does not support the SVG DOM-model. Therefore some parts of it may work, others don't (like offset() or width()).
I'd definitely not rely on HTML5 drag & drop support right now, but rather use a library to handle this issue. If you want to work with SVG, you could try Raphaël. If you need jQuery too, maybe the SVG plugin is the way to go. Note that both projects are not actively developed at the moment.
I know this is not really a satisfactory answer, but I had to learn too, that jQuery and SVG do not go that well together. Hopefully someone proves me wrong ;).

Javascript Animation Baseball Runners

Thank you in advance for you help. I am hoping someone could provide some solid examples of some Javascript or jQuery animation for running around a baseball diamond rather than starting from scratch.
So far I've found at least 1 think that gets me close however needs much control introduced. I'm looking for tracking live progress so this would be conditional based on the batters progress around the bases. So if the batter hit a double, the animation would go to 2nd base and stop. Eventually I need to add functionality to interact with the circle but that'll be another story.
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Untitled Document</title>
<script>
var context;
var x=100;
var y=200;
var dx=3;
var dy=3;
function init()
{
context= myCanvas.getContext('2d');
setInterval(draw,10);
}
function draw()
{
context.clearRect(0,0, 300,300);
context.beginPath();
context.fillStyle="#0000ff";
// Draws a circle of radius 20 at the coordinates 100,100 on the canvas
context.arc(x,y,20,0,Math.PI*2,true);
context.closePath();
context.fill();
// Boundary Logic
if( x<0 || x>300) dx=-dx;
if( y<0 || y>300) dy=-dy;
x+=dx;
y+=dy;
}
</script>
</head>
<body onLoad="init();">
<canvas id="myCanvas" width="300" height="300" > </canvas>
</body>
</html>
Lots of negative votes and I can see why, however its actually quite straightforward if you use svg and a library, eg Snap.
jsfiddle click to move between bases
Here's a run down of the process....
Firstly load an svg, just plucked one from the internet, and load it..
Snap.load( "https://upload.wikimedia.org/wikipedia/commons/8/89/Baseball_diamond_clean.svg", onSVGLoaded )
We need to create a route, simply log a mouse click to get its x,y which you can use to create a path...
s.click( setRoute );
function setRoute(ev, x,y) {
console.log('xy', ev, x, y); // a click will log coordinates so you can build path route
movePlayer(currentPath++);
if( currentPath > 3) currentPath = 0;
}
Once you've clicked on the points you want to have as the path, add them into the array....
// build our 'virtual path' and player to animate when clicked
function onSVGLoaded( frag ) {
s.append( frag );
s.click( setRoute )
paths = [
"M335,430L448,324", // these were logged from mouse click
"M453,325L337,210",
"M330,210L215,324",
"M215,325L330,436"
];
player = s.circle(335,430,10).attr({ fill: 'red' })
for( var c=0; c<paths.length; c++) {
pathList[c] = s.path(paths[c]).attr({ stroke: 'none' });
}
}
Finally, we can animate the movement...
function movePlayer( currentPath ) {
Snap.animate(0, pathList[currentPath].getTotalLength(), function( val ) {
var pt = pathList[currentPath].getPointAtLength( val );
player.attr({ cx: pt.x, cy: pt.y })
}, 2000)
}
edit: Heh, just seen how old this is, not sure why it popped up now!
Using Html5 Canvas
You can use linear interpolation to navigate your lines and you can use De Casteljau's algorithm to navigate around your Bezier Curve.
Using SVG
Svg Paths have built-in methods that give you the total length of the path: getTotalLength and also the X,Y at a specified length along the path: getPointAtLength. You can use these methods to navigate your lines and curves.

Categories

Resources