jQuery .position() strangeness while using CSS3 rotate attribute - javascript

I'm getting absolutely positioned rotated elements position with jQuery .position() method, then setting position-related attributes (top, left) with jQuery .css(pos), where pos is the data returned by .position(). I think it'll leave the element in it's place, but elements position is changing.
How can I use set rotated elements position, so that it'll be placed as expected? Maybe there is a coefficient depended on angle that changes position?
I'm testing in Google Chrome v.9, Windows XP.
HTML
<div id="container">
<div id="element">
<img src="http://t0.gstatic.com/images?q=tbn:ANd9GcS0Fawya9MVMez80ZusMVtk_4-ScKCIy6J_fg84oZ37GzKaJXU74Ma0vENc"/>
</div>
</div>
CSS
#container {
position: relative;
border: 1px solid #999;
padding: 5px;
height: 300px;
width:300px;
}
#element {
position: absolute;
top:50px;
left: 60px;
width: auto;
border: 1px solid #999;
padding: 5px;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
}
JS
$(document).ready(function(){
var $el = $('#element'),
// getting position
pos = $el.position();
alert(pos.left + '/' + pos.top);
// alerts 37/11
// setting css position attributes equal to pos
$el.css(pos);
// re-getting position
pos = $el.position();
alert(pos.left + '/' + pos.top);
// alerts 14/-28
});
View it http://jsfiddle.net/Antaranian/2gVL4/

// Needed to read the "real" position
$.fn.adjustedPosition = function() {
var p = $(this).position();
return {
left: p.left - this.data('dx'),
top: p.top - this.data('dy')
}
};
$(function() {
var img = $('img'),
pos;
// Calculate the delta
img.each(function() {
var po = $(this).position(), // original position
pr = $(this).addClass('rot').position(); // rotated position
$(this).data({
dx: pr.left - po.left, // delta X
dy: pr.top - po.top // delta Y
});
});
// Read the position
pos = img.adjustedPosition();
alert(pos.left + '/' + pos.top);
// Write the position
img.css(pos);
// Read the position again
pos = img.adjustedPosition();
alert(pos.left + '/' + pos.top);
});
Live demo: http://jsfiddle.net/2gVL4/4/
So what is going on here:
The CSS code that rotates the image is stored inside a special CSS class. I do this because I want to read the original position of the image (before rotating). Once I read that original position, I apply the .rot class, and then read the position again to calculate the difference (delta), which is stored inside the element's data().
Now, I can read the position via the custom method adjustedPosition (which is defined above). This method will read the position of the element and then subtract the delta values stored inside the data() of the element.
To write the position, just use the css(pos) method like normally.

Had similar problem. There is simple solution (not elegant, but working):
set current angle to 0
read X/Y position
revert angle back to its value
var temp = $(this).position();
temp.angle = getRotationDegrees( $(this) ); // remember current angle
rotateObject($(this), 0); // set angle to 0
temp.left = Math.round($(this).position().left); // proper value
temp.top = Math.round($(this).position().top); // proper value
// revert back the angle
rotateObject($(this), temp.angle);
Used functions:
function rotateObject(obj, angle) {
obj.css({ '-webkit-transform': 'rotate(' + angle + 'deg)'});
obj.css({ '-moz-transform': 'rotate(' + angle + 'deg)'});
obj.css({ '-ms-transform': 'rotate(' + angle + 'deg)'});
obj.css({ 'msTransform': 'rotate(' + angle + 'deg)'});
obj.css({ '-o-transform': 'rotate(' + angle + 'deg)'});
obj.css({ '-sand-transform': 'rotate(' + angle + 'deg)'});
obj.css({ 'transform': 'rotate(' + angle + 'deg)'});
}
function getRotationDegrees(obj) {
var matrix = obj.css("-webkit-transform") ||
obj.css("-moz-transform") ||
obj.css("-ms-transform") ||
obj.css("-o-transform") ||
obj.css("transform");
if(matrix !== 'none') {
var tr;
if (tr = matrix.match('matrix\\((.*)\\)')) {
tr = tr[1].split(',');
if(typeof tr[0] != 'undefined' && typeof tr[1] != 'undefined') {
var angle = Math.round(Math.atan2(tr[1], tr[0]) * (180/Math.PI));
}else{
var angle = 0;
}
}else if(tr = matrix.match('rotate\\((.*)deg\\)')){
var angle = parseInt(tr[1]);
}
} else { var angle = 0; }
return (angle < 0) ? angle + 360 : angle;
}

