Position and Drag An Element With A Transform Applied - javascript

I have a javascript code that works perfectly for dragging object...but when I scaled the body down to 0.5...
transform:scale(0.5);
the position of mouse and the object dragged is not the same. How can I fix this? or is this possible?... thank you.
Heres a fiddle : http://jsfiddle.net/Xcb8d/65/

This was pretty interesting and makes me rethink all the locations I simply used offsetHeight and offsetWidth without knowing that if a transformation was applied to the element, these readonly properties in JavaScript would return incorrect.
The "trick" to this is the clientHeight/offsetHeight will not report their transformed properties correctly. In order to get the correct sizing information from the element you need to call getBoundingClientRect(). You then can calculate the correct pixel information of the scaled element, allowing you then to perform the correct positioning.
Retrieving the bounding rectangle allows you to get the pixel information off the viewport, you then can compare this information to the clientHeight within the browser to determine the scaled offset height, and position.
I modified some of the event wire ups just for testing. Also I added another class to produce a quarter sized object just to prove it works regardless of scale.
CSS:
html,
body {
width:100%;
height:100%;
}
.half-size
{
transform:scale(0.5);
-moz-transform:scale(0.5);
-webkit-transform:scale(0.5);
}
.quarter-size
{
transform:scale(0.25);
-moz-transform:scale(0.25);
-webkit-transform:scale(0.25);
}
#draggable-element {
width:100px;
height:100px;
background-color:#666;
color:white;
padding:10px 12px;
cursor:move;
position:relative; /* important (all position that's not `static`) */
display:block;
}
JavaScript:
var selected = null, // Object of the element to be moved
x_pos = 0, y_pos = 0, // Stores x & y coordinates of the mouse pointer
x_elem = 0, y_elem = 0; // Stores top, left values (edge) of the element
var elem_height = 0;
var elem_width = 0;
// Will be called when user starts dragging an element
function _drag_init(elem) {
// Store the object of the element which needs to be moved
selected = elem;
var boundingRectangle = selected.getBoundingClientRect();
y_elem = (selected.offsetHeight - (boundingRectangle.bottom - boundingRectangle.top)) / 2;
x_elem = (selected.offsetWidth - (boundingRectangle.right - boundingRectangle.left)) / 2
half_elem_height = (boundingRectangle.bottom - boundingRectangle.top) / 2;
half_elem_width = (boundingRectangle.right - boundingRectangle.left) /2;
document.addEventListener('mousemove', _move_elem, false);
document.addEventListener('mouseup', _destroy, false);
};
// Will be called when user dragging an element
function _move_elem(e)
{
x_pos = e.clientX;
y_pos = e.clientY;
selected.style.left = ((x_pos - x_elem) - half_elem_width) + 'px';
selected.style.top = ((y_pos - y_elem) - half_elem_height) + 'px';
}
// Destroy the object when we are done and remove event binds
function _destroy() {
selected = null;
document.removeEventListener('mousemove', _move_elem);
document.removeEventListener('mouseup', _destroy);
}
// Bind the functions...
document.getElementById('draggable-element').onmousedown = function () {
_drag_init(this);
};
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div id="draggable-element" class='half-size'>Drag me!</div>
</body>
</html>
Click below for a live preview
http://jsbin.com/moyadici/1/edit
(I prefer jsBin over jsFiddle for its live updates)
I did modify some of the event wire ups just for my initial testing. Also I broke the transform into a style so I could try other transforms. Tests look correct when rendering the 'quarter-size'. This just proves out it works regardless of scale and you don't need a magic number for your position calculations.

Not a real answer , but too long for a comment and because it is still jumpy, maybe not on first try ...
In Firefox, using pointer-events, you could try to restrain the area that will catch the mouse.
create an inside element right in center wich has the size of the element itself once scaled.
Your fiddle says scale(0.5) a square of 100px, so lets draw a square of 50px right in the middle with a pseudo element.
Set pointer-events to none to the element and reset it back to normal for the pseudo element. Now we have a square of 50px that will accept the mouse. once the element scaled down to 50px , we still have this area reacting. (the square only looks smaller, but keeping using same space.
To finalize your fiddle test, let's add to body : transform-origin:top left;, now we should be abble to drag this square.
Firefox test :http://jsfiddle.net/Xcb8d/78/
Chrome test : http://jsfiddle.net/Xcb8d/79/ (negative margins added )
after a few clicks, it really gets jumpy of limits :)
hope it gives some hints.
reading this : http://css-tricks.com/get-value-of-css-rotation-through-javascript/
I thought , we could get the transform value from body and update calculation within the function , so we can modify scale value without touching script
. rookie test : http://jsfiddle.net/Xcb8d/82/
ex: function updated from original fiddle
// Will be called when user dragging an element
function _move_elem(e) {
var el = window.document.body;
var st = window.getComputedStyle(el, null);
var tr = st.getPropertyValue("transform") ;
var values = tr.split('(')[1];
values = values.split(')')[0];
values = values.split(',');
var a = values[0];
var b = values[1];
var c = values[2];
var d = values[3];
x_pos = document.all ? window.event.clientX : e.pageX;
y_pos = document.all ? window.event.clientY : e.pageY;
if (selected !== null) {
selected.style.left = ((x_pos / a) - x_elem) + 'px';
selected.style.top = ((y_pos / d) - y_elem) + 'px';
}
}

