Javascript 更改滚动上的URL哈希并保持后退按钮工作

Javascript 更改滚动上的URL哈希并保持后退按钮工作,javascript,jquery,html,url,back,Javascript,Jquery,Html,Url,Back,在一个带有固定顶部菜单和锚导航的单页布局上,我有一个“scrollspy”,可以更改scroll上的片段标识符,根据滚动位置为菜单链接提供一个活动类,并使用Velocity.js设置滚动到锚的动画 不幸的是,当点击浏览器后退按钮时,它会引导我完成滚动方式的所有步骤,这意味着我加载网站,向下和向上滚动一点点,然后频繁点击后退按钮,浏览器也会向下和向上滚动,但不会转到最后访问的id或返回浏览器历史记录 //DOM上的jQuery就绪 //使用VelocityJS的页面内滚动动画 // ------

在一个带有固定顶部菜单和锚导航的单页布局上,我有一个“scrollspy”,可以更改scroll上的片段标识符,根据滚动位置为菜单链接提供一个活动类,并使用Velocity.js设置滚动到锚的动画

不幸的是,当点击浏览器后退按钮时,它会引导我完成滚动方式的所有步骤,这意味着我加载网站,向下和向上滚动一点点,然后频繁点击后退按钮,浏览器也会向下和向上滚动,但不会转到最后访问的id或返回浏览器历史记录

