I'm trying to create a image cropper, that's based on a image inside a container box. The user should be able to drag or move the image and pinch to zoom.
Currently, I've been working in the drag functionality, but I find that's not working properly. When going from Top left and drag to Bottom right works more or less ok, but if I do try to drag from left to right or the inverse, the image keeps bouncing back to the initial drag position.
My code is really basic, where you can see that I just add to x or y the deltaY/X, which I seem would be more then enough ?
var x = 0;
var y = 0;
var imgEl = document.querySelectorAll("img")[0];
Hammer(element).on("dragstart", function(event) {
imgEl = document.querySelectorAll("img")[0];
y = parseFloat(imgEl.style.top) || 0;
x = parseFloat(imgEl.style.top) || 0;
});
Hammer(element).on("drag", function(event) {
imgEl.style.top = (y + parseFloat(event.gesture.deltaY))+'px';
imgEl.style.left = (x + parseFloat(event.gesture.deltaX))+'px';
});
I've got a demo setup here: http://jsbin.com/kimes/4
When testing I've been noticing that, when I drag from Bottom right to Top Left works fine!
Thanks for looking!
You need to add new mouse position to the dragging variable.
Oh, sorry about taking you guys time, I had a typo:
y = parseFloat(imgEl.style.top) || 0;
x = parseFloat(imgEl.style.top) || 0;
Should be:
y = parseFloat(imgEl.style.top) || 0;
x = parseFloat(imgEl.style.left) || 0;
Related
I’m fairly new to web development and I’ve only ever used jQuery to write my scripts. Today however, I’d like to improve my skills and build a little game that could be used on a smartphone as a web app in vanilla JS.
The game’s pretty straightforward:
You hold your phone in portrait mode and control a character that stays at the bottom of the screen and has to dodge objects that are falling on him. The character can only move left or right and thus always stays on the same x-axis. In order to control him, your finger has to stay on the screen. Once you take it off, you lose. Also, the move isn’t triggered by tapping the screen, but by moving your finger left or right.
For now, I’ve only been experimenting to get the hang of touchevents and was able to make the character move when swiping:
document.addEventListener('touchmove',function(e){
e.preventDefault(); //disable scroll
var board = document.getElementById(‘board);
var character = document.getElementById(‘character’);
if (e.targetTouches.length === 1) {
var touch = e.targetTouches[0];
board.classList.add(‘moving’);
character.style.left = touch.pageX + 'px';
}
}, false);
(The ‘moving’ class is used to move the background-position of the board and animate the character’s sprite in plain CSS.)
Separately, I made a little script that puts objects with random classes in a container with a set interval. These objects are then animated in css and fall from the top to the bottom of the screen.
Now, here comes the tricky part: the collision detection.
As I said, I’m new to development and vanilla JS, so I searched a bit to figure out how to detect when two objects collide, and it seems that most tutorials do this using canvases. The thing is, I’ve never used them and they scare me quite a bit. What’s more, I think it would render what I’ve done so far useless.
I’m okay with trying the canvas way, but before I do, I’d like to know if there’s any other way to detect if two moving objects collide?
Also, if there turns out to be no real way to do this without canvas, I plan on using this tutorial to learn how to build the app. However, this game wasn’t built for touchscreen devices, and the spaceship’s position changes on certain keystrokes (left & right) :
function update() {
if (keydown.left) {
player.x -= 5;
}
if (keydown.right) {
player.x += 5;
}
player.x = player.x.clamp(0, CANVAS_WIDTH - player.width);
}
My question is: how should I do to update the position using touchmove instead of keystrokes?
Thank you all in advance.
1) the idea : 'if you stop touching, you loose', is just a bad idea, drop it.
2) most convenient way to control is to handle any touch event (touch start/move/end/cancel), and to have the character align on the x coordinate of this event.
3) the intersection test is just a basic boundig box intersection check.
I made a very basic demo here, that uses touch, but also mouse to ease testing :
http://jsbin.com/depo/1/edit?js,output
a lot of optimisations are possible here, but you will see that touches adjust the ship's position, and that collisions are detected, so it will hopefully lead you to your own solution
Edit : i added default to 0 for left, top, in case they were not set.
boilerplate code :
var collisionDisplay = document.getElementById('collisionDisplay');
// hero ship
var ship = document.getElementById('ship');
ship.onload = launchWhenReady ;
// bad ship
var shipBad = document.getElementById('shipBad');
shipBad.onload = launchWhenReady ;
// image loader
imagesCount = 2 ;
function launchWhenReady() {
imagesCount --;
if (imagesCount) return;
setInterval(animate, 20);
}
var shipBadY = 0;
touch events :
// listen any touch event
document.addEventListener('touchstart', handleTouchEvent, true);
document.addEventListener('touchmove', handleTouchEvent, true);
document.addEventListener('touchend', handleTouchEvent, true);
document.addEventListener('touchcancel', handleTouchEvent, true);
// will adjust ship's x to latest touch
function handleTouchEvent(e) {
if (e.touches.length === 0 ) return;
e.preventDefault();
e.stopPropagation();
var touch = e.touches[0];
ship.style.left = (touch.pageX - ship.width / 2) + 'px';
}
animation :
// animation loop
function animate() {
// move ship
shipBadY += 1;
shipBad.style.top = Math.ceil(shipBadY) + 'px';
// test collision
var isColliding = testCollide(shipBad);
collisionDisplay.style.display = isColliding ? 'block' : 'none';
}
collision :
// collision test when the enemy and the ship are images
function testCollide(enemi) {
var shipPosX = parseInt(ship.style.left) || 0 ;
var shipPosY = parseInt(ship.style.top) || 0 ;
var shipWidth = ship.width ;
var shipHeight = ship.height;
var badX = parseInt(enemi.style.left) || 0 ;
var badY = parseInt(enemi.style.top) || 0 ;
var badWidth = enemi.width;
var badHeight = enemi.height;
return bBoxIntersect(shipPosX, shipPosY, shipWidth, shipHeight,
badX, badY, badWidth, badHeight);
}
EDIT : in case you're not using images :
// collision test when the enemy and the ship are ** NOT ** images
function testCollide(o) {
var characterPosX = parseInt(character.style.left);
var characterPosY = parseInt(character.style.top);
var characterWidth = parseInt(character.style.width);
var characterHeight = parseInt(character.style.height);
var obstacleX = parseInt(o.style.left) || 0 ;
var obstacleY = parseInt(o.style.top) || 0 ;
var obstacleWidth = parseInt(o.style.width);
var obstacleHeight = parseInt(o.style.height);
return boundingBoxIntersect(characterPosX, characterPosY, characterWidth, characterHeight, obstacleX, obstacleY, obstacleWidth, obstacleHeight);
}
function bBoxIntersect(x1, y1, w1, h1, x2, y2, w2, h2) {
return !(x1 + w1 < x2 || x1 > x2 + w2 || y1 + h1 < y2 || y1 > y2 + w2);
}
mouse events :
// -----------------------------------------------------
// Handle mouse event for easy testing on Browser
document.addEventListener('mousemove', handleMouseEvent);
function handleMouseEvent(e) {
ship.style.left = (e.pageX - ship.width / 2) + 'px';
}
If for instance, we set a -vendor-transform: rotate(40deg) css attribute on a rectangle <div>, all the sudden dragging and resizing becomes very weird and flawed.
Here is an example with a simple jQueryUI: http://jsfiddle.net/Ja4dY/1/
You will notice, that if you drag or resize that rectangle when transformed, it will jump up or down and the cursor will not remain in the correct place. In my real code I'm using custom code for resizing and dragging, however I encountered the same problems.
Well, of course the "problem" is that the direction of an Element will change. So left can be right, top gets bottom and something inbetween and the Javascript code still handles each direction as it would be not transformed.
So, the question: How can we compensate transformed / rotated Elements ?
Any good resources / books / blogs are also very welcome.
You can get the current transformation matrix that is applied to an element by using getComputedStyle(). You can use this to transform the current mouse position to its position in transformed space and see whether the click/drag events are within the element boundary and/or corners. Good resources for this:
http://www.useragentman.com/blog/2011/01/07/css3-matrix-transform-for-the-mathematically-challenged/
http://www.eleqtriq.com/2010/05/css-3d-matrix-transformations/
BTW, as you're experiencing, this is non-trivial to code. We had to do it for Sencha Animator, and it was a beast.
The problem is that functions that make elements draggable, wether using jQuery UI or not, relies heavily on the native getBoundingClientRect() function to figure out the position of the element etc.
When applying CSS3 transforms, like rotation, the values of getBoundingClientRect() or the equalent jQuery offset() function used in jQuery UI no longer works as expected, and the position of the mouse pointer gets messed up because the size of the element is suddenly wrong after it has been rotated.
To fix it you need to add some sort of helper function that recalculates the values, and there is a monkey patch available for this that works with jQuery UI's draggable.
It's hard to say anything about how to make the same patch work for custom code, but you'll probably have to integrate it in your custom function somehow, and it will take some coding on your part, and it's even harder to come up with something that works as a helper function out of the box for custom code one has not seen, and be aware that it is rather involved doing these calculations, see the code below :
function monkeyPatch_mouseStart() {
var oldFn = $.ui.draggable.prototype._mouseStart ;
$.ui.draggable.prototype._mouseStart = function(event) {
var o = this.options;
function getViewOffset(node) {
var x = 0, y = 0, win = node.ownerDocument.defaultView || window;
if (node) addOffset(node);
return { left: x, top: y };
function getStyle(node) {
return node.currentStyle || // IE
win.getComputedStyle(node, '');
}
function addOffset(node) {
var p = node.offsetParent, style, X, Y;
x += parseInt(node.offsetLeft, 10) || 0;
y += parseInt(node.offsetTop, 10) || 0;
if (p) {
x -= parseInt(p.scrollLeft, 10) || 0;
y -= parseInt(p.scrollTop, 10) || 0;
if (p.nodeType == 1) {
var parentStyle = getStyle(p)
, localName = p.localName
, parent = node.parentNode;
if (parentStyle.position != 'static') {
x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
y += parseInt(parentStyle.borderTopWidth, 10) || 0;
if (localName == 'TABLE') {
x += parseInt(parentStyle.paddingLeft, 10) || 0;
y += parseInt(parentStyle.paddingTop, 10) || 0;
}
else if (localName == 'BODY') {
style = getStyle(node);
x += parseInt(style.marginLeft, 10) || 0;
y += parseInt(style.marginTop, 10) || 0;
}
}
else if (localName == 'BODY') {
x += parseInt(parentStyle.borderLeftWidth, 10) || 0;
y += parseInt(parentStyle.borderTopWidth, 10) || 0;
}
while (p != parent) {
x -= parseInt(parent.scrollLeft, 10) || 0;
y -= parseInt(parent.scrollTop, 10) || 0;
parent = parent.parentNode;
}
addOffset(p);
}
}
else {
if (node.localName == 'BODY') {
style = getStyle(node);
x += parseInt(style.borderLeftWidth, 10) || 0;
y += parseInt(style.borderTopWidth, 10) || 0;
var htmlStyle = getStyle(node.parentNode);
x -= parseInt(htmlStyle.paddingLeft, 10) || 0;
y -= parseInt(htmlStyle.paddingTop, 10) || 0;
}
if ((X = node.scrollLeft)) x += parseInt(X, 10) || 0;
if ((Y = node.scrollTop)) y += parseInt(Y, 10) || 0;
}
}
}
this.helper = this._createHelper(event);
this._cacheHelperProportions();
if($.ui.ddmanager)
$.ui.ddmanager.current = this;
this._cacheMargins();
this.cssPosition = this.helper.css("position");
this.scrollParent = this.helper.scrollParent();
this.offset = this.positionAbs = getViewOffset(this.element[0]);
this.offset = {
top: this.offset.top - this.margins.top,
left: this.offset.left - this.margins.left
};
$.extend(this.offset, {
click: {
left: event.pageX - this.offset.left,
top: event.pageY - this.offset.top
},
parent: this._getParentOffset(),
relative: this._getRelativeOffset()
});
this.originalPosition = this.position = this._generatePosition(event);
this.originalPageX = event.pageX;
this.originalPageY = event.pageY;
(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
if(o.containment)
this._setContainment();
if(this._trigger("start", event) === false) {
this._clear();
return false;
}
this._cacheHelperProportions();
if ($.ui.ddmanager && !o.dropBehaviour)
$.ui.ddmanager.prepareOffsets(this, event);
this.helper.addClass("ui-draggable-dragging");
this._mouseDrag(event, true);
if ( $.ui.ddmanager ) $.ui.ddmanager.dragStart(this, event);
return true;
};
}
monkeyPatch_mouseStart();
And here's a FIDDLE showing it working as expected with jQuery UI's draggable and resizeable !
I found this... It's a working example plus info, demo and download link.
jquery-ui-rotation-using-css-transform -> live-demo
He use his own libraries, but if you are interest in the subject, you can read and learn how he get it.
cheers and good luck.
Gmo.-
Btw, the web is in Russian, but with google translate you can manage ;-)
It is not bug in jQuery. Simply it is not supported. If you check jQuery UI source code you will figure out that it doesn't use transformation matrix to calculate difference between transformed object and page.
Your example, and probably every jQ UI drag implementation suffer from this issue cause of 2 methods in JQ UI source code (around 314 line of jquery.ui.draggable.js file v1.8.23 ). Calculated offset do not matter about change in offset since rotation is done over center of element.
You have to calculate what is that change. Here is workaround, quick and dirty. The idea is to check what is difference in bounding box of transformed element.
Check sample here http://jsfiddle.net/mjaric/9Nqrh/
Ignore part with first two rotations, they are just done to minimize lines of code. Third involves translation of coordinate system for calculated difference. It will offset left and top after translation is performed (note it is first in filter).
If you want to avoid first two rotation filters, You could make code using formula for 2D rotation:
x' = x cos f - y sin f
y' = y cos f + x sin f
where f is angle of rotation, but it's not that simple and also includes more lines of code where you have to calculate what is diagonal angle of original bounding box since you need initial angle of top left corner which x and y coords are comparing to x axis (positive part). Then calculate change in x-x' and y-y'. But I'm predicting some issues with sign of change and coding/debugging would take more time then I have right now. Sorry cause of that but I'm sure you can figure out what to do after reading this post.
It looks better if we override the cursorAt:
$("#foo").mousedown(function (e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
console.log(x);
$("#foo").draggable("option", "cursorAt", {left: x, top:y});
});
Updated fiddle: http://jsfiddle.net/johnkoer/Ja4dY/8/
You said you are not interested with JQuery solutions then,
One solution is;
I recommend you to write your own drag and resize functions. You can
handle resizing and draging on rotated objects to add their top and left with sine and cosine of that degree.
Another solution is;
You can use libraries like Raphael JS to create objects to transform,
drag and resize. Raphael JS uses svg!
For more information about Raphael JS
Yet another solution is;
If you do not want to use library like Raphael JS, you can directly use SVG with JQuery
For more information about SVG
Cannot write more details now, I expand this solutions tomorrow.
Hope these help for now.
This, indeed, seems to be a bug in jQuery. An easy workaround would be: surround the resizable div with a container div. Set the .draggable() to the outer div and .resizable() to the inner div. This seems to be working fine in Chromium running on Ubuntu. See Fiddle.
I've colored the outer div to give you an idea what's happening under the hood.
I've been trying to make a javascript to get a X and Y coordinates of a div element. After some trying around I have come up with some numbers but I'm not sure how to validate the exact location of them(the script returns the X as 168 and Y as 258) I'm running the script with a screen resolution of 1280 x 800. This is the script I use to get this result:
function get_x(div) {
var getY;
var element = document.getElementById("" + div).offsetHeight;
var get_center_screen = screen.width / 2;
document.getElementById("span_x").innerHTML = element;
return getX;
}
function get_y(div) {
var getY;
var element = document.getElementById("" + div).offsetWidth;
var get_center_screen = screen.height / 2;
document.getElementById("span_y").innerHTML = element;
return getY;
}
Now the question is. Would it be reasonable to assume that these are accurate coordinates returned by the function or is there an easy to to just spawn a little something on that location to see what exactly it is?
And finally how would I go about making this div element move? I know I should use a mousedown event handler and a while to keep moving the element but yeah any tips/hints are greatly appreciated my biggest concern is to how to get that while loop running.
By far, the easiest way to get the absolute screen position of an element is getBoundingClientRect.
var element = document.getElementById('some-id');
var position = element.getBoundingClientRect();
var x = position.left;
var y = position.top;
// Et voilà!
Keep in mind, though, that the coordinates don’t include the document scroll offset.
Here a simple way to get various information regarding the position of a html element:
var my_div = document.getElementById('my_div_id');
var box = { left: 0, top: 0 };
try {
box = my_div.getBoundingClientRect();
}
catch(e)
{}
var doc = document,
docElem = doc.documentElement,
body = document.body,
win = window,
clientTop = docElem.clientTop || body.clientTop || 0,
clientLeft = docElem.clientLeft || body.clientLeft || 0,
scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop,
scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
top = box.top + scrollTop - clientTop,
left = box.left + scrollLeft - clientLeft;
You need to find the position using the parent's position too. There's a very good tutorial here: http://www.quirksmode.org/js/findpos.html
I think you could use jQuery .offset() http://api.jquery.com/offset/
Given the element...
<div id="abc" style="position:absolute; top:350px; left:190px;">Some text</div>
If the element is in the main document you can get the DIV's coordinates with...
var X=window.getComputedStyle(abc,null).getPropertyValue('left');
var Y=window.getComputedStyle(abc,null).getPropertyValue('top');
If the element is in an iframe you can get the DIV's coordinates with...
var X=FrameID.contentWindow.getComputedStyle(abc,null).getPropertyValue('left');
var Y=FrameID.contentWindow.getComputedStyle(abc,null).getPropertyValue('top');
NB: The returned values should be in the format "190px" and "350px".
I need to change my mouse cursor to a custom image.
If possible I would like to do it on a spritesheet.
I can't do it from the css because I'm using it in a game. I already know how to decide when etc.
What I need to know is how do I change the cursor to an image, and deciding the image position and size?
Is there any easy solution similar to the drawImage's image position?
You can set the CSS using javascript to hide the cursor:
your_canvas.style.cursor = "none"
You can then get the cursor's position (it's now hidden) with something like this:
your_canvas.addEventListener("mousemove", function (ev) {
var mouseX = ev.pageX - GetTopLeft(your_canvas).Left;
var mouseY = ev.pageX - GetTopLeft(your_canvas).Top;
});
Then you can modify your canvas to show your fancier cursor sprite at that location.
GetTopLeft is defined as follows:
function GetTopLeft(elm){
var x, y = 0;
//set x to elm’s offsetLeft
x = elm.offsetLeft;
//set y to elm’s offsetTop
y = elm.offsetTop;
//set elm to its offsetParent
elm = elm.offsetParent;
//use while loop to check if elm is null
// if not then add current elm’s offsetLeft to x
//offsetTop to y and set elm to its offsetParent
while(elm != null)
{
x = parseInt(x) + parseInt(elm.offsetLeft);
y = parseInt(y) + parseInt(elm.offsetTop);
elm = elm.offsetParent;
}
//here is interesting thing
//it return Object with two properties
//Top and Left
return {Top:y, Left: x};
}
Though I can't remember where I copied the GetTopLeft function from...
If you are using a canvas, just hide the cursor over the canvas and draw your own sprite on the canvas at mouse position.
I come to you with an intersting question.
Given position (x,y) in an HTML document, how can you trigger a focus event on an element at that given position.
The problem translates into is there any way to select the element matching a given position?
Sort of like getElementByPosition?
The easiest option is to use elementFromPoint:
var element = document.elementFromPoint(x, y);
element.focus();
Other than that, you can write your own function. This is what first came to mind - I used it a while back when there was some reason for elementFromPoint not working correctly, I don't remember what exactly. There are likely to be better ways to do it, but I just tried what I thought of first:
var coords = [100, 100],
elems = document.getElementsByTagName("*");
for(var i = 0; i < elems.length; i++) {
var left = elems[i].offsetLeft,
top = elems[i].offsetTop,
width = elems[i].offsetWidth;
height = elems[i].offsetHeight;
if((left <= coords[0]) && (left + width >= coords[0]) && (top <=coords[1]) && (top + height >= coords[1])) {
elems[i].focus();
}
}
You can see it working here.
you can use elementFromPoint(x, y)
https://developer.mozilla.org/en/DOM/document.elementFromPoint