Related

Create a wiggle effect for a text

So what I want to happen is that when viewing the Span the text is normal but as you scroll down it starts moving until it looks like such:
Before the effect:
While the effect occurs:
The header is represented by spans for each letter. In the initial state, the top pixel value for each is 0. But the idea as mentioned is that that changes alongside the scroll value.
I wanted to keep track of the scroll position through JS and jQuery and then change the pixel value as needed. But that's what I have been having trouble with. Also making it smooth has been another issue.
Use the mathematical functions sine and cosine, for characters at even and odd indices respectively, as the graphs of the functions move up and down like waves. This will create a smooth effect:
cos(x) == 1 - sin(x), so in a sense, each character will be the "opposite" of the next one to create that scattered look:
function makeContainerWiggleOnScroll(container, speed = 0.01, distance = 4) {
let wiggle = function() {
// y-axis scroll value
var y = window.pageYOffset || document.body.scrollTop;
// make div pseudo-(position:fixed), because setting the position to fixed makes the letters overlap
container.style.marginTop = y + 'px';
for (var i = 0; i < container.children.length; i++) {
var span = container.children[i];
// margin-top = { amplitude of the sine/cosine function (to make it always positive) } + { the sine/cosine function (to make it move up and down }
// cos(x) = 1 - sin(x)
var trigFunc = i % 2 ? Math.cos : Math.sin;
span.style.marginTop = distance + distance * trigFunc(speed * y)/2 + 'px';
}
};
window.addEventListener('scroll', wiggle);
wiggle(); // init
}
makeContainerWiggleOnScroll(document.querySelector('h2'));
body {
height: 500px;
margin-top: 0;
}
span {
display: inline-block;
vertical-align: top;
}
<h2>
<span>H</span><span>e</span><span>a</span><span>d</span><span>e</span><span>r</span>
</h2>
Important styling note: the spans' display must be set to inline-block, so that margin-top works.
Something like this will be the core of your JS functionality:
window.addEventListener('scroll', function(e) {
var scrl = window.scrollY
// Changing the position of elements that we want to go up
document.querySelectorAll('.up').forEach(function(el){
el.style.top = - scrl/30 +'px';
});
// Changing the position of elements that we want to go down
document.querySelectorAll('.down').forEach(function(el){
el.style.top = scrl/30 +'px';
});
});
We're basically listening in on the scroll event, checking how much has the user scrolled and then act upon it by offsetting our spans (which i've classed as up & down)
JSBin Example
Something you can improve on yourself would be making sure that the letters wont go off the page when the user scrolls a lot.
You can do this with simple math calculation, taking in consideration the window's total height and using the current scrollY as a multiplier.
- As RokoC has pointed out there is room for performance improvements.Implement some debouncing or other kinds of limiters

JavaScript: Why a dynamically created div won't position top/bottom edge at .clientY?