//DOM上的jQuery就绪
//使用VelocityJS的页面内滚动动画
// ------------------------------------------------ //
// https://john-dugan.com/fixed-headers-with-hash-links/
$('.menu-a')。在('单击')上,函数(e){
var hash=this.hash,
$hash=$(哈希)
addHash=函数(){
window.location.hash=散列;
};      
$hash.velocity(“滚动”{duration:700,easing:[.4,21,35,1],complete:addHash});
e、 预防默认值();
});
//ScrollSpy用于菜单项和片段标识符
// ------------------------------------------------ //
// https://jsfiddle.net/mekwall/up4nu/
$menuLink=$('.menu-a')
var lastId,
//与菜单项相对应的锚定
scrollItems=$menuLink.map(函数(){
var item=$($(this.attr(“href”));
if(item.length){return item;}
});
$(窗口)。滚动(函数(){
//获取容器滚动位置
var fromTop=$(this).scrollTop()+30;//或#导航高度的值
//获取当前滚动项目的id
var cur=scrollItems.map(函数(){
if($(this).offset().top
使用上述代码,以下代码可以正常工作:

  • 当使用VelocityJS为滚动动画单击菜单链接时,哈希或片段标识符会很好地更新

  • 活动类在滚动时提供给相应的菜单链接

  • 当滚动而不是单击菜单链接时,片段标识符也会很好地更新

问题
第1部分:当你在小提琴上滚动一小点,然后点击后退按钮时,你会看到滚动条以完全相同的方式“移动”,记住所做的滚动

我需要“后退”按钮正常工作。 a) 返回浏览器历史记录并返回您所在的页面/站点,然后 b) 单击锚定链接(i),然后单击锚定链接(ii),然后单击后退按钮后,页面应返回锚定链接(i)

第2部分:由于IE8不支持
历史.pushState
,我正在寻找一种方法来使用
window.location.hash=$(this.attr('id')取而代之。无论我在代码末尾尝试了什么,我都无法获得
window.location.hash=$(this.attr('id')开始工作。我真的不想使用HistoryJS或其他东西,但我有兴趣学习并自己写

除了后退按钮损坏行为,我想要的所有其他行为都已经存在,现在我只需要修复后退按钮行为

编辑 我想我可能已经找到了一个解决方案,将测试,然后详细回答,如果我得到这个工作

相关:



要回答问题的第一部分,如果不想污染浏览器的历史记录,可以使用
history.replaceState()
而不是
history.pushState()
。当
pushState
更改URL并将新条目添加到浏览器的历史记录时,
replaceState
在修改当前历史记录条目而不是添加新条目时更改URL


还有一篇很好的文章介绍了
pushState
replaceState

之间的区别,对于我决定包含的较旧的浏览器,通过这篇文章,我获得了所需的行为(或多或少)

答案是:
-用于菜单项的滚动间谍,并将和活动类设置为滚动上的菜单项
-scroll spy也适用于URL哈希,根据当前滚动到的部分设置正确的哈希值
-一种滚动停止函数,用于检查用户何时停止滚动,然后从当前活动菜单项中获取值并将其设置为当前URL哈希。这样做的目的是在滚动时不捕获节的锚,而只捕获用户滚动到的节的锚。
-单击菜单链接以及使用后退和前进按钮时,Velocity.js会平滑滚动
-对加载和重新加载页面作出反应的函数,这意味着,如果您输入带有特定URL哈希的页面,则该页面将设置滚动到该部分的动画,如果重新加载页面,则该页面将设置滚动到当前部分顶部的动画

代码是一个粗略的草图,可能需要一些调整,这只是为了演示的目的。我想我还是一个初学者,请指出明显的错误,这样我就可以从中学习。还包括指向代码片段来源的所有链接

// In-Page Scroll Animation to Menu Link on click
// ------------------------------------------------ //
// https://john-dugan.com/fixed-headers-with-hash-links/
// https://stackoverflow.com/questions/8355673/jquery-how-to-scroll-an-anchor-to-the-top-of-the-page-when-clicked
// http://stackoverflow.com/a/8579673/1010918
// $('a[href^="#"]').on('click', function(e) {
$('.menu-a').on('click', function(e) {

    // default = make hash appear right after click
    // not default = make hash appear after scrolling animation is finished
    e.preventDefault();

    var hash  = this.hash,
        $hash = $(hash)

    $hash.velocity("scroll", { duration: 700, easing: [ .4, .21, .35, 1 ], queue: false });
});



// In-Page Scroll Animation to Hash on Load and Reload
// ----------------------------------------------------------- //
// https://stackoverflow.com/questions/680785/on-window-location-hash-change
// hashchange triggers popstate !
$(window).on('load', function(e) {

    var hash  = window.location.hash;
    console.log('hash on window load '+hash);
    $hash = $(hash)

    $hash.velocity("scroll", { duration: 500, easing: [ .4, .21, .35, 1 ], queue: false });

    // if not URL hash is present = root, go to top of page on reload
    if (!window.location.hash){
        $('body').velocity("scroll", { duration: 500, easing: [ .4, .21, .35, 1 ], queue: false });
    }   
});



// In-Page Scroll Animation to Hash on Back or Forward Button
// ---------------------------------------------------------- //
$('.menu-a').on('click', function(e) {  
    e.preventDefault(); 
    // keep the link in the browser history
    // set this separately from scrolling animation so it works in IE8
    history.pushState(null, null, this.href);
    return false
}); 
$(window).on('popstate', function(e) {
    // alert('popstate fired');
    $('body').append('<div class="console1">popstate fired</div>');
    $('.console1').delay(1000).fadeOut('slow');

    if (!window.location.hash){
        $('body').append('<div class="console2">no window location hash present</div>');

        $('body').velocity("scroll", { duration: 700, easing: [ .4, .21, .35, 1 ], queue: false });

        $('.console2').delay(1000).fadeOut('slow');
    }

    console.log('window.location.hash = '+window.location.hash);
    var hash  = window.location.hash;
    $hash = $(hash)

    $hash.velocity("scroll", { duration: 700, easing: [ .4, .21, .35, 1 ], queue: false });
});



// ScrollSpy for Menu items only - gives selected Menu items the active class
// ------------------------------------------------------------------------ //
// Does not update fragment identifier in URL https://en.wikipedia.org/wiki/Fragment_identifier
// https://jsfiddle.net/mekwall/up4nu/
    var lastId,

    // Anchors corresponding to menu items
    scrollItems = $menuLink.map(function(){
        var item = $($(this).attr("href"));
        // console.table(item);
        if (item.length) { return item; }
    });

    // Give menu item the active class on load of corresponding item
    function scrollSpy () {

        // Get container scroll position
        var fromTop = $(this).scrollTop()+ $menuButtonHeight;

        // Get id of current scroll item
        var cur = scrollItems.map(function(){
            if ($(this).offset().top < fromTop)
            return this;
        });

        // Get the id of the current element
        cur = cur[cur.length - 1];
        var id = cur && cur.length ? cur[0].id : "";

        if (lastId !== id) {
            lastId = id;
            // Set/remove active class
            $menuLink
            .parent().removeClass("active").end()
            .filter("[href='#"+id+"']").parent().addClass("active");
        }

        // Active Menu Link href Attribute
        activeMenuLinkHref = $('.menu-li.active > .menu-a').attr('href');
        // console.log('activeMenuLinkHref '+activeMenuLinkHref);   
    }
    scrollSpy()

    $(window).scroll(function(e){
        scrollSpy()
    });



// On Stop of Scrolling get Active Menu Link Href and set window.location.hash
// --------------------------------------------------------------------------- //

// Scroll Stop Function
//---------------------//
// https://stackoverflow.com/questions/8931605/fire-event-after-scrollling-scrollbars-or-mousewheel-with-javascript
// http://stackoverflow.com/a/8931685/1010918
// http://jsfiddle.net/fbSbT/1/
// http://jsfiddle.net/fbSbT/87/

(function(){ 
    var special = jQuery.event.special,
        uid1 = 'D' + (+new Date()),
        uid2 = 'D' + (+new Date() + 1); 
    special.scrollstart = {
        setup: function() { 
            var timer,
                handler =  function(evt) { 
                    var _self = this,
                        _args = arguments; 
                    if (timer) {
                        clearTimeout(timer);
                    } else {
                        evt.type = 'scrollstart';
                        // throws "TypeError: jQuery.event.handle is undefined"
                        // jQuery.event.handle.apply(_self, _args);
                        // solution
                        // http://stackoverflow.com/a/20809936/1010918
                        // replace jQuery.event.handle.apply with jQuery.event.dispatch.apply
                        jQuery.event.dispatch.apply(_self, _args);
                    } 
                    timer = setTimeout( function(){
                        timer = null;
                    }, special.scrollstop.latency); 
                }; 
            jQuery(this).bind('scroll', handler).data(uid1, handler); 
        },
        teardown: function(){
            jQuery(this).unbind( 'scroll', jQuery(this).data(uid1) );
        }
    }; 
    special.scrollstop = {
        latency: 250,
        setup: function() { 
            var timer,
                    handler = function(evt) { 
                    var _self = this,
                        _args = arguments; 
                    if (timer) {
                        clearTimeout(timer);
                    }
                     timer = setTimeout( function(){ 
                        timer = null;
                        evt.type = 'scrollstop';                        
                        // throws "TypeError: jQuery.event.handle is undefined"
                        // jQuery.event.handle.apply(_self, _args);
                        // solution
                        // http://stackoverflow.com/a/20809936/1010918
                        // replace jQuery.event.handle.apply with jQuery.event.dispatch.apply
                        jQuery.event.dispatch.apply(_self, _args); 
                    }, special.scrollstop.latency); 
                }; 
            jQuery(this).bind('scroll', handler).data(uid2, handler); 
        },
        teardown: function() {
            jQuery(this).unbind( 'scroll', jQuery(this).data(uid2) );
        }
    };

})();



// Scroll Stop Function Called
//----------------------------//

$(window).on("scrollstop", function() {

    // window.history.pushState(null, null, activeMenuLinkHref);
    // window.history.replaceState(null, null, activeMenuLinkHref);

    // http://stackoverflow.com/a/1489802/1010918 //
    // works best really
    hash = activeMenuLinkHref.replace( /^#/, '' );
    console.log('hash '+hash);
    var node = $( '#' + hash );
    if ( node.length ) {
      node.attr( 'id', '' );
      // console.log('node.attr id'+node.attr( 'id', '' ));
    }
    document.location.hash = hash;
    if ( node.length ) {
      node.attr( 'id', hash );
    }
});
// In-Page Scroll Animation to Menu Link on click
// ------------------------------------------------ //
// https://john-dugan.com/fixed-headers-with-hash-links/
// https://stackoverflow.com/questions/8355673/jquery-how-to-scroll-an-anchor-to-the-top-of-the-page-when-clicked
// http://stackoverflow.com/a/8579673/1010918
// $('a[href^="#"]').on('click', function(e) {
$('.menu-a').on('click', function(e) {

    // default = make hash appear right after click
    // not default = make hash appear after scrolling animation is finished
    e.preventDefault();

    var hash  = this.hash,
        $hash = $(hash)

    $hash.velocity("scroll", { duration: 700, easing: [ .4, .21, .35, 1 ], queue: false });
});



// In-Page Scroll Animation to Hash on Load and Reload
// ----------------------------------------------------------- //
// https://stackoverflow.com/questions/680785/on-window-location-hash-change
// hashchange triggers popstate !
$(window).on('load', function(e) {

    var hash  = window.location.hash;
    console.log('hash on window load '+hash);
    $hash = $(hash)

    $hash.velocity("scroll", { duration: 500, easing: [ .4, .21, .35, 1 ], queue: false });

    // if not URL hash is present = root, go to top of page on reload
    if (!window.location.hash){
        $('body').velocity("scroll", { duration: 500, easing: [ .4, .21, .35, 1 ], queue: false });
    }   
});



// In-Page Scroll Animation to Hash on Back or Forward Button
// ---------------------------------------------------------- //
$('.menu-a').on('click', function(e) {  
    e.preventDefault(); 
    // keep the link in the browser history
    // set this separately from scrolling animation so it works in IE8
    history.pushState(null, null, this.href);
    return false
}); 
$(window).on('popstate', function(e) {
    // alert('popstate fired');
    $('body').append('<div class="console1">popstate fired</div>');
    $('.console1').delay(1000).fadeOut('slow');

    if (!window.location.hash){
        $('body').append('<div class="console2">no window location hash present</div>');

        $('body').velocity("scroll", { duration: 700, easing: [ .4, .21, .35, 1 ], queue: false });

        $('.console2').delay(1000).fadeOut('slow');
    }

    console.log('window.location.hash = '+window.location.hash);
    var hash  = window.location.hash;
    $hash = $(hash)

    $hash.velocity("scroll", { duration: 700, easing: [ .4, .21, .35, 1 ], queue: false });
});



// ScrollSpy for Menu items only - gives selected Menu items the active class
// ------------------------------------------------------------------------ //
// Does not update fragment identifier in URL https://en.wikipedia.org/wiki/Fragment_identifier
// https://jsfiddle.net/mekwall/up4nu/
    var lastId,

    // Anchors corresponding to menu items
    scrollItems = $menuLink.map(function(){
        var item = $($(this).attr("href"));
        // console.table(item);
        if (item.length) { return item; }
    });

    // Give menu item the active class on load of corresponding item
    function scrollSpy () {

        // Get container scroll position
        var fromTop = $(this).scrollTop()+ $menuButtonHeight;

        // Get id of current scroll item
        var cur = scrollItems.map(function(){
            if ($(this).offset().top < fromTop)
            return this;
        });

        // Get the id of the current element
        cur = cur[cur.length - 1];
        var id = cur && cur.length ? cur[0].id : "";

        if (lastId !== id) {
            lastId = id;
            // Set/remove active class
            $menuLink
            .parent().removeClass("active").end()
            .filter("[href='#"+id+"']").parent().addClass("active");
        }

        // Active Menu Link href Attribute
        activeMenuLinkHref = $('.menu-li.active > .menu-a').attr('href');
        // console.log('activeMenuLinkHref '+activeMenuLinkHref);   
    }
    scrollSpy()

    $(window).scroll(function(e){
        scrollSpy()
    });



// On Stop of Scrolling get Active Menu Link Href and set window.location.hash
// --------------------------------------------------------------------------- //

// Scroll Stop Function
//---------------------//
// https://stackoverflow.com/questions/8931605/fire-event-after-scrollling-scrollbars-or-mousewheel-with-javascript
// http://stackoverflow.com/a/8931685/1010918
// http://jsfiddle.net/fbSbT/1/
// http://jsfiddle.net/fbSbT/87/

(function(){ 
    var special = jQuery.event.special,
        uid1 = 'D' + (+new Date()),
        uid2 = 'D' + (+new Date() + 1); 
    special.scrollstart = {
        setup: function() { 
            var timer,
                handler =  function(evt) { 
                    var _self = this,
                        _args = arguments; 
                    if (timer) {
                        clearTimeout(timer);
                    } else {
                        evt.type = 'scrollstart';
                        // throws "TypeError: jQuery.event.handle is undefined"
                        // jQuery.event.handle.apply(_self, _args);
                        // solution
                        // http://stackoverflow.com/a/20809936/1010918
                        // replace jQuery.event.handle.apply with jQuery.event.dispatch.apply
                        jQuery.event.dispatch.apply(_self, _args);
                    } 
                    timer = setTimeout( function(){
                        timer = null;
                    }, special.scrollstop.latency); 
                }; 
            jQuery(this).bind('scroll', handler).data(uid1, handler); 
        },
        teardown: function(){
            jQuery(this).unbind( 'scroll', jQuery(this).data(uid1) );
        }
    }; 
    special.scrollstop = {
        latency: 250,
        setup: function() { 
            var timer,
                    handler = function(evt) { 
                    var _self = this,
                        _args = arguments; 
                    if (timer) {
                        clearTimeout(timer);
                    }
                     timer = setTimeout( function(){ 
                        timer = null;
                        evt.type = 'scrollstop';                        
                        // throws "TypeError: jQuery.event.handle is undefined"
                        // jQuery.event.handle.apply(_self, _args);
                        // solution
                        // http://stackoverflow.com/a/20809936/1010918
                        // replace jQuery.event.handle.apply with jQuery.event.dispatch.apply
                        jQuery.event.dispatch.apply(_self, _args); 
                    }, special.scrollstop.latency); 
                }; 
            jQuery(this).bind('scroll', handler).data(uid2, handler); 
        },
        teardown: function() {
            jQuery(this).unbind( 'scroll', jQuery(this).data(uid2) );
        }
    };

})();



// Scroll Stop Function Called
//----------------------------//

$(window).on("scrollstop", function() {

    // window.history.pushState(null, null, activeMenuLinkHref);
    // window.history.replaceState(null, null, activeMenuLinkHref);

    // http://stackoverflow.com/a/1489802/1010918 //
    // works best really
    hash = activeMenuLinkHref.replace( /^#/, '' );
    console.log('hash '+hash);
    var node = $( '#' + hash );
    if ( node.length ) {
      node.attr( 'id', '' );
      // console.log('node.attr id'+node.attr( 'id', '' ));
    }
    document.location.hash = hash;
    if ( node.length ) {
      node.attr( 'id', hash );
    }
});
.console1{
    position: fixed;
    z-index: 9999;
    top:0;
    right:0;    
    background-color: #fff;
    border: 2px solid red;
}

.console2{
    position: fixed;
    z-index: 9999;
    bottom:0;
    right:0;    
    background-color: #fff;
    border: 2px solid red;
}