Related

angular2 - jQuery add pixels to position top

I want to position my markers when zoom 18 += 10px up, so what I dit is this:
his._mapsWrapper.subscribeToMapEvent<void>('zoom_changed').subscribe(() => {
this._mapsWrapper.getZoom().then((z: number) => {
this._zoom = z;
if(z === 18) {
$('.marker').css({ 'width' : '25px', 'height' : '25px', 'line-height': '25px', 'top' : '+= 10!important' });
$('.number-id').css({'font-size': '11px'})
}
But it seems not to work can somebody help me out here? Here is a PLUNKER, where you can see this code in src/google-maps/directive/google-map.ts in the method _handleMapZoomChange()
What it does it is setting all the elements to 10px, but what I basically wants is to set the current top position + 10px.
$('.marker').each( function (index) {
console.log(index + ":" + $(this).css('top'));
var currentTop = $(this).css('top');
$(this).css('top', currentTop + '10px');
})
If you are using custom overlay, it can cause unexpected behaviour if you adjust left or top position of the marker, because that is tied to latLng position of the marker.
Instead, just use margin-top css attribute (margin-top: 10px or margin-top: -10px depending on your needs)
The same thing when drawing the marker, don't adjust position like this:
if (point) {
div.style.left = (point.x - 10) + 'px';
div.style.top = (point.y - 10) + 'px';
}
Instead add the offset as margin-top and margin-left to your marker's css:
div.style.cssText = `width: 25px;
height: 25px;
...
margin-top: -10px;
margin-left: -10px;`

On mousemove translate div position within a specified range

I have a wrapper called #mousearea and I have a div called #mouseshift what I would like to do is when I hover over #mousearea I would like to shift the translate3d(0,230%,0) value between a particular range.
I have got the mousemove working but I currently end up with something like translate3d(7881%,230%,0) it's just too sensetive I would like it to translate the X co-ordinate between something like 0-60% so it's far more subtle.
Here is what I have so far:
jQuery(document).ready(function($){
$('#mousearea').mousemove(function (e) {
var shiftAmount = 1;
$('#mouseshift').css(
'transform', 'rotate(90deg) translate3d(' + -e.pageY + shiftAmount + '%,230%,0)'
);
});
});
Update:
This is a little closer, except it logs the correct translate3d but doesn't apply it to #mouseshift.
$('#mousearea').mousemove(function(e){
var x = e.pageY - this.offsetTop;
var transfromPosition = 'translate3d(' + x + ', 230%, 0)';
console.log(transfromPosition);
if ((x <= 800)) {
//$('#mouseshift').css({'top': x});
$('#mouseshift').css('transform', transfromPosition);
}
});
Final Solution:
jQuery(document).ready(function($){
$('#mousearea').mousemove(function(e){
var min = 50;
var max = 70;
var x = e.pageY;
var windowHeight = window.innerHeight;
scrolled = (x / windowHeight);
percentageScrolled = scrolled * 100;
offsetScroll = max - min;
offsetPercentage = scrolled * 20;
translateX = min + offsetPercentage;
console.log(x + 'px');
console.log(windowHeight + 'px window height');
console.log(percentageScrolled + '% scrolled');
console.log(offsetScroll + 'offset scroll');
console.log(offsetPercentage + '% offset percentage');
var transfromPosition = 'rotate(90deg) translate3d(' + translateX + '%, 230%, 0)';
$('#mouseshift h1').css('transform', transfromPosition);
});
});
Convert to a reusable plugin I would like to extend this to work with more than one object now and each object would have a different max and min value:
This is what I have but it seems to effect all the items on only use on elements max and min.
$(function () {
$('#mouseshift-1, #mouseshift-2').mouseShift();
});
(function ($) {
$.fn.mouseShift = function () {
return this.each(function () {
var myEl = $(this);
var min = $(this).data('min');
var max = $(this).data('max');
$('#mousearea').mousemove(function (e) {
var yPosition = e.pageY;
var windowHeight = window.innerHeight;
scrolled = (yPosition / windowHeight);
//percentageScrolled = scrolled * 100;
offsetRange = max - min;
offsetRangePercentage = scrolled * 20;
offset = min + offsetRangePercentage;
//// Debug
console.log('max: ' + max + ', Min:' + min);
console.log(yPosition + 'px');
console.log(windowHeight + 'px window height');
//console.log(percentageScrolled + '% scrolled');
console.log(offsetRange + 'px offset scroll');
console.log(offsetRangePercentage + '% offset percentage');
var transfromPosition = 'rotate(90deg) translate3d(' + offset + '%, 230%, 0)';
myEl.css('transform', transfromPosition);
});
});
};
})(jQuery);
And some HTML for clarity:
<div class="column"><h1 id="mouseshift-1" data-min="50" data-max="70">boo</h1></div>
<div class="column"><h1 id="mouseshift-2" data-min="20" data-max="90">bah</h1></div>
<div class="column"><h1 id="mouseshift-3" data-min="80" data-max="100">bing</h1></div>
I think what you are looking for is finding an average that your can distribute. The best way to do this is to divide by the maximum amount it can move, and multiply it by the maximum value it can have, so basically:
position / maxposition * maxvalue
The first bit will return a number between 0 and 1, while the last bit will make it the value between 0 and 60. Below I have built a simply (jquery-less) version of it to show how this would work:
var mousePointer = document.getElementById('test')
document.addEventListener('mousemove', function(e){
var x = e.pageX / window.innerHeight;
x = x * -60;
mousePointer.style.webkitTransform = 'translateX(' + x + '%)';
mousePointer.style.transform = 'translateX(' + x + '%)';
})
#test {
position: absolute;
left: 50%;
top: 50%;
width: 20px;
height: 20px;
background: red;
}
<div id="test"></div>
Update: Reusable Snippet
I don't really like using jQuery, so once again it will be vanilla javascript (but it's pretty simple). Is that what you were - sort of - trying to do with the reusable plugin?
var divs = Array.prototype.slice.call(document.querySelectorAll('[data-range]'));
document.addEventListener('mousemove', function(e){
var eased = e.pageX / window.innerWidth;
divs.forEach(function(div){
var range = div.getAttribute('data-range').split(',');
var min = parseFloat(range[0]);
var max = parseFloat(range[1]);
var ease = min + (eased * (max - min));
div.style.webkitTransform = 'translateX(' + ease + '%)';
div.style.transform = 'translateX(' + ease + '%)';
});
});
div {
position: absolute;
left: 50%;
top: 50%;
width: 200px;
height: 200px;
background: gray;
}
#d2 { background: yellow; }
#d3 { background: #666; }
<div data-range="60,70" id="d1"></div>
<div data-range="-70,70" id="d2"></div>
<div data-range="-60,-70" id="d3"></div>
From simple reading, I see that you're missing a % sign. Should be like this:
$('#mousearea').mousemove(function(e){
var x = e.pageY - this.offsetTop;
var transfromPosition = 'translate3d(' + x + '%, 230%, 0)';
console.log(transfromPosition);
if ((x <= 800)) {
//$('#mouseshift').css({'top': x});
$('#mouseshift').css('transform', transfromPosition);
}
});
This should be working like your first example, where you do use % for both values inside the translate3d string.
Update:
To coerce your x Value to something between 0 and 60, you need to find a pair of possible min and max values for x. Then you can do something like what's shown in this answer:
Convert a number range to another range, maintaining ratio

How to scale DIV elements with CSS3 and keep the distances between them

I'm trying to scale some divs inside a 'workspace' div. The problem I have are with the distance between them (I lose it).
I have read a lot of post about CSS3 transform, in special about scale and translate and origin, but I can't solve the problem...
Can someone explain me how can I keep them? I show one code using a mousewheel plugin, regards.
HTML
<body>
<div id="workspace">
<div style="z-index: 101;"><img src="image1-300x300.jpg" /></div>
<div style="z-index: 102; left: 300px; top: 400px;"><img src="image2-400x400.jpg" /></div>
</div>
</body>
CSS
#workspace {
width: 1600px;
height: 900px;
position: absolute;
margin: auto;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: inline;
}
#workspace > div {
display: inline;
position: absolute;
}
JAVASCRIPT
$(document).ready(function () {
var scale = 1; // scale of the image
var xLast = 0; // last x location on the screen
var yLast = 0; // last y location on the screen
var xImage = 0; // last x location on the image
var yImage = 0; // last y location on the image
// if mousewheel is moved
$("#workspace").mousewheel(function (e, delta) {
// find current location on screen
var xScreen = e.pageX - $(this).offset().left;
var yScreen = e.pageY - $(this).offset().top;
// find current location on the image at the current scale
xImage = xImage + ((xScreen - xLast) / scale);
yImage = yImage + ((yScreen - yLast) / scale);
// determine the new scale
if (delta > 0) {
scale *= 2;
}
else {
scale /= 2;
}
scale = scale < 1 ? 1 : (scale > 64 ? 64 : scale);
// determine the location on the screen at the new scale
var xNew = (xScreen - xImage) / scale;
var yNew = (yScreen - yImage) / scale;
// save the current screen location
xLast = xScreen;
yLast = yScreen;
// redraw
$('#mosaicContainer > div').each(function() {
$(this).css('-webkit-transform', 'scale(' + scale + ')' + 'translate(' + xNew + 'px, ' + yNew + 'px' + ')')
.css('-webkit-transform-origin', (xImage) + 'px ' + (yImage) + 'px');
});
return false;
});
});

