When I use drag() on a scaled (zoomed) object, the object moves according to the scale, so that if for example, the scale is set to 3 -- then each 1px move of the mouse is multiplied by 3.
After 20 or so pixel moves by the mouse the behavior is completely unacceptable.
Is that a bug or am I doing something wrong?
var g = s.g();
g.transform("scale(3)");
var rect = g.rect(20,20,40,40);
var circle = g.circle(60,150,50);
var move = function(dx,dy) {
this.attr({
transform: this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [dx, dy]
});
}
var start = function() {
this.data('origTransform', this.transform().local );
}
var stop = function() {
console.log('finished dragging');
}
rect.drag(move, start, stop );
circle.drag(move, start, stop );
See fiddle at http://jsfiddle.net/mje8knLf/1/ (just drag one of the shapes)
TIA!
To do this, we need to account for existing transformations that appear on all the outer elements.
Here is a plugin I did a while back to help with this.
The main bit is the dragMove function. We find the existing other transforms that are applied (there are 3 types of transform matrix in Snap on an element, localMatrix, diffMatrix, globalMatix), and invert it to get the matrix that we will want to apply to allow for the existing effect. (If there are some cases where diffMatrix doesn't work, take a look at globalMatrix).
Then using that, we can use y() and x() on the new drag amount, to find what it would be in the new coordinate space.
Snap.plugin( function( Snap, Element, Paper, global ) {
Element.prototype.altDrag = function() {
this.drag( dragMove, dragStart, dragEnd );
return this;
}
var dragStart = function ( x,y,ev ) {
this.data('ot', this.transform().local );
}
var dragMove = function(dx, dy, ev, x, y) {
var tdx, tdy;
var snapInvMatrix = this.transform().diffMatrix.invert();
snapInvMatrix.e = snapInvMatrix.f = 0;
tdx = snapInvMatrix.x( dx,dy ); tdy = snapInvMatrix.y( dx,dy );
this.transform( "t" + [ tdx, tdy ] + this.data('ot') );
}
var dragEnd = function() {
}
});
Try: transform: this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [dx/3, dy/3].
Basic implementation:-
Scale is a global variable (can be a FLOAT also)
All dx and dy motion is divided by Scale
sidenote: all elements can be dragged out of the box. You might want to implement some movement limit around the edges.
Here I am using svg-pan-zoom.js for the pan and zoom for SVG, very easy to use.
And I am using snap.svg
var svgElement;
var panZoomPlan;
function setPanZoom(){
svgElement = document.querySelector('svg');
panZoomPlan = svgPanZoom(svgElement);
panZoomPlan.setMinZoom(0.1);
panZoomPlan.setMaxZoom(50);
panZoomPlan.setZoomScaleSensitivity(0.2);
panZoomPlan.disableDblClickZoom();
}
function set_draggable(svg_element) {
var shape = plan.select("#" + svg_element);
shape.drag(dragNode, dragStart, dragStop);
}
var dragStart = function() {
panZoomPlan.disablePan();
panZoomPlan.disableZoom();
this.isDragged = false;
var node_id = this.node.id;
this.data('origTransform', this.transform().local );
}
var dragNode = function(dx,dy) {
this.isDragged = true;
realZoom = panZoomPlan.getSizes().realZoom;
var rdx = dx/realZoom;
var rdy = dy/realZoom;
this.attr({
transform: this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [rdx, rdy]
});
event.preventDefault();
}
var dragStop = function() {
panZoomPlan.enablePan();
panZoomPlan.enableZoom();
if (!this.isDragged) {
return;
}
//update scene elements data
var node_id = this.node.id;
Nodes_dict[node_id].x += Nodes_dict[node_id].rdx;
Nodes_dict[node_id].y += Nodes_dict[node_id].rdy;
}
I hope this helps later users who are seeing this question.
Related
First off i'm new to javascript and still learning its basics, i'm trying to determine in which box i clicked(canvas).
My boxes are a list of dictionaries that look like this if we use console.log to visualize them, let's call that list labels:
[
{"id":"1","image":"1-0.png","name":"","xMax":"4802","xMin":"4770","yMax":"156","yMin":"141"},
{"id":"2","image":"1-0.png","name":"","xMax":"4895","xMin":"4810","yMax":"157","yMin":"141"},
{"id":"3","image":"1-0.png","name":"","xMax":"4923","xMin":"4903","yMax":"156","yMin":"145"},
{"id":"4","image":"1-0.png","name":"","xMax":"4956","xMin":"4931","yMax":"156","yMin":"145"}
]
Here we can see we have 4 rectangles and their coordinates.
The function i used to get mouse clicks is :
canvas.addEventListener("contextmenu", getPosition, false);
function getPosition(event) {
event.preventDefault();
var x = event.x;
var y = event.y;
var canvas = document.getElementById("canvas");
x -= canvas.offsetLeft;
y -= canvas.offsetTop;
console.log("x:" + x + " y:" + y);
}
The part where i'm struggling is to find out if where i clicked are inside any of the boxes and if the click is inside one i want the id.
What i tried:
I tried adding this after the console.log in the previous code snippet:
for (i = 0,i < labels.length; i++) {
if (x>labels[i].xMin) and (x<labels[i].xMax) and (y>labels[i].yMin) and (y<labels[i].yMax) {
log.console(labels[i].id)
}
}
but it didn't work
The rects in Labels are all very far to the right, so probably you need to add the scroll position to the mouse position.
Made a working example (open the console to see the result):
https://jsfiddle.net/dgw0sxu5/
<html>
<head>
<style>
body{
background: #000;
margin: 0
}
</style>
<script>
//Some object which is used to return an id from a click
//Added a fillStyle property for testing purposes
var Labels = [
{"id":"0","image":"1-0.png","name":"","xMax":"4956","xMin":"0","yMax":"50","yMin":"0","fillStyle":"pink"},
{"id":"1","image":"1-0.png","name":"","xMax":"4802","xMin":"4770","yMax":"156","yMin":"141","fillStyle":"red"},
{"id":"2","image":"1-0.png","name":"","xMax":"4895","xMin":"4810","yMax":"157","yMin":"141","fillStyle":"blue"},
{"id":"3","image":"1-0.png","name":"","xMax":"4923","xMin":"4903","yMax":"156","yMin":"145","fillStyle":"limegreen"},
{"id":"4","image":"1-0.png","name":"","xMax":"4956","xMin":"4931","yMax":"156","yMin":"145","fillStyle":"aqua"}
];
//Initialisiing for the testcase
window.onload = function(){
//The canvas used for click events
var tCanvas = document.body.appendChild(document.createElement('canvas'));
tCanvas.width = 4956; //Highest xMax value from labels
tCanvas.height = 157; //Highest yMax value from labels
//The graphical object
var tCTX = tCanvas.getContext('2d');
//Drawing the background
tCTX.fillStyle = '#fff';
tCTX.fillRect(0, 0, tCanvas.width, tCanvas.height);
//Drawing the rects for testing purposes
//The rectangles are kinda far on the right side
for(var i=0, j=Labels.length; i<j; i++){
tCTX.fillStyle = Labels[i].fillStyle;
tCTX.fillRect(+(Labels[i].xMin), +(Labels[i].yMin), +(Labels[i].xMax)-+(Labels[i].xMin), +(Labels[i].yMax)-+(Labels[i].yMin));
};
tCanvas.onclick = function(event){
var tX = event.clientX - this.offsetLeft + (document.body.scrollLeft || document.documentElement.scrollLeft), //X-Position of click in canvas
tY = event.clientY - this.offsetTop + (document.body.scrollTop || document.documentElement.scrollTop), //Y-Position of click in canvas
tR = []; //All found id at that position (can be more in theory)
//Finding the Labels fitting the click to their bounds
for(var i=0, j=Labels.length; i<j; i++){
if(tX >= +(Labels[i].xMin) && tX <= +(Labels[i].xMax) && tY >= +(Labels[i].yMin) && +(tY) <= +(Labels[i].yMax)){
tR.push(Labels[i].id)
}
};
console.log(
'Following ids found at the position #x. #y.: '
.replace('#x.', tX)
.replace('#y.', tY),
tR.join(', ')
)
}
}
</script>
</head>
<body></body>
</html>
First of all: what exactly did not work?
var canvas = document.getElementById("canvas"); should be outside of your function to save performance at a second call.
And getting coordinates is not complicated, but complex.
There is a great resource on how to get the right ones: http://javascript.info/coordinates
Be sure about the offset measured relative to the parent element (offsetParent and also your offsetLeft), document upper left (pageX) or viewport upper left (clientX).
Your logic seems to be correct, you just need to fix the syntax.
for (i = 0; i < labels.length; i++) {
if ((x>labels[i].xMin) && (x<labels[i].xMax) && (y>labels[i].yMin) && (y<labels[i].yMax)) {
console.log(labels[i].id)
}
}
Here is a complete example:
labels = [
{"id":"1","image":"1-0.png","name":"","xMax":"4802","xMin":"4770","yMax":"156","yMin":"141"},
{"id":"2","image":"1-0.png","name":"","xMax":"4895","xMin":"4810","yMax":"157","yMin":"141"},
{"id":"3","image":"1-0.png","name":"","xMax":"4923","xMin":"4903","yMax":"156","yMin":"145"},
{"id":"4","image":"1-0.png","name":"","xMax":"4956","xMin":"4931","yMax":"156","yMin":"145"}
]
var canvas = document.getElementById("canvas");
canvas.addEventListener("contextmenu", getPosition, false);
function getPosition(event) {
event.preventDefault();
var x = event.clientX;
var y = event.clientY;
var label = labels.find(function(label){
return (x>label.xMin) && (x<label.xMax) && (y>label.yMin) && (y<label.yMax)
});
if(label){
console.log("clicked label", label.id);
}else{
console.log("no label was clicked");
}
}
I created a turn-based javascript game.For moving the players I use jQuery. I'm moving a sprite inside my game board which consists of 100 squares with board-0 ids to board-99. I'm moving my sprite from id to id. Everything works perfectly, but I can not prohibit certain id's to travel. All the boxes with a class "accesOn" are ok all those with the class "accesOff" should not be used. I managed to block the upward movement, if the top position to a "accesOff" class at the start of the game. The problem is that after my sprite never goes up again. Could you help me to make sure not to move on all the divs that have an "accesOff" class?
Thanks
/*********RECUP. ID POS. TOP before move******** */
var nombreIdPos = maDivId.substr(6);
var posActuelleNombreId = parseInt(nombreIdPos);
var posDessusNombreId = posActuelleNombreId -= 10;
var $posDessusId = 'board-' + posDessusNombreId;
//******** MOVE
function main() {
var $nombreId = maDivId.substr(6);
var $posDiv1 = parseInt($nombreId);
var $currentPosId = maDivId; //TEST voir si besoin plus tard
var $largeur = ($('#contenu').width());
var $hauteur = ($('#contenu').height());
$(window).on('keydown', function (e) {
var touche = e.which;
switch (touche) {
case 38: //TOP
var $idDivDessus = $('#' + $posDessusId);
if ($idDivDessus.hasClass('accesOn')) { // this if is not good
$posX = '0%';
$posY = '100%';
var $newPosH = $posDiv1 -= 10;
var $newPos = $("#board-" + $newPosH);
$($newPos).css('background', $player1 + $posX + $posY);
$('.joueur1').css('background', "").removeClass('joueur1');
$($newPos).addClass('joueur1');
} //****FIN IF
} //FIN if 1er
break;
}); // FIN Event keydown
} //FIN main function
$(document).ready(main);
You only care about the tile you are moving to, you don't have to check for access on the tile you are already on (assume you handled this correctly before arriving on the current tile).
Add a check for the tile you want to move to, if it has the class accessOff simply avoid moving, no other actions should be required.
example check for //TOP
$posX = '0%';
$posY = '66%';
$newPos = $posDiv1 -= 10;
$newPos = $("#board-" + $newPos);
if(!$newPos.hasClass('accessOff')){ //check here for accessOff
$($newPos).css('background', $player1 + $posX + $posY);
$('.joueur1').css('background', "").removeClass('joueur1');
$($newPos).addClass('joueur1');
console.log('Variable $newPosR = ' + $newPos);
} else {
$posDiv1 -= change;
}
I would recommend creating a function to move your player to get rid of some code duplication. It will make your life easier in the future.
Something like this:
function MovePlayer(x, y, change) {
$newPos = $posDiv1 += change;
$newPos = $("#board-" + $newPos);
if(!$newPos.hasClass('accessOff')){
$($newPos).css('background', $player1 + x + y);
$('.joueur1').css('background', "").removeClass('joueur1');
$($newPos).addClass('joueur1');
console.log('Variable $newPosR = ' + $newPos);
} else {
$posDiv1 -= change;
}
}
Your //TOP would then just be MovePlayer('0%', '66%', -10), and you can reuse that function to move any direction.
I'm having trouble creating a click event for my Javascript canvas game. So far I have been following a tutorial, however the way you interact with the game is through mouse hover. I would like to change it so that instead of hovering over objects in the canvas to interact, I instead use a mouse click.
The following is the code I use to detect the mouse hover.
getDistanceBetweenEntity = function (entity1,entity2) //return distance
{
var vx = entity1.x - entity2.x;
var vy = entity1.y - entity2.y;
return Math.sqrt(vx*vx+vy*vy);
}
testCollisionEntity = function (entity1,entity2) //return if colliding
{
var distance = getDistanceBetweenEntity(entity1,entity2);
return distance < 50;
}
I then use this in a loop to interact with it.
var isColliding = testCollisionEntity(player,nounList[key]);
if(isColliding)
{
delete nounList[key];
player.score = player.score + 10;
}
Below is a complete copy of my game at its current state.
<canvas id="ctx" width="500" height="500" style="border:1px solid #000000;"></canvas>
<script>
var ctx = document.getElementById("ctx").getContext("2d");
ctx.font = '30px Arial';
//Setting the height of my canvas
var HEIGHT = 500;
var WIDTH = 500;
//Player class
var player =
{
x:50,
spdX:30,
y:40,
spdY:5,
name:'P',
score:0,
};
//Creating arrays
var nounList ={};
var adjectivesList ={};
var verbsList ={};
getDistanceBetweenEntity = function (entity1,entity2) //return distance
{
var vx = entity1.x - entity2.x;
var vy = entity1.y - entity2.y;
return Math.sqrt(vx*vx+vy*vy);
}
testCollisionEntity = function (entity1,entity2) //return if colliding
{
var distance = getDistanceBetweenEntity(entity1,entity2);
return distance < 50;
}
Nouns = function (id,x,y,name)
{
var noun =
{
x:x,
y:y,
name:name,
id:id,
};
nounList[id] = noun;
}
Adjectives = function (id,x,y,name)
{
var adjective =
{
x:x,
y:y,
name:name,
id:id,
};
adjectivesList[id] = adjective;
}
Verbs = function (id,x,y,name)
{
var verb =
{
x:x,
y:y,
name:name,
id:id,
};
verbsList[id] = verb;
}
document.onmousemove = function(mouse)
{
var mouseX = mouse.clientX;
var mouseY = mouse.clientY;
player.x = mouseX;
player.y = mouseY;
}
updateEntity = function (something)
{
updateEntityPosition(something);
drawEntity(something);
}
updateEntityPosition = function(something)
{
}
drawEntity = function(something)
{
ctx.fillText(something.name,something.x,something.y);
}
update = function ()
{
ctx.clearRect(0,0,WIDTH,HEIGHT);
drawEntity(player);
ctx.fillText("Score: " + player.score,0,30);
for(var key in nounList)
{
updateEntity(nounList[key]);
var isColliding = testCollisionEntity(player,nounList[key]);
if(isColliding)
{
delete nounList[key];
player.score = player.score + 10;
}
}
for(var key in adjectivesList)
{
updateEntity(adjectivesList[key])
var isColliding = testCollisionEntity(player,adjectivesList[key]);
if(isColliding)
{
delete adjectivesList[key];
player.score = player.score - 1;
}
}
for(var key in verbsList)
{
updateEntity(verbsList[key])
var isColliding = testCollisionEntity(player,verbsList[key]);
if(isColliding)
{
delete verbsList[key];
player.score = player.score - 1;
}
}
if(player.score >= 46)
{
ctx.clearRect(0,0,WIDTH,HEIGHT);
ctx.fillText("Congratulations! You win!",50,250);
ctx.fillText("Refresh the page to play again.",50,300);
}
}
Nouns('N1',150,350,'Tea');
Nouns('N2',400,450,'Park');
Nouns('N3',250,150,'Knee');
Nouns('N4',50,450,'Wall');
Nouns('N5',410,50,'Hand');
Adjectives('A1',50,100,'Broken');
Adjectives('A2',410,300,'Noisy');
Verbs('V1',50,250,'Smell');
Verbs('V2',410,200,'Walk');
setInterval(update,40);
To summarize all I want to do is change it so that instead of mousing over words to delete them you have to click.
(Apologies for not using correct terminology in places, my programming knowledge is quite limited.)
You can have your canvas listen for mouse clicks on itself like this:
// get a reference to the canvas element
var canvas=document.getElementById('ctx');
// tell canvas to listen for clicks and call "handleMouseClick"
canvas.onclick=handleMouseClick;
In the click handler, you'll need to know the position of your canvas relative to the viewport. That's because the browser always reports mouse coordinates relative to the viewport. You can get the canvas position relative to the viewport like this:
// get the bounding box of the canvas
var BB=canvas.getBoundingClientRect();
// get the left X position of the canvas relative to the viewport
var BBoffsetX=BB.left;
// get the top Y position of the canvas relative to the viewport
var BBoffsetY=BB.top;
So your mouseClickHandler might look like this:
// this function will be called when the user clicks
// the mouse in the canvas
function handleMouseClick(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get the canvas postion relative to the viewport
var BB=canvas.getBoundingClientRect();
var BBoffsetX=BB.left;
var BBoffsetY=BB.top;
// calculate the mouse position
var mouseX=e.clientX-BBoffsetX;
var mouseY=e.clientY-BBoffsetY;
// report the mouse position using the h4
$position.innerHTML='Click at '+parseInt(mouseX)+' / '+parseInt(mouseY);
}
If your game doesn't let the window scroll or resize then the canvas postion won't change relative to the viewport. Then, for better performance, you can move the 3 lines relating to getting the canvas position relative to the viewport to the top of your app.
// If the browser window won't be scrolled or resized then
// get the canvas postion relative to the viewport
// once at the top of your app
var BB=canvas.getBoundingClientRect();
var BBoffsetX=BB.left;
var BBoffsetY=BB.top;
function handleMouseClick(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// calculate the mouse position
var mouseX=e.clientX-BBoffsetX;
var mouseY=e.clientY-BBoffsetY;
// report the mouse position using the h4
$position.innerHTML='Click at '+parseInt(mouseX)+' / '+parseInt(mouseY);
}
When we create multiple sprites, the function mouseover is called when any hover in hitArea polygon. Regardless, whether applied to another object.
Visibility of sprite governed by sorting the array. The later was added to the sprite in stage.children, the higher it will be. Here is an example in which one rectangle superimposed on the other. At the same time, when we put things on the upper left corner of the bottom sprite, at the top of the object function mouseover will work call, although it is under the other.
How to solve this problem? hitarea not suitable, since the facilities will be constantly dragging.
Thanks in advance for your reply!
var stage = new PIXI.Stage(0x97c56e, true);
var renderer = PIXI.autoDetectRenderer(window.innerWidth, window.innerHeight, null);
document.body.appendChild(renderer.view);
renderer.view.style.position = "absolute";
renderer.view.style.top = "0px";
renderer.view.style.left = "0px";
requestAnimFrame( animate );
var texture = new PIXI.RenderTexture()
r1 = new PIXI.Graphics()
r1.beginFill(0xFFFF00);
r1.drawRect(0, 0, 400, 400)
r1.endFill()
texture.render(r1);
var texture2 = new PIXI.RenderTexture()
r1 = new PIXI.Graphics()
r1.beginFill(0xDDDD00);
r1.drawRect(0, 0, 300, 300)
r1.endFill()
texture2.render(r1);
createBunny(100, 100, texture)
createBunny(120, 120, texture2)
function createBunny(x, y, texture) {
var bunny = new PIXI.Sprite(texture);
bunny.interactive = true;
bunny.buttonMode = true;
bunny.anchor.x = 0.5;
bunny.anchor.y = 0.5;
bunny.scale.x = bunny.scale.y = 0.5;
bunny.mouseover = function(data) {
console.log('mouse over!')
}
bunny.mousedown = bunny.touchstart = function(data) {
this.data = data;
this.alpha = 0.9;
this.dragging = true;
this.sx = this.data.getLocalPosition(bunny).x * bunny.scale.x;
this.sy = this.data.getLocalPosition(bunny).y * bunny.scale.y;
};
bunny.mouseup = bunny.mouseupoutside = bunny.touchend = bunny.touchendoutside = function(data) {
this.alpha = 1
this.dragging = false;
this.data = null;
};
bunny.mousemove = bunny.touchmove = function(data) {
if(this.dragging) {
var newPosition = this.data.getLocalPosition(this.parent);
this.position.x = newPosition.x - this.sx;
this.position.y = newPosition.y - this.sy;
}
}
bunny.position.x = x;
bunny.position.y = y;
stage.addChild(bunny);
}
function animate() {
requestAnimFrame( animate );
renderer.render(stage);
}
http://jsfiddle.net/sD8Tt/48/
I know this is old thread, but for those who come to this page, the issue can be fixed by extending the PIXI's Sprite Class, Look at the following code.
PIXI.Sprite.prototype.bringToFront = function() {
if (this.parent) {
var parent = this.parent;
parent.removeChild(this);
parent.addChild(this);
}
}
And call above method in mousedown event like this
this.bringToFront();
Working JSFiddle
http://jsfiddle.net/6rk58cqa/
I've made elements draggable on a straight path and fixed to an axis before using jQuery UI, like so:
$('#element').draggable({
axis:'y'
});
However, I'm wondering how to make an element draggable while following a certain path that is essentially a draggable line.
I basically have one style / position of the element in the beginning and another style / position of its end location and I want it to be draggable along the path between these two positions.
How can I do this with javascript or jQuery?
I don't think jQuery provides that kind of functionality. However, you can do it in pure JavaScript, or use this plugin I made:
$.fn.dragBetween = function(eles) {
var dragEle = this,
md = false, offset, a, b, ab;
eles[0] = $(eles[0])[0];
eles[1] = $(eles[1])[0];
dragEle.on("mousedown", function(e) {
if (e.which == 1) {
mD = true;
offset = new Vector(
e.clientX - this.offsetLeft,
e.clientY - this.offsetTop
);
a = new Vector(eles[0].offsetLeft, eles[0].offsetTop);
b = new Vector(eles[1].offsetLeft, eles[1].offsetTop);
ab = b.sub(a);
}
});
$(window).on("mousemove", function(e) {
if (!mD) return false;
var cursor = new Vector(e.clientX, e.clientY).sub(offset).sub(a),
mag = cursor.dot(ab) / ab.dot(ab),
proj = ab.times(Math.max(0, Math.min(1, mag)));
var final = proj.add(a);
dragEle.css({
top: final._arr[1],
left: final._arr[0]
});
e.preventDefault();
}).mouseup(function() {
mD = false;
});
};
And this is the Vector class I wrote in a hurry:
function Vector(x, y) {
this._arr = [x, y];
}
Vector.prototype.dot = function(v2) {
var v1 = this;
return v1._arr[0] * v2._arr[0] + v1._arr[1] * v2._arr[1];
}
Vector.prototype.add = function(v2) {
var v1 = this;
return new Vector(v1._arr[0] + v2._arr[0], v1._arr[1] + v2._arr[1]);
}
Vector.prototype.sub = function(v2) {
var v1 = this;
return new Vector(v1._arr[0] - v2._arr[0], v1._arr[1] - v2._arr[1]);
}
Vector.prototype.times = function(n) {
var v = this;
return new Vector(v._arr[0] * n, v._arr[1] * n);
}
To call it, simply do:
$("#drag").dragBetween(["#a", "#b"]);
Demo: http://jsfiddle.net/DerekL/0j6e60d8/
For formula explanation, see: https://www.desmos.com/calculator/tpegbbk8gt
I wasn't able to use Derek's answer because you lose the other features jquery ui draggable provides like containment.
I found to make it drag diagonally, you can modify the css using the drag event like this:
$(".leftTopCircle").draggable({ axis:"x", containment: ".topLeft", scroll: false });
$(".leftTopCircle").on( "drag", function(event, ui){
$(this).css("top", ui.position.left+"px");
});