I made a image scrolling with the mouse.
The image scroll to a position based on the mouse position percentage of the window height.
$(imageContainer).mouseenter(function(e){
var scrollingTo = ((e.pageY/$(this).height())-.083) * ( $(imageContainer).prop('scrollHeight') - $(imageContainer).height() );
hijacked = true;
$(imageContainer).animate({scrollTop:scrollingTo},300,function(){hijacked=false;});
}).mousemove(function(e){
if(hijacked) return;
var scrollingTo = ((e.pageY/$(this).height())-.083) * ( $(imageContainer).prop('scrollHeight') - $(imageContainer).height() );
$(imageContainer).scrollTop(scrollingTo);
});
So. in that line
$(imageContainer).animate({scrollTop:scrollingTo},300,function(){hijacked=false;});
I want that scrollingTo change. Because during the animation, the user can move the mouse, changing the scrollingTo variable.
Alright, I managed to cook together a hacky way of dynamically altering an animation. My understanding of the internal animation queue for jQuery is not great, but as far as I know there's no way to alter a queued animation, other than to make it stop. Anyway, here's the key code for an example that alters position, which should be adaptable to scrolling (in fiddle form):
$(document).ready(function () {
var last_update = 0;
$(document).on("mousemove", function (e) {
if (Date.now() - last_update > 50) {
$mover = $("#mover");
$mover.stop();
$mover.animate({ left: e.pageX, top: e.pageY}, 200, "linear");
last_update = Date.now();
}
});
});
There were a couple of tricks to make it work, I'll go through them and try to explain how I believe they could be adapted to scrolling:
The main idea is that on mousemove, the prior event is cancelled, and a new one is started.
I don't believe this will require any changes for scrolling.
Some forms of animation accelerate/decelerate over the course of the animation - it's too hard to preserve this in a constantly changing animation (at least without writing a custom animation function), so the animation easing is set to "linear".
rapidly changing animations takes time (especially for an event as common as mousemove), so there's a limit on how often the animation can change. Before a change to the animation is made, it's ensured that no changes have been made in the last .05 seconds (this is done with "last_update").
I believe if you just swap out the animation properties for your own (scrollTop), this should do what you're looking for.
Related
I'm trying to position two images by changing their top margin based on the scroll position.
The margins have a max value as well.
Everything works just fine but I have a warning on the console, saying that this solution can cause a jittery scrolling effect in browsers with asynchronous scrolling.
My first question is, should I worry about this?
Also, this is literally my first few lines of javascript and I'm not sure if this solution is good enough, so any advice is appreciated.
It just looks so simple I feel like there is a catch.
I could do it by adding classes to the images and set the margins in CSS, but it would be a lot longer code I guess.
I'm trying to do this strictly with js and CSS grid, just to learn to solve problems with limited tools.
The images are in a div, which is in a grid cell.
window.addEventListener("scroll", function () {
myMargin = 0.011 * window.scrollY;
if (myMargin < 3.4) { //max margin is 3.4% for myImg1
myImg1.style.marginTop = animMargin + "%";
myImg2.style.marginTop = animMargin / 2.7 + "%"; //myImg2 moves on a different scale
} else {
myImg1.style.marginTop = "3.4%"; //when the max value reached the margin is fixed
myImg2.style.marginTop = "1.25%";
}
});
Scrolling handlers can be intensive and put performance strain on the page as they will fire far more times than your handler actually needs. This ends up causing choppy/lag when scrolling as the browser may need to repaint in response to your handler.
A common technique is to throttle or debouce the handler.
Throttle:
only invokes func at most once per every x milliseconds.
Debouce:
delays invoking func until after x milliseconds have elapsed since the last time the debounced function was invoked.
The warning you are getting is actually fine, but you may benefit from using a throttled callback and increase the wait time to the max that is suitable for your needs - so least times it is called in order for it to work for you.
Demo using lodash throttle
// only call the handler once every 200ms
const throttledScroll = _.throttle(() => {
console.info('throttled', window.scrollY);
}, 200);
window.addEventListener('scroll', throttledScroll);
html,
body {
height: 300vh;
}
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.min.js"></script>
I have 2 divs (left and right) and i want to scroll the left based on the right.
https://jsfiddle.net/3jdsazhg/2/
This works fine on desktop, but when i change to mobile, it's not smooth anymore...
This can be noticed very easily, by changing
_left.style.top = _content.scrollTop - (_content.scrollTop * ratioLeftRight) + 'px';
to
_left.style.top = _content.scrollTop + 'px';
Where it should act as a fixed positioned div
I would like to know the exact reason why this isn't smooth... I know that it's not the animation. Simple animation on the div is smooth, the issue comes up when it's based on scroll.
How can i make this animation smooth?
It's probably choppy because it's being fired ALOT when being scrolled, in fact i'm pretty sure IOS mobile pauses the javascript execution whilst the user is scrolling.
Instead I'd suggest using an interval, you could tweak the time between each interval to what feels good for your use-case.
Although it may seem intensive that it's firing this logic every X millisecond when using the scroll event you could be firing the event off hundreds of times per second, which is going to be far more intensive and noticeable to a user using a device with limit processing power.
(function () {
var interval = null,
//Currently set at 0.4 seconds, play with the code
//and change this value to see what works best for
//this use-case
time_between_interval = 400;
setInterval(scrollLogic, time_between_interval);
function scrollLogic () {
//The function body of what you're assigning
//to the scroll event.
}
//I have omitted clearing the interval but you would want to do that, perhaps on page change, or something.
//clearInterval(interval);
})();
I finally managed to think out a solution.
From my point of view, i'm guessing the mobile view fires the scroll event less often and because we are scrolling the wrapper, we first scroll the whole page and then scroll back with js the left part and because it's different from the desktop version, this issue becomes visible...
The solution was to change the left side to fixed position, and substract from the top instead of adding to it.
_left.style.top = -(_content.scrollTop * ratioLeftRight) + 'px';
I am working with js greensock animation library. I have an animation that pauses when you start to resize the window and continues when you stop resizing the window.
I am now attempting to make the animation (Start over) or from a (certain point) if the window width passes 865px in either direction.
The current code im working with works "Okay" it refreshes the entire page upon reaching 865px in either direction. However, instead of reloading entire page I would just simply like to "restart" the animation.
Here is a working CODEPEN
And here is the snippet of JS that I am working with and referring to.
//Reload Animation if window width crosses over 865px either way.
var ww = $(window).width();
var limit = 865;
function refresh() {
ww = $(window).width();
var w = ww<limit ? (location.reload(true)) : ( ww>limit ? (location.reload(true)) : ww=limit );
}
var tOut;
$(window).resize(function() {
var resW = $(window).width();
clearTimeout(tOut);
if ( (ww>limit && resW<limit) || (ww<limit && resW>limit) ) {
tOut = setTimeout(refresh, 0);
}
});
Thanks!
There is no need to refresh the whole page. There are several ways to manipulate a Tween or Timeline.
If you want to play it from the beginning the easiest way would be:
myTimeline.restart()
Alternatively you could change the progress and play the animation from that point:
myTimeline.progress( myProgress )
The placeholder myProgress can be 0 marking the start and 1 being the end of the Tween (or any number inbetween). So the code to play it from the start would look something like this:
myTimeline.progress( 0 ).play()
A third way would be .play( time )
This is similar to .progress() with the difference that myProgress is a relative measurement and time is an absolute one. So if the duration of myTimeline is 8 seconds myTimeline.progress(0.5).play() would have the same result as myTimeline.play(4)
EDIT:
Here is a fork of your Pen. I believe it does what you are looking for:
http://codepen.io/anon/pen/adqypP
You can call .restart() which restarts and begins playing forward from the beginning when using TweenMax or TweenLite.
API infos here:
https://greensock.com/docs/TweenMax/restart()
SCENARIO
I have developed a function with jQuery which listens to the user's mouse input (based on the mousewheel plugin).
I analyze the user's input with a function, and alter the default behavior of the mouse, so that it scrolls a given px value with an animation.
PROBLEM
There are div containers in the webpage that transform its size when hovered.
This causes my original mousewheel animation to delay its action for a little time (more or less, half a second). If a div is hovered, and quickly afterwards the mousewheel is rolled, the effect won't run 100% smoothly (it will cause a little lag while the scroll animation is executing, and right afterwards, it will show the animation, which was already running).
If I delete the transition in the containers, the problem is solved. However, I would like to keep the original CSS intact, and run my original animation smoothly.
How can I accomplish this?
JSFIDDLE
http://jsfiddle.net/kouk/z7p0vxpg/
JS CODE
$(function () {
function wheel($div, deltaY) {
if (deltaY == -1) {
var dest = ($(document).scrollTop()+500);
$('html,body').animate({scrollTop: dest}, 1000);
return false;
} else if (deltaY == 1) {
var dest = ($(document).scrollTop()-500);
$('html,body').animate({scrollTop: dest}, 1000);
return false;
}
}
$('html').bind('mousewheel', function (event, delta, deltaX, deltaY) {
if ($('html,body').is(":animated")){
return false;
}
if ( (delta > -2) && (delta < 2) ) {
wheel($(this), deltaY);
event.preventDefault();
console.log(delta);
}
});
});
This is a common problem with the animate() function. The previous animations are in the queue and lagging behind. You should empty the animation queue before starting the next animation to avoid the "lag" feeling.
There are two functions which let you do that : finish() and stop() . I recommend using finish as it will stop the running animation )and remove all queued animations. This was you can immediately start your latest animation.
A user doesn't necessarily want to wait for his previous animation which he has already started a new action.
Here's some sample code:
$('html,body').dequeue().animate({scrollTop: dest}, 1000);
See if the behavior is as your expected now.
And your code (updated with finish()) - http://jsfiddle.net/z7p0vxpg/15/
I have a horizontally scrolling website, and I have a block that I want to stay in frame at all times as the user scrolls right. It looks perfectly smooth in webkit browsers, but is crazy jagged in Firefox and I don't really care about IEs.
function fixMyId(){
$('#myId').css({'margin-left': 150 + $(window).scrollLeft()});
}
function fixMyIdAlt(){
$('#myId').stop().animate({'margin-left': 150 + $(window).scrollLeft()}, 300);
}
And then I have it triggered on window scroll.
What would be a best way to average out the scrolling, so that maybe every so many seconds or pixels of scrolling it fires the function, or upon stopping the scrolling it animates the block into place? I tried playing with delay() but that doesn't do anything. And this one just looks stupid (plus I have no idea what the overhead of this kind of crunching is):
function fixMyIdStupid(){
window.scrollCounter++;
if(window.scrollCounter % 20 == 0) $('#myId').stop().animate({'margin-left': 150 + $(window).scrollLeft()}, 300);
}
So what do I do? setTimeout and setInterval may be required, but those always make my head hurt.
EDIT: Here's a jsfiddle of it in action: http://jsfiddle.net/xsxSq/
The #f0f square is the #myId.
I tried to do such things as well, problem is that the scroll event isn't fired as much as you want. A nice workaround was subscribing the calculation function to the mousemove event, so it triggers A LOT. But on the other hand, I came up with another solution.
Why not turn things around and ask yourself:
Lets make it a position:fixed object and calculate what happens on resize. Because you actually are trying to create a position-x:fixed; and a position-y:absolute;
I actually did the following for the opposite kind of thing. A block that has to be exactly in the middle of the x-document, but in the y it was fixed.
$(document).ready(function ()
{
replaceFixed();
$(window).resize(replaceFixed);
$('#content').ajaxSuccess(replaceFixed);
$(window).scroll(replaceFixed);
function replaceFixed()
{
var jEl = $('#centeredFixedContainer');
var winW = $(window).width();
var docW = $(document).width();
var scrL = $(window).scrollLeft();
var divW = jEl.width();
var result = 0;
// window bigger than the element
if(winW > divW)
{
result = -scrL + ((docW-winW)/2);
}
else
{
result = $('#mainContainer').offset().left - scrL;
}
jEl.css('left',result);
}
});
Copying this code will not give you the solution, but will indicate another way to look at your problem.