This isn't so much a jQuery question as it is an overall conceptual question.
In my example I can populate a container with divs that have a top value set in a nonlinear fashion.
The top value of each one is calculated based on a formula that takes into account the top position of the one to its left as well as the height of the container (line 33 of fiddle).
//this formula sets the top value for each new child added to the container
//height is 100% of its parent which is 20% of the body
//newOne:last is the most recently added child and will have an initial top value of 10%
parseInt($(this).next().css('top'), 10) / $('#queue').height()) * 75 + (parseInt($('.newOne:last').css('top'), 10) * 2) + '%'
I more of less stumbled upon this by chance and it seems to work 'ok', but if an optimization is obvious to you, please point it out :)
What I'm having trouble coming up with is an elegant formula for how to adjust the children smoothly during a drag event. I'm thinking the top value needs to be adjusted based on some manipulation of the left offset, but after hours of experimenting, I haven't found anything that keeps the original position intact when I start dragging and continues adjusting the values smoothly during my drag. The children should gradually approach a minimum top value of 10% as I drag left (child with left offset of 0 will have a top value of 10%), and gradually move away from that top value back toward their initial position as I drag right.
$('#queue').draggable({
axis: "x",
scroll: false,
drag: function(){
//adjust values of each child
$('.newOne').each(function(){
var percentLeft = $(this).offset().left / $('footer').width() * 100
var thisLeft = parseInt($(this).css('left'), 10) / $(window).width() * 100;
var thisTop = parseInt($(this).css('top'), 10) / $('#queue').height() * 100;
if (percentLeft >= 0){
//top value of each one gradually decreases...
//as it gets closer to an offset value of 0 and minimum top value of 10%
//non-linear attempt but not even close
//$(this).css('top', $(this).css('top', 10 + (thisTop - 10 / thisLeft) + '%'));
//linear step
$(this).css({'top': 8 + (percentLeft/2) + '%'});
}
});
}
});
http://jsfiddle.net/5RRCS/17/
P.S. I know I'm asking a lot here, but hopefully someone is up to the challenge :)
Update:
Stumbled onto exp method and did something like this:
adjustTop = function(offset){
return 100 * (1.0-Math.min(0.98,(0.83 + ( 0.17/ (Math.exp(0.007*offset))) )) ) + '%';
};
$(this).css('top', adjustTop($(this).offset().left) );
Here's a version that I believe does what you are looking for.
The first thing I did was to refactor the top calculation so that both the initialization and the drag handlers would get the same results.
Rather than calculate the positions of the child divs based on their offset to the document, I changed the logic to use position relative to their container.
I also remove z-index as the child divs already being added the parent with the correct stacking order - the left most child is the last element in the container.
Calculating the height of each child depended on whether #queue's current position was to the left or right of its origin.
I also change the iteration logic to behave the same to simplify calculating the current elements starting offset:
$($('.newOne').get().reverse()).each(function (index) {
$(this).css({
'background': 'rgba(255,255,255,.80)',
'top': calcTop($(this), index)
});
});
Code for positioning the child elements:
function calcTop($ele, index) {
var elePositionLeft = $ele.position().left;
var queuePositionLeft = $('#queue').position().left;
var footerWidth = $('footer').width();
var queueHeight = $('#queue').height();
var distanceToTravel = queuePositionLeft < 0 ? elePositionLeft : footerWidth - elePositionLeft;
var percentTraveled = Math.abs(queuePositionLeft) / distanceToTravel;
var thisPercentLeft = (elePositionLeft + queuePositionLeft) / footerWidth;
var queuePercentLeft = queuePositionLeft / footerWidth;
var newTop;
var myStartOffset = (index + 1) * startOffset;
var topTravel = queuePositionLeft < 0 ? -myStartOffset + startOffset : (queueHeight - startOffset);
var linear = false;
if (linear) {
newTop = myStartOffset + (topTravel * percentTraveled);
newTop = newTop > startOffset ? Math.round(newTop) : startOffset;
return newTop;
} else {
if (queuePositionLeft >= 0) {
newTop = myStartOffset + (topTravel * thisPercentLeft * percentTraveled);
newTop = newTop > startOffset ? Math.round(newTop) : startOffset;
} else {
newTop = myStartOffset + (topTravel * (1+thisPercentLeft) * percentTraveled);
newTop = newTop < startOffset ? startOffset : Math.round(newTop);
}
return newTop;
}
}
There was also a minor bug in the reset function - it wasn't setting childCount back to zero:
$('#reset').click(function () {
$('#queue').empty().css('left', 0);
childCount = 0;
});
Demo Fiddle
Related
This is a follow-on from this question: Animation based on scroll position
The goal is to loop through each element, and change it's rotation and perspective based on the users scroll position. I guess from an organic UX viewpoint, you'd want the top of the browser window to 'squash' the topmost item, and smoothy flip the element down.
Here's a screenshot for guidance:
Here is a Fiddle: https://jsfiddle.net/nfquerido/0zpc2a76/
And the loop function:
var _items = function () {
forEach(items, function (item) {
var scrollTop = _scrollTop(),
elementTop = item.offsetTop,
documentHeight = _getDocumentHeight(),
// Transform the item based on scroll
rotationFactor = Math.max(0, scrollTop - elementTop),
perspectiveFactor = Math.max(0, scrollTop - elementTop),
rotation = (rotationFactor / (documentHeight - windowHeight) * 90),
perspective = (perspectiveFactor / (documentHeight - windowHeight) * 2000),
transform = 'perspective(' + perspective + ') rotateX(' + rotation + 'deg)';
// Elements off the top edge.
if(scrollTop > elementTop) {
item.classList.add('scrolling');
item.style.webkitTransform = transform;
} else {
item.classList.remove('scrolling');
item.style.webkitTransform = null; // Reset the transform
}
});
};
I updated your fiddle: https://jsfiddle.net/0zpc2a76/1/
If I understand your question correctly, I think you are trying to get the blue boxes to "fold over" as if they are being pushed down by the top of the viewport. For that, your calculations seem to be wrong, so I updated some of the variable assignments:
rotation = (rotationFactor / (item.offsetHeight) * 90),
perspective = 2000 - (perspectiveFactor / (item.offsetHeight) * 2000),
So, I'm trying to animate elements (sequentially and independently) based on their scroll position.
The goal is to loop through each element, and change it's rotation based on the users scroll position. Right now, the rotations on each item are always the same. How can this be achieved?
Here is a Fiddle: https://jsfiddle.net/nfquerido/wy84pLud/
And this is the loop function:
var _items = function () {
forEach(items, function(item) {
var scrollTop = _scrollTop(),
elementTop = item.offsetTop,
documentHeight = _getDocumentHeight(),
elementHeight = _getElementHeight(item),
// Transform the item based on scroll
rotation = (scrollTop / (documentHeight - windowHeight) * 360),
transform = 'rotate(' + rotation + 'deg)';
// Elements off the top edge.
if (scrollTop > elementTop) {
item.classList.add('scrolling');
item.style.webkitTransform = transform;
} else {
item.classList.remove('scrolling');
item.style.webkitTransform = null; // Reset the transform
}
});
};
I'd appreciate vanilla JavaScript suggestions only please!
I think this is the fix you're looking for:
I added this right above your assignment of the rotation var:
// Transform the item based on scroll
rotationFactor = Math.max(0, scrollTop - elementTop),
rotation = ( rotationFactor / (documentHeight - windowHeight) * 360),
After replacing this they each get their relative offset rotation :)
The error is that the only changing/affecting variable to the rotation was your scrollTop, while that is only on a document-level.
To effect on an element-level we also want to include that difference :)
I'm trying to make a square appear at random positions of the screen. I have set it's position property to be absolute and in javascript i'm running a random number between 0 to 100, this will then be assigned as a percentage to top and left property. however if the random number was ~100 or a bit less the square will appear out of the screen. How do I fix this problem?
var shape1 = document.getElementById("shape1");
//creating random number to 100
function randomNum() {
var r = Math.random();
var y = (r * (100 - 0 + 1)) + 0;
var x = Math.floor(y);
console.log(x);
return x;
}
//reappear at random position
function reappear(object) {
object.style.left = randomNum().toString() + "%";
object.style.top = randomNum().toString() + "%";
object.style.display = "block";
}
reappear(shape1);
code: https://jsfiddle.net/y3m4ygow/1/
You can call the getBoundingClientRect method (MDN reference) on the object and check to see if its bottom property is bigger than window.innerHeight (means it's falling outside the window height), or if its right property is bigger than window.innerWidth (means it's falling outside the window width), and if so, call the reappear function again:
function reappear(object) {
object.style.left = randomNum().toString() + "%";
object.style.top = randomNum().toString() + "%";
object.style.display = "block";
var rect = object.getBoundingClientRect();
if(rect.right > window.innerWidth || rect.bottom > window.innerHeight)
reappear(object);
}
Fiddle update: https://jsfiddle.net/y3m4ygow/2/
As you can see what's happening here is sometimes the object falling out of the document because (the width or height of it + the randomized percentage) is more than document width or height.
For example, say that document width is 1000px and the random number turned out to be 90% (=900px), since the box width is 200px, so you will have 100px out of the screen.
Now you have two solutions:
First: As #Sidd noted, you can check whether the box is in or out using getBoundingClientRect this will return a variable for you having two properties one is bottom which is the distance of the box from the upper edge of the document, the other property is height which is the distance of the box from the left border of the screen.
Now what you can do is compare those two values with the document width and height.
Now by adding those three lines you'll have your problem solved:
var rect = object.getBoundingClientRect(); // getting the properties
if(rect.right > window.innerWidth // comparing width
|| rect.bottom > window.innerHeight) // comparing height
{reappear(object);} // re-randomizing
https://jsfiddle.net/y3m4ygow/2/
this WILL work, but it might produce some flickering with some browsers, and i'm not very comfortable about calling a function inside itself.
Second Solution is: which is what I would prefer you to do, is by not using a percentage, but using a fixed height and width values.
you can get the current height and weight values from the window object and substract your box dimensions from it:
var cHeight = window.innerHeight - 200;
var cWidth = window.innerWidth - 200;
set those two as the maximum value for the top and the right.
function topRandomNum() {
var r = Math.random();
var y = (r * (cHeight - 0 + 1)) + 0;
var x = Math.floor(y);
return x;
}
function rightRandomNum() {
var r = Math.random();
var y = (r * (cWidth - 0 + 1)) + 0;
var x = Math.floor(y);
return x;
}
and here's the fiddle for the second solution: https://jsfiddle.net/uL24u0e4/
i have a simple jQ script:
a set width/height container
a landscape img (can be bigger or
smaller than container)
when a user mouses over the image, it pans
(no click/drag) until it reaches the end
The equation to move the img to the left is this:
-1(relative mouse-position)*(img width)/(container width)
This works fine, but it leaves a space one the mouse reaches the end of the img.
Fiddle
$("figure img").mousemove( function (e) {
var a = $(this).closest("figure"),
b = $(this).width(),
c = a.width(),
d = (e.clientX - a.offset().left);
$(this).css({
left: -1*(d*b/c)
}, 100);
});
can someone help? I want the img to be completely aligned to the right of the container once the mouse reaches the end.
The correct formula is: -1 * (d/c) * (b - c)
Or, more clearly: -1 * (mouseX / figureWidth) * (imgWidth - figureWidth)
(mouseX / figureWidth) represents the percent of the width of the figure that the mouse is positioned at. It will be a number between 0 and 1.
(imgWidth - figureWidth) represents the biggest X value you want to use to position the image at the opposite side.
Multiplying the percent by the total range of movement gives you the movement amount for the current mouse position!
Updated Fiddle
I suggest using more descriptive variable names such as figureWidth, imgWidth, mouseX etc. Not only will it be easier for you to understand, but it will be easier for people to answer.
This should work: http://jsfiddle.net/0zd5t1wf/4/
i just get the limit value for the left propriety of image (the image width - the figure box)
$("figure img").each( function () {
if($(this).width() >= ($(this).height() * 2.5)) {
$(this)
.attr("class", "panorama")
.mousemove( function (e) {
var a = $(this).closest("figure"),
b = $(this).width(),
c = a.width(),
d = (e.clientX - a.offset().left),
newLeft = -1*(d*b/c),
limitValue = parseInt($(this).width()) - parseInt($("figure").width());
if ( newLeft < 0 && (newLeft *-1) < limitValue ){
$(this).css({
left: newLeft
}, 100);
}
$("#hello").html('why');
});
}
});
I said “JavaScript”. I'm trying to make my own JavaScript custom scrollbar. It's almost successful. The problem is that I can't get an accurate multiplier for the scrolling speed. This is my code:
var elem = document.getElementById('scroll-area'),
track = elem.children[1],
thumb = track.children[0],
height = parseInt(elem.offsetHeight, 10),
cntHeight = parseInt(elem.children[0].offsetHeight, 10),
trcHeight = parseInt(track.offsetHeight, 10),
distance = cntHeight - height,
mean = 50, // For multiplier (go faster or slower)
current = 0;
elem.children[0].style.top = current + "px"; // Set default `top` value as `0` for initiation
thumb.style.height = Math.round(trcHeight * height / cntHeight) + 'px'; // Set the scrollbar thumb hight
var doScroll = function (e) {
// cross-browser wheel delta
e = window.event || e;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
// (1 = scroll-up, -1 = scroll-down)
// Always check the scroll distance, make sure that the scroll distance value will not
// increased more than the content height and/or less than zero
if ((delta == -1 && current * mean >= -distance) || (delta == 1 && current * mean < 0)) {
current = current + delta;
}
// Move element up or down by updating the `top` value
elem.children[0].style.top = (current * mean) + 'px';
thumb.style.top = 0 - Math.round(trcHeight * (current * mean) / cntHeight) + 'px';
e.preventDefault();
};
if (elem.addEventListener) {
elem.addEventListener("mousewheel", doScroll, false);
elem.addEventListener("DOMMouseScroll", doScroll, false);
} else {
elem.attachEvent("onmousewheel", doScroll);
}
And the markup:
<div id="scroll-area">
<div><!-- CONTENT --></div>
<span class="scrollbar-track"><span class="scrollbar-thumb"></span></span>
</div>
My problem is on mean = 50. When you scroll the container until the bottom of content, the red line that I made in the demo page should stop right at the bottom of the container, not higher than that.
Anyone have an idea for the accurate result?
PS: I also want to add a function that will enable user to scroll the content by dragging the scrollbar thumb. But I think I want to focus on this issue first. Thanks for your help.
DEMO: http://jsfiddle.net/tovic/2B8Ye/
You could have done this much much easier.
Instead of calculating the top property for the container you can set its scrollTop property, and you guess what - you don't have to check if the content moves upper than 0 or lower than height because scrollTop property can not be set to a value smaller than 0 or greater than container's height!
Here's your modified fiddle
Notice that I had to wrap everything with additional <div id="everything">...</div> so the scrollbar won't be scrolled together with the #scroll-area div.
Also I assume using clientHeight instead of offsetHeight for .scroll-content div to include padding in the cntHeight variable.
More info on the scrollTop property you can find in this article.