質問

I have made the following messy javascript that should smoothly scroll the page 128px above a div.

The script scrolls the page to the right place, but without being smooth.

This is because i can not get the function setTimeout working properly.

function pageScroll()
{   
    var from_top=$("#body_box_title_skills").offset().top;  

    scroll_speed = 15;
    goto_px = from_top - 128;

    times_scroll = goto_px / scroll_speed; 
    times_scroll = times_scroll.toString();

    times_scroll_array = times_scroll.split(".");
    times_scroll_array[1] = "0."+times_scroll_array[1];

    px_scroll_extra = times_scroll_array[1] * scroll_speed;

    scrollto_px = 0;

    while (times_scroll_array[0] >= 1)
    {
        scrollto_px = scrollto_px + scroll_speed;
        setTimeout(function(){window.scrollTo(0,scrollto_px)}, 1000);
        times_scroll_array[0]--;
    }
    scrollto_px = scrollto_px + px_scroll_extra;
    window.scrollTo(0,scrollto_px);

}
役に立ちましたか?

解決

Your problem is that you are closing over the same variable scrollto_px in your setTimeout. When the setTimeout function actually runs, the value of scrollto_px is the same value it had at the end of the loop, so you just immediately scroll to that last value. You need to copy to a new variable that your setTimeout can close over.

Your second problem is that you are setting a timeouts that all trigger at almost the same time. You need to chain your timeouts so that the first timeout occurs after 1000 seconds, and the next occurs 1000 seconds after that. Note that setTimeout isn't the same as sleep in other languages. It doesn't suspend execution.

Your third problem is that you have these lines:

scrollto_px = scrollto_px + px_scroll_extra;
window.scrollTo(0,scrollto_px);

After your while loop which will automatically jump all the way to the end.

Here's one way to fix the problems you are having. I removed your while loop and added a function that will call itself again after a timeout:

function doScroll() {
    if (times_scroll_array[0] >= 1) {
        scrollto_px = scrollto_px + scroll_speed;
        console.log(scrollto_px);
        window.scrollTo(0, scrollto_px);
        times_scroll_array[0]--;
        setTimeout(doScroll, 100);
    } else {
        scrollto_px = scrollto_px + px_scroll_extra;
        window.scrollTo(0, scrollto_px);
    }
}
doScroll();

You can see it running here (I changed the timing because 1 second between steps was really slow - but you can adjust it as needed): http://jsfiddle.net/KqRwx/

他のヒント

The problem is that when you run the that function, the whole function is run in its entirety before any of the timeouts are triggered. What you really want is something like this

function pageScroll(goto_px) {   
    var current_scroll = ( (window.pageYOffset || document.documentElement.scrollTop) - (document.documentElement.clientTop || 0) );
    var scroll_speed = 15;
    // if we are less then 15 px from the destination, scroll whatever is left to go
    if ( Math.abs(goto_px - current_scroll) <= scroll_speed) {
        window.scrollTo(0, goto_px);
    } else {
        // scroll 15 pixels in the direction where the destination is
        window.scrollTo(0, current_scroll + ((current_scroll < goto_px ? 1 : -1) * scroll_speed) );
        window.setTimeout(function(){pageScroll(goto_px)}, 1000);
    }
}


pageScroll( $("#body_box_title_skills").offset().top - 128 );

Basically, call the function with the destination, the function calculates the current position of the window, and scrolls toward the destination once, and creates a timeout calling itself. This function is by no means perfect though, as you can easily get a endless loop by invoking scrolls in opposite directions in which case you will be just scrolling back and fourth in to infinity. Additional steps should be taken to ensure that only one instance is running at any one time.

Also manually scrolling the page in the middle of the smooth scroll will cause the page to continue scrolling until the destination is reached, and listening to the scroll event to cancel the timeout does not seem to be a feasible solution due to window.scrollTo also triggering it. One idea would be to keep track of current scroll and cancel the timeout if it jumps unexpectedly.

An additional note: I'm not sure if requestAnimationFrame is applicable here, but to get consistent, as smooth as possible animations in general this is what you want to use. Might be worth reading.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top