Using JavaScript, I dynamically create a <div>(call it 'popup'), populate it with content, insert it into the DOM, and then attempt to position it relative to (.clientX, .clientY) of the click event.
The positioning scheme is simple. If .clientX is in the left half of the view port, I want popup's left edge to be at .clientX. If .clientX is in the right half of the view port, I want popup's right edge to be at .clientX. Similarly for .clientY. If it is in the top half of the view port, I want popup's top edge at .clientY; if in the bottom half, popup's bottom edge should be at .clientY.
I have the horizontal alignment working correctly but can not get the vertical to work.
The algorithm I'm using is:
function positionPopupOnPage( evt ) {
var vpWH = [];
var vpW, vpH;
var coordX = evt.clientX;
var coordY = evt.clientY;
vpWH = getViewPortWidthHeight();
vpW = vpWH[0];
vpH = vpWH[1];
popup.style.position = 'absolute';
// if not display: block, .offsetWidth & .offsetHeight === 0
popup.style.display = 'block';
popup.style.zIndex = '10100';
if ( coordX > vpW/2 ) { coordX -= popup.offsetWidth; }
if ( coordY > vpH/2 ) { coordY -= popup.offsetHeight; }
popup.style.top = coordY + 'px';
popup.style.left = coordX + 'px';
} // end fn positionPopupOnPage
The function call was positionPopupOnPage(event).The function getViewPortWidthHeight() is the one given in answer to the stackoverflow question Find the exact height and width of the viewport in a cross-browser way (no Prototype/jQuery).
The problem is that popup's top/bottom edge does not align with .clientY. In the screenshot below, (.clientX, .clientY) was the the "C" in "Charlie Fowler" which is where the mouse clicked. But popup's bottom edge is way above that position.
.
--> EDIT 0 <-- (in response to #user2330270's remarks)
Popup is inserted as high up the DOM tree as possible, as the first child of <body>. The function call is:
// insert popup above the first child of <body>
parentNode = document.getElementsByTagName("body")[0];
targetNode = parentNode.children[0];
insertPopup( parentNode, targetNode );
The function definition is:
function insertPopup( parentNode, targetNode ) {
parentNode.insertBefore(popup, targetNode);
popup.classList.add( 'popup')
existsPopup = true;
} // end fn insertPopup
There is a Pen, Table Play, at CodePen. It is the full code. The definition of positionPopupOnPage() is the third up from the bottom in the JS area, beginning at line 233.
The only CSS reaching popup is:
.popup {
position: absolute;
border: 2px solid #000;
border-radius: 10px;
width: 200px;
height: 250px;
overflow-y: auto;
background-color: rgba(0, 0, 0, .5);
color: #fff;
z-index: 1000;
display: none;
}
and the JS style assignments in positionPopupOnPage() as given above.
--> End Edit 0 <--
-->Edit 1<--
Correct the statement of the problem.The function does not work in Safari or Firefox as was initially erroneously reported. Update the positioning function to the one currently used in the Pen.
-->End Edit 1<--
Can someone help determine what is happening and how I can get popup's top/bottom edge to align with .clientY in Chrome?
Your effort and interest in my question are much appreciated. Thank you.
-Steve
From looking at your algorithm it looks right. What I would suggest is set the top position as 0px and see where it lies. If it is not at the top of the page, then you know you have CSS stopping it working correctly.
Maybe try making a simplified fiddle and you could be assisted further. The popup should be as top a level as possible in the HTML tree to keep things easy and reliable. This may also fix your issue if it is not.
The trick, for me, was to realize 4 things:
An absolutely positioned element, which the div popup is, is positioned from the page top not the view port top.
As the page is scrolled up, one has to account for the distance scrolled up, i.e. the distance from the view port top to the page top.
That distance is obtained by document.body.scrollTop.
Add that distance to the distance from the click point to the view port top, i.e. event.clientY, to get the total distance to use in setting popup's CSS top property.
The correct function to solve the problem then becomes:
// positon popup on page relative to cursor
// position at time of click event
function positionPopupOnPage( evt ) {
var VPWH = []; // view port width / height
var intVPW, intVPH; // view port width / height
var intCoordX = evt.clientX;
var intCoordY = evt.clientY; // distance from click point to view port top
var intDistanceScrolledUp = document.body.scrollTop;
// distance the page has been scrolled up from view port top
var intPopupOffsetTop = intDistanceScrolledUp + intCoordY;
// add the two for total distance from click point y to top of page
var intDistanceScrolledLeft = document.body.scrollLeft;
var intPopupOffsetLeft = intDistanceScrolledLeft + intCoordX;
VPWH = getViewPortWidthHeight(); // view port Width/Height
intVPW = VPWH[0];
intVPH = VPWH[1];
popup.style.position = 'absolute';
// if not display: block, .offsetWidth & .offsetHeight === 0
popup.style.display = 'block';
popup.style.zIndex = '10100';
if ( intCoordX > intVPW/2 ) { intPopupOffsetLeft -= popup.offsetWidth; }
// if x is in the right half of the viewport, pull popup left by its width
if ( intCoordY > intVPH/2 ) { intPopupOffsetTop -= popup.offsetHeight; }
// if y is in the bottom half of view port, pull popup up by its height
popup.style.top = intPopupOffsetTop + 'px';
popup.style.left = intPopupOffsetLeft + 'px';
} // end fn positionPopupOnPage
With thanks to user ershwetabansal on CSS Tricks for leading me to point 2 above.

Change margin-top as user scrolls

I am trying to get a div to scroll up at the same amount of pixels as the user scrolls down the page. For example, in Google Chrome when using the mouse wheel, it scrolls down in about 20px intervals. But when you scroll down using the handle, the scrolling amount varies.
Here is my code so far:
var scrollCtr = 50;
$(window).scroll(function(){
scrollCtr = scrollCtr - 20;
$('div.nexus-files').css('margin-top', scrollCtr + 'px');
});
There are a few problems with this:
The user scrolling varies
It needs to subtract from margin-top if scrolling down and add to margin-top if scrolling up
Here is an example:
http://www.enflick.com/
Thanks for the help
You're doing it the wrong way, what you are trying to do should be done using position: fixed on div.nexus-files
div.nexus-files{position: fixed; top: 0;}
but anyway - if you still want to know what you can do with the scroll event - you better get to scrollTop of the document and set the margin-top to the same value
window.onscroll = function(event){
var doc = document.documentElement, body = document.body;
var top = (doc && doc.scrollTop || body && body.scrollTop || 0);
document.getElementById('nexus-files_id').style.marginTop = top+'px';
}
I'm using pure Javascript instead of jQuery because of the overhead that might be crucial when the browser need to calculate stuff in a very short amount of time (during the scrolling). [this can be done even more efficient by storing reference to the element and the doc... but you know..)
I used id based selector to get the specific element instead of class based
AND I SAY AGAIN - this is not how you should do what you were trying to do
Why not using the actual scroll offset as reference or position ?
// or whatever offset you need
var scrollOffset = document.body.scrollTop + 20;
// jQuery
var scrollOffset = $("body").scrollTop() + 20;
Finally Got it
Here is the code I used to accomplish the task.
Most of the code is from http://enflick.com and I modified it to work with my individual situation.
jQuery(window).load(function(){
initParallax();
});
// parallax init
function initParallax(){
var win = jQuery(window);
var wrapper = jQuery('#wrapper');
var bg1 = wrapper.find('.nexus-files');
var koeff = 0.55;
if (bg1.length) {
function refreshPosition(){
var scrolled = win.scrollTop();
var maxOffsetY1 = 450;
var offsetY1 = scrolled * koeff;
var offsetY2 = scrolled * koeff - (maxOffsetY1 * koeff - offsetY1);
if (offsetY1 <= maxOffsetY1 * koeff - offsetY1) {
bg1.css("margin-top", +-offsetY1+"px");
//alert(+-offsetY1+"px");
}
}
refreshPosition();
win.bind('resize scroll', refreshPosition);
}
}

javascript (but not jQuery please) fixed position on x-axis but not y?

I've looked everywhere and so far have not found a non-jQuery js to handle this. I would like to avoid using a library for just this one simple task.
I would like to fix three navigation divs ("#header", "#tabs" and "#footer") to viewport left (or alternatively, to the x position of a div "#helper" with "position: fixed; left: 0; top: 0;") -- but not fix y. They can not be vertically fixed.
I've created a working js that forces the divs to reposition based on scrolling, but it's not smooth in the real page (too many dynamic and graphic elements) - I'd like it to either animate smoothly, or mimic fixed-left and not appear to reposition at all.
Anyone who can give pointers or a quick script, or review and modify the script I have made? I've noticed people tend to ask why an obvious solution is not used instead of answering the question... I will be glad to answer, but would prefer help with the actual problem.
Here is a jsFiddle with the problem: http://jsfiddle.net/BMZvt/6/
Thank you for any help!
Smooth animation example:
var box = document.getElementById('box');
var moveTo = function(obj, target) {
// start position
// you should obtain it from obj.style
var cpos = {
x: 0,
y: 0
}
var iv = setInterval(function(){
cpos.x += (target.x - cpos.x) * 0.3; // 0.3 is speed
cpos.y += (target.y - cpos.y) * 0.3; // 0.3 is speed
obj.style.left = Math.floor(cpos.x) + 'px';
obj.style.top = Math.floor(cpos.y) + 'px';
var dist = Math.abs(cpos.y - target.y); // distance (x+y) from destination
dist += Math.abs(cpos.x - target.x); // < 1 = object reached the destination
if(dist < 1) { // here we are checking is box get to the destination
clearInterval(iv);
}
}, 30); // this is also the speed
}
box.onclick = function(){
moveTo(box, {x: 90, y: 75}); // fire this function to move box to specified point
}
Demonstration: http://jsfiddle.net/Qwqf6/5/
Your script is your job, but this is a quick start how to solve animation problem
You can also do some fancy stuff with speed for example use sin(x) to set the speed
Demonstration #2 http://jsfiddle.net/Qwqf6/6/ (very smooth)
Full script here https://gist.github.com/3419179
I don't think there's a straight way to do this...
But here's a way.
First, You need to be able to detect the direction of the scrolling when window.onscroll event happens. You would do this by comparing the current page offsets with the newly acquired page offsets whenever the scroll event happens. (http://stackoverflow.com/questions/1222915/can-one-use-window-onscroll-method-to-include-detection-of-scroll-direction)
Now suppose you know the direction of the scroll, you want to change the styling for the divs depending on the direction of the scroll.
Let FixAtX be the value of the x coordinate that you want to fix your divs at.
Let OriginalY be the y coordinate of the divs.
Also whenever scrolling happens, despite of the direction, you want to remember the pageoffset X and Y. Let's call them OldX and OldY
If scrolling vertically:
Set position value for divs' style to be absolute.
Set top value for divs' style to be OriginalY
Set left value for divs' style to be OldX + FixAtX
If scrolling horizontally:
Set position value for divs' style to be fixed.
set top value for divs' style to be OriginalY - OldY (<- this may be different depending on how the browser computes pageOffset value,)
Set Left value for divs' style to be FixAtX
I think this should work...
Since you are just using browser's rendering for positioning, it should be very smooth!
hope I understood the question correctly.
This is for people who view this post - I wound up going with the solution I initially put together in the jsFiddle that used a simple javascript to mimic fixed x.
The javascript in the first answer was hefty and wound up buggy, and the second answer sounded good but did not work in practice. So, I'm recommending the javascript from the jsFiddle (below) as the best answer to fixed x and fluid y without a javascript library. It's not perfect and has a minimal delay but is the best answer I've found.
function fixLeft() {
function getScrollX() {
var x = 0, y = 0;
if( typeof( window.pageYOffset ) == 'number' ) {
x = window.pageXOffset;
} else if( document.body && ( document.body.scrollLeft) ) {
x = document.body.scrollLeft;
} else if( document.documentElement && ( document.documentElement.scrollLeft) ) {
x = document.documentElement.scrollLeft;
}
return [x];
}
var x = getScrollX();
var x = x[0];
// have to get and add horizontal scroll position px
document.getElementById('header').style.left = x + "px";
document.getElementById('tabs').style.left = x + "px";
document.getElementById('footer').style.left = x + "px";
}
window.onscroll = fixLeft;

HTML/Javascript: Fix scroll position after screen rotation

What is the best way to restore the scroll position in an HTML document after the screen has been rotated? (This is in a Cocoa Touch UIWebView, but I think it's a problem everywhere.) The default behavior seems to restore the y-offset in pixels, but since the text has been reflowed this is now a different position in the document.
My initial thought is to:
Pepper the document with invisible, uniquely-id'ed elements.
Before rotation, search for the element e whose y-offset is closest to the scroll offset.
After rotation, update the scroll offset to e's new y-offset.
Even if that works, I'd prefer not to insert a bunch of crud into the document. Is there a better way?
Here's a diagram to clarify the problem. Restoring the original y-offset does not produce the intended result because more text fits on a line in landscape mode.
Not pretty but it works. This requires there to be span tags throughout the document text.
// Return the locator ID closest to this height in pixels.
function findClosestLocator(height) {
var allSpans = document.getElementsByTagName("span");
var closestIdx = 0;
var closestDistance = 999999;
for(var i = 0; i < allSpans.length; i++) {
var span = allSpans[i];
var distance = Math.abs(span.offsetTop - height);
if(distance < closestDistance) {
closestIdx = i;
closestDistance = distance;
}
}
return allSpans[closestIdx].id;
}
After rotation, document.getElementById(spanId).offsetTop is the new y-offset, where spanId is the result of findClosestLocator() before rotation.
Conceptually the problem isn't so hard to think about. You have scroll events, rotation events, and variables. I would track the scrollTop position on the document.body DOM node on the scroll event. Reapply it with the orientation event fires.
Something like this perhaps.
// Track position
var pos;
// On scroll update position
document.body.addEventListener("scroll", function() {
pos = document.body.scrollTop;
}, true);
// On rotation apply the scroll position
window.addEventListener("orientationchange", function() {
document.body.scrollTop = pos;
}, true);

Categories