How to draw a line beet wen two points in loop?

I have an animations with jQuery which looks like that:
$(function() {
var elems = $('div.icon').not('#icon-0');
var increase = Math.PI * 2 / elems.length,
x = 0,
y = 0,
angle = 0,
radius = 200;
var center_top = ($("#slider-1").outerHeight() - $("#icon-0").outerHeight())/2,
center_left = ($("#slider-1").outerWidth() - $("#icon-0").outerWidth())/2;
$('.icon').css({
'top': center_top + 'px',
'left': center_left + 'px'
});
$(elems).css('opacity', '0').each(function(i) {
elem = elems[i];
x = radius * Math.cos(angle) + center_left;
y = radius * Math.sin(angle) + center_top;
$(elem).delay(400*i).animate({
'position': 'absolute',
'left': x + 'px',
'top': y + 'px',
'opacity': '1'
}, 1000);
angle += increase;
});
});
http://jsfiddle.net/d6pYR/
How do I write the line between the circle in the center and each of the outer circles?
I tried with the canvas and getting center coordinates via offset and some easy math, but canvas wouldn't do it properly or I just can't do that.
I'd appreciate any help from you guys.
Cheers!
taking into mind that you do not want to use canvas (or is not feasible) made ​​a solution using css3 transform ...
follows the solution
in this JSFiddle -> http://jsfiddle.net/d6pYR/2/
you need to create more one css class to make a line
.line {
border-top: 1px solid black;
position: absolute;
height: 1px;
opacity: 0;
-webkit-transform-origin: left;
transform-origin: left;
}
to create a element you can create in loop
var line = $("<div class='line'></div>");
slider.append(line);
line.css("width", 0);
line.css("top", center_top + ( $(this).height() / 2 ) );
line.css("left", center_left + ( $(this).width() / 2 ) );
line.css("transform", "rotateZ(" + angle + "rad)");

