offsetting an html anchor to adjust for fixed head

2018-12-31 03:47发布

This question already has an answer here:

I am trying to clean up the way my anchors work. I have a header that is fixed to the top of the page, so when you link to an anchor elsewhere in the page, the page jumps so the anchor is at the top of the page, leaving the content behind the fixed header (I hope that makes sense). I need a way to offset the anchor by the 25px from the height of the header. I would prefer HTML or CSS, but Javascript would be acceptable as well.

28条回答
萌妹纸的霸气范
2楼-- · 2018-12-31 04:24

I'm facing this problem in a TYPO3 website, where all "Content Elements" are wrapped with something like:

<div id="c1234" class="contentElement">...</div>

and i changed the rendering so it renders like this:

<div id="c1234" class="anchor"></div>
<div class="contentElement">...</div>

And this CSS:

.anchor{
    position: relative;
    top: -50px;
}

The fixed topbar being 40px high, now the anchors work again and start 10px under the topbar.

Only drawback of this technique is you can no longer use :target.

查看更多
明月照影归
3楼-- · 2018-12-31 04:25

As this is a concern of presentation, a pure CSS solution would be ideal. However, this question was posed in 2012, and although relative positioning / negative margin solutions have been suggested, these approaches seem rather hacky, create potential flow issues, and cannot respond dynamically to changes in the DOM / viewport.

With that in mind I believe that using JavaScript is still (February 2017) the best approach. Below is a vanilla-JS solution which will respond both to anchor clicks and resolve the page hash on load (See JSFiddle). Modify the .getFixedOffset() method if dynamic calculations are required. If you're using jQuery, here's a modified solution with better event delegation and smooth scrolling.

(function(document, history, location) {
  var HISTORY_SUPPORT = !!(history && history.pushState);

  var anchorScrolls = {
    ANCHOR_REGEX: /^#[^ ]+$/,
    OFFSET_HEIGHT_PX: 50,

    /**
     * Establish events, and fix initial scroll position if a hash is provided.
     */
    init: function() {
      this.scrollToCurrent();
      window.addEventListener('hashchange', this.scrollToCurrent.bind(this));
      document.body.addEventListener('click', this.delegateAnchors.bind(this));
    },

    /**
     * Return the offset amount to deduct from the normal scroll position.
     * Modify as appropriate to allow for dynamic calculations
     */
    getFixedOffset: function() {
      return this.OFFSET_HEIGHT_PX;
    },

    /**
     * If the provided href is an anchor which resolves to an element on the
     * page, scroll to it.
     * @param  {String} href
     * @return {Boolean} - Was the href an anchor.
     */
    scrollIfAnchor: function(href, pushToHistory) {
      var match, rect, anchorOffset;

      if(!this.ANCHOR_REGEX.test(href)) {
        return false;
      }

      match = document.getElementById(href.slice(1));

      if(match) {
        rect = match.getBoundingClientRect();
        anchorOffset = window.pageYOffset + rect.top - this.getFixedOffset();
        window.scrollTo(window.pageXOffset, anchorOffset);

        // Add the state to history as-per normal anchor links
        if(HISTORY_SUPPORT && pushToHistory) {
          history.pushState({}, document.title, location.pathname + href);
        }
      }

      return !!match;
    },

    /**
     * Attempt to scroll to the current location's hash.
     */
    scrollToCurrent: function() {
      this.scrollIfAnchor(window.location.hash);
    },

    /**
     * If the click event's target was an anchor, fix the scroll position.
     */
    delegateAnchors: function(e) {
      var elem = e.target;

      if(
        elem.nodeName === 'A' &&
        this.scrollIfAnchor(elem.getAttribute('href'), true)
      ) {
        e.preventDefault();
      }
    }
  };

  window.addEventListener(
    'DOMContentLoaded', anchorScrolls.init.bind(anchorScrolls)
  );
})(window.document, window.history, window.location);
查看更多
不再属于我。
4楼-- · 2018-12-31 04:26

@AlexanderSavin's solution works great in WebKit browsers for me.

I additionally had to use :target pseudo-class which applies style to the selected anchor to adjust padding in FF, Opera & IE9:

a:target {
  padding-top: 40px
}

Note that this style is not for Chrome / Safari so you'll probably have to use css-hacks, conditional comments etc.

Also I'd like to notice that Alexander's solution works due to the fact that targeted element is inline. If you don't want link you could simply change display property:

<div id="myanchor" style="display: inline">
   <h1 style="padding-top: 40px; margin-top: -40px;">My anchor</h1>
</div>
查看更多
大哥的爱人
5楼-- · 2018-12-31 04:27

This takes many elements from previous answers and combines into a tiny (194 bytes minified) anonymous jQuery function. Adjust fixedElementHeight for the height of your menu or blocking element.

    (function($, window) {
        var adjustAnchor = function() {

            var $anchor = $(':target'),
                    fixedElementHeight = 100;

            if ($anchor.length > 0) {

                $('html, body')
                    .stop()
                    .animate({
                        scrollTop: $anchor.offset().top - fixedElementHeight
                    }, 200);

            }

        };

        $(window).on('hashchange load', function() {
            adjustAnchor();
        });

    })(jQuery, window);

If you don't like the animation, replace

$('html, body')
     .stop()
     .animate({
         scrollTop: $anchor.offset().top - fixedElementHeight
     }, 200);

with:

window.scrollTo(0, $anchor.offset().top - fixedElementHeight);

Uglified version:

 !function(o,n){var t=function(){var n=o(":target"),t=100;n.length>0&&o("html, body").stop().animate({scrollTop:n.offset().top-t},200)};o(n).on("hashchange load",function(){t()})}(jQuery,window);
查看更多
看淡一切
6楼-- · 2018-12-31 04:27

I ran into this same issue and ended up handling the click events manually, like:

$('#mynav a').click(() ->
  $('html, body').animate({
      scrollTop: $($(this).attr('href')).offset().top - 40
  }, 200
  return false
)

Scroll animation optional, of course.

查看更多
不再属于我。
7楼-- · 2018-12-31 04:28

A further twist to the excellent answer from @Jan is to incorporate this into the #uberbar fixed header, which uses jQuery (or MooTools). (http://davidwalsh.name/persistent-header-opacity)

I've tweaked the code so the the top of the content is always below not under the fixed header and also added the anchors from @Jan again making sure that the anchors are always positioned below the fixed header.

The CSS:

#uberbar { 
    border-bottom:1px solid #0000cc; 
    position:fixed; 
    top:0; 
    left:0; 
    z-index:2000; 
    width:100%;
}

a.anchor {
    display: block;
    position: relative;
    visibility: hidden;
}

The jQuery (including tweaks to both the #uberbar and the anchor approaches:

<script type="text/javascript">
$(document).ready(function() {
    (function() {
        //settings
        var fadeSpeed = 200, fadeTo = 0.85, topDistance = 30;
        var topbarME = function() { $('#uberbar').fadeTo(fadeSpeed,1); }, topbarML = function() { $('#uberbar').fadeTo(fadeSpeed,fadeTo); };
        var inside = false;
        //do
        $(window).scroll(function() {
            position = $(window).scrollTop();
            if(position > topDistance && !inside) {
                //add events
                topbarML();
                $('#uberbar').bind('mouseenter',topbarME);
                $('#uberbar').bind('mouseleave',topbarML);
                inside = true;
            }
            else if (position < topDistance){
                topbarME();
                $('#uberbar').unbind('mouseenter',topbarME);
                $('#uberbar').unbind('mouseleave',topbarML);
                inside = false;
            }
        });
        $('#content').css({'margin-top': $('#uberbar').outerHeight(true)});
        $('a.anchor').css({'top': - $('#uberbar').outerHeight(true)});
    })();
});
</script>

And finally the HTML:

<div id="uberbar">
    <!--CONTENT OF FIXED HEADER-->
</div>
....
<div id="content">
    <!--MAIN CONTENT-->
    ....
    <a class="anchor" id="anchor1"></a>
    ....
    <a class="anchor" id="anchor2"></a>
    ....
</div>

Maybe this is useful to somebody who likes the #uberbar fading dixed header!

查看更多
登录 后发表回答