Does there exist a method or plugin to allow for rotate transforms to respect div boundries?

By default, the css rotate property seems to occur after tag spacing. For instance, if you have two divs in a column and you rotate one, they overlap. It's possible I completely missed some aspect of css or html that handles this issue, have I?
The obvious solution seems to be to write some javascript to manage the placement of elements post rotate. Does a plugin exist that helps handle this spacing? The only thing close I could locate was the jquery-rotate plug, but it does not seems to provide any functionality with regard to spacing.
Relevant html/css demonstrating the spacing problem.
HTML
<div class="red-box rotate-right"></div>
<div class="blue-box"></div>​
CSS
.rotate-right {
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-o-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.red-box{
height: 50px;
width: 100px;
background: red;
}
.blue-box{
height: 50px;
width: 100px;
background: blue;
}
Alright, beware this is ugly to look at.
First, I used code from CSS-Tricks to get the angle of rotation. Then, I use some algebra to find the distance (from the center of the rotated element) to the sides of a box that contains the element. Then I add margins to the edge of the rotated element to create (or remove) extra space where needed. This also takes into account the original margins (if any).
Usage:
After rotating an element, call $(rotatedElement).space([grow],[shrink]). See code comments for argument descriptions.
jQuery.fn.space = function(grow,shrink){
// grow = Grow area around element to fit? (true/false)
// shrink = Shrink area around element to fit? (true/false)
var el = this.get(0);
if(typeof(grow)=='undefined'){
grow = true; // Default to grow extra space when needed
}
if(typeof(shrink)=='undefined'){
shrink = false; // Default to not shrink at all
}
//Get angle of rotated element
var st = window.getComputedStyle(el, null);
var tr = st.getPropertyValue("-webkit-transform") ||
st.getPropertyValue("-moz-transform") ||
st.getPropertyValue("-ms-transform") ||
st.getPropertyValue("-o-transform") ||
st.getPropertyValue("transform");
var v = tr.split('(')[1].split(')')[0].split(',');
var scale = Math.sqrt(v[0]*v[0] + v[1]*v[1]);
var angle = Math.round(Math.atan2(v[1], v[0]) * (180/Math.PI));
//Save or recall original margins
var m = new Array();
if(el.getAttribute('margins')==null){
m[0] = st.getPropertyValue("margin-left").match(/\d+/);
m[1] = st.getPropertyValue("margin-right").match(/\d+/);
m[2] = st.getPropertyValue("margin-top").match(/\d+/);
m[3] = st.getPropertyValue("margin-bottom").match(/\d+/);
el.setAttribute('margins',m[0]+","+m[1]+","+m[2]+","+m[3]);
} else {
m = el.getAttribute('margins').split(',');
console.log(m);
}
//Get center coords
var cx = st.getPropertyValue("width").match(/\d+/)/2;
var cy = st.getPropertyValue("height").match(/\d+/)/2;
//Convert radian values to degrees
function toDeg(angle){
return angle*Math.PI/180;
}
// Coords of the corners
// (starting from top-left and proceeding clockwise)
// relative to the center of the element
// c[cornerID][x|y]
var c = [ [Math.round(cx*Math.cos(toDeg(angle-180))
+ cy*Math.cos(toDeg(angle-90))),
Math.round(cx*Math.sin(toDeg(angle-180))
+ cy*Math.sin(toDeg(angle-90)))],
[Math.round(cx*Math.cos(toDeg(angle))
+ cy*Math.cos(toDeg(angle-90))),
Math.round(cx*Math.sin(toDeg(angle))
+ cy*Math.sin(toDeg(angle-90)))],
[Math.round(cx*Math.cos(toDeg(angle))
+ cy*Math.cos(toDeg(angle+90))),
Math.round(cx*Math.sin(toDeg(angle))
+ cy*Math.sin(toDeg(angle+90)))],
[Math.round(cx*Math.cos(toDeg(angle-180))
+ cy*Math.cos(toDeg(angle+90))),
Math.round(cx*Math.sin(toDeg(angle-180))
+ cy*Math.sin(toDeg(angle+90)))]
];
var elx = ([c[0][0], c[1][0], c[2][0], c[3][0]]).sort(function(a,b){
return(a*1)-(b*1);});
var ely = ([c[0][1], c[1][1], c[2][1], c[3][1]]).sort(function(a,b){
return(a*1)-(b*1);});
var b = [-elx[0], elx[3], -ely[0], ely[3]]; // [Left, Right, Top, Bottom]
if(grow){
if(b[0]-cx>0) el.style.marginLeft = (m[0] + b[0]-cx) + "px";
if(b[1]-cx>0) el.style.marginRight = (m[1] + b[1]-cx) + "px";
/*}
if(growY){ */
if(b[2]-cy>0) el.style.marginTop = (m[2] + b[2]-cy) + "px";
if(b[3]-cy>0) el.style.marginBottom = (m[3] + b[3]-cy) + "px";
}
if(shrink){
if(b[0]-cx<0) el.style.marginLeft = (m[0] + b[0]-cx) + "px";
if(b[1]-cx<0) el.style.marginRight = (m[1] + b[1]-cx) + "px";
/*}
if(shrinkY){ */
if(b[2]-cy<0) el.style.marginTop = (m[2] + b[2]-cy) + "px";
if(b[3]-cy<0) el.style.marginBottom = (m[3] + b[3]-cy) + "px";
}
}
You may wish to split (grow and shrink) into (growX, growY and shrinkX, shrinkY) depending on what's going on in your live site, so you don't break your layout. To do so, just adjust/add the arguments and defaults at the top, and the if(grow)/if(shrink) statements at the bottom.

Categories

Resources