web-dev-qa-db-fra.com

compensation d'une ancre HTML pour l'ajustement de l'en-tête fixe

J'essaie de nettoyer le fonctionnement de mes ancres. J'ai un en-tête qui est fixé en haut de la page. Ainsi, lorsque vous créez un lien vers une ancre située ailleurs dans la page, la page saute de manière à ce que l'ancre se trouve en haut de la page, laissant le contenu derrière l'en-tête fixe (j'espère). ça a du sens). J'ai besoin d'un moyen de compenser l'ancre par le 25px de la hauteur de l'en-tête. Je préférerais HTML ou CSS, mais javascript serait également acceptable.

1011
Matt Dryden

Vous pouvez simplement utiliser CSS sans javascript.

Donnez à votre ancre un cours:

<a class="anchor" id="top"></a>

Vous pouvez ensuite positionner l'ancre avec un décalage supérieur ou inférieur à l'endroit où elle apparaît réellement sur la page, en en faisant un élément de bloc et en le positionnant de manière relative. -250px placera l'ancre jusqu'à 250px

a.anchor {
    display: block;
    position: relative;
    top: -250px;
    visibility: hidden;
}
1095
Jan

J'ai trouvé cette solution:

<a name="myanchor">
    <h1 style="padding-top: 40px; margin-top: -40px;">My anchor</h1>
</a>

Cela ne crée aucune lacune dans le contenu et les liens d'ancrage fonctionnent vraiment bien.

316
Alexander Savin

Comme il s’agit d’un problème de présentation, une solution CSS pure serait idéale. Cependant, cette question a été posée en 2012 et, bien que des solutions de positionnement relatif/marge négative aient été suggérées, ces approches semblent plutôt compliquées, créent des problèmes de flux et ne peuvent pas répondre de manière dynamique aux modifications apportées au DOM/viewport.

Dans cet esprit, j'estime que l'utilisation de JavaScript est toujours (février 2017) la meilleure approche. Vous trouverez ci-dessous une solution Vanilla-JS qui répondra à la fois aux clics d’ancrage et à la résolution du hachage de page au chargement (Voir JSFiddle) . Modifiez la méthode .getFixedOffset() si des calculs dynamiques sont requis. Si vous utilisez jQuery, voici une solution modifiée avec une meilleure délégation d'événements et un défilement régulier .

(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);
154
Ian Clark

Je cherchais une solution à cela aussi. Dans mon cas, c'était assez facile.

J'ai un menu de liste avec tous les liens:

<ul>
<li><a href="#one">one</a></li>
<li><a href="#two">two</a></li>
<li><a href="#three">three</a></li>
<li><a href="#four">four</a></li>
</ul>

Et ci-dessous les rubriques où il devrait aller.

<h3>one</h3>
<p>text here</p>

<h3>two</h3>
<p>text here</p>

<h3>three</h3>
<p>text here</p>

<h3>four</h3>
<p>text here</p>

Maintenant, parce que j'ai un menu fixe en haut de ma page, je ne peux pas simplement le faire aller à mon tag car ce serait derrière le menu.

Au lieu de cela, je mets une balise span dans ma balise avec le bon identifiant.

<h3><span id="one"></span>one</h3>

Maintenant, utilisez 2 lignes de CSS pour les positionner correctement.

h3{ position:relative; }
h3 span{ position:absolute; top:-200px;}

Modifiez la valeur supérieure pour qu'elle corresponde à la hauteur de votre en-tête fixe (ou plus). Maintenant, je suppose que cela fonctionnerait également avec d’autres éléments.

141
Hrvoje Miljak

FWIW cela a fonctionné pour moi:

*[id]:before { 
  display: block; 
  content: " "; 
  margin-top: -75px; 
  height: 75px; 
  visibility: hidden; 
}
100
Mark Nottingham

Solution Pure CSS inspirée par Alexander Savin:

a[name] {
  padding-top: 40px;
  margin-top: -40px;
  display: inline-block; /* required for webkit browsers */
}

Si vous le souhaitez, vous pouvez éventuellement ajouter les éléments suivants si la cible est toujours hors de l'écran:

  vertical-align: top;
83
Ziav

Cela prend beaucoup d'éléments des réponses précédentes et combine dans une minuscule fonction anonyme jQuery (194 octets minifiés). Ajustez fixedElementHeight à la hauteur de votre élément de menu ou de blocage.

    (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);

Si vous n'aimez pas l'animation, remplacez

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

avec:

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

Version altérée:

 !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);
36
Lance

Ma solution combine les sélecteurs cible et avant pour notre CMS. Les autres techniques ne prennent pas en compte le texte dans l'ancre. Ajustez la hauteur et la marge négative au décalage dont vous avez besoin ...

:target:before {
    content: "";
    display: block;
    height: 180px;
    margin: -180px 0 0;
}
32
Lezz

J'avais été confronté à un problème similaire. Malheureusement, après avoir mis en œuvre toutes les solutions ci-dessus, je suis parvenu à la conclusion suivante.

  1. Mes éléments internes avaient une structure CSS fragile et implémentant une position jeu relatif/absolu, cédait complètement le design de la page.
  2. CSS n'est pas mon point fort.

J'ai écrit ce simple texte défilant js, qui tient compte du décalage dû à l'en-tête et a déplacé la div d'environ 125 pixels en dessous. S'il vous plaît, utilisez-le comme bon vous semble.

Le HTML

<div id="#anchor"></div> <!-- #anchor here is the anchor tag which is on your URL -->

Le javascript

 $(function() {
  $('a[href*=#]:not([href=#])').click(function() {
    if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') 
&& location.hostname == this.hostname) {

      var target = $(this.hash);
      target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
      if (target.length) {
        $('html,body').animate({
          scrollTop: target.offset().top - 125 //offsets for fixed header
        }, 1000);
        return false;
      }
    }
  });
  //Executed on page load with URL containing an anchor tag.
  if($(location.href.split("#")[1])) {
      var target = $('#'+location.href.split("#")[1]);
      if (target.length) {
        $('html,body').animate({
          scrollTop: target.offset().top - 125 //offset height of header here too.
        }, 1000);
        return false;
      }
    }
});

Voir un implémentation en direct ici .

30
Shouvik

Pour les navigateurs modernes, ajoutez simplement le sélecteur CSS3: target à la page. Cela s'appliquera à toutes les ancres automatiquement.

:target {
    display: block;    
    position: relative;     
    top: -100px;
    visibility: hidden;
}
28
Alessandro Alinone

Vous pouvez le faire sans js et sans modifier le code HTML. Css-only.

a[id]:before {
    content:"";
    display:block;
    height:50px;
    margin:-30px 0 0;
}

Cela va ajouter un pseudo-élément avant chaque balise a avec un identifiant. Ajustez les valeurs pour correspondre à la hauteur de votre en-tête.

26
Zsolt Szilagy

Comme @moeffju le suggère, cela peut être réalisé avec CSS . La question que j’ai rencontrée (et qui me surprend de ne pas avoir discuté de la question) est la superposition d’éléments précédents superposés avec un remplissage ou une bordure transparente empêche le survol et les actions de clic au bas de ces sections car le suivant ordre z.

La meilleure solution que j'ai trouvée consistait à placer le contenu de la section dans un div qui se trouve à z-index: 1:

// Apply to elements that serve as anchors
.offset-anchor {
  border-top: 75px solid transparent;
  margin: -75px 0 0;
  -webkit-background-clip: padding-box;
  -moz-background-clip: padding;
  background-clip: padding-box;
}

// Because offset-anchor causes sections to overlap the bottom of previous ones,
// we need to put content higher so links aren't blocked by the transparent border.
.container {
  position: relative;
  z-index: 1;
}
11
Kris Braun

Les solutions avec des propriétés de changement de position ne sont pas toujours possibles (cela peut détruire la mise en page) donc je suggère ceci:

HTML:

<a id="top">Anchor</a>

CSS:

#top {
    margin-top: -250px;
    padding-top: 250px;
}

Utilisez ceci:

<a id="top">&nbsp;</a>

minimiser les chevauchements et définir la taille de la police sur 1px. Une ancre vide ne fonctionnera pas dans certains navigateurs.

10
user2475125

Pour le même problème, j'ai utilisé une solution simple: placez un capuchon de 40 pixels sur chaque ancre.

9
odupont

En empruntant une partie du code d’une réponse donnée sur ce lien (aucun auteur n’est spécifié), vous pouvez inclure un effet de défilement lisse de Nice à l’ancre, tout en l’arrêtant à -60px au-dessus de l’ancre, s'ajustant bien sous la barre de navigation fixe bootstrap (nécessite jQuery):

$(".dropdown-menu a[href^='#']").on('click', function(e) {
   // prevent default anchor click behavior
   e.preventDefault();

   // animate
   $('html, body').animate({
       scrollTop: $(this.hash).offset().top - 60
     }, 300, function(){
     });
});
8
Mark C Mitchell

J'ai rencontré le même problème et j'ai fini par gérer les événements de clic manuellement, comme suit:

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

Faites défiler l'animation facultative, bien sûr.

3
jean

Les méthodes ci-dessus ne fonctionnent pas très bien si votre ancre est un élément de tableau ou au sein d'un tableau (ligne ou cellule).

Je devais utiliser javascript et me lier à l'événement window hashchange pour contourner ce problème ( démo ):

function moveUnderNav() {
    var $el, h = window.location.hash;
    if (h) {
        $el = $(h);
        if ($el.length && $el.closest('table').length) {
            $('body').scrollTop( $el.closest('table, tr').position().top - 26 );
        }
    }
}

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

* Remarque: L'événement hashchange n'est pas disponible dans tous les navigateurs.

3
Mottie

Au lieu d’avoir une barre de navigation à position fixe qui est sous-jacente au reste du contenu de la page (le corps de la page entier est déroulable), envisagez plutôt d’avoir un corps non défilable avec une barre de navigation statique, puis le contenu de la page dans div défilement absolument positionné ci-dessous.

C'est-à-dire, avoir du HTML comme ça ...

<div class="static-navbar">NAVBAR</div>
<div class="scrollable-content">
  <p>Bla bla bla</p>
  <p>Yadda yadda yadda</p>
  <p>Mary had a little lamb</p>
  <h2 id="stuff-i-want-to-link-to">Stuff</h2>
  <p>More nonsense</p>
</div>

... et CSS aiment ça:

.static-navbar {
  height: 100px;
}
.scrollable-content {
  position: absolute;
  top: 100px;
  bottom: 0;
  overflow-y: scroll;
  width: 100%;
}

Cela permet d’obtenir le résultat souhaité de manière simple et sans hacky. La seule différence de comportement entre ceci et certains des astucieux CSS suggérés ci-dessus est que la barre de défilement (dans les navigateurs qui en rendent un) sera attachée à la div content plutôt qu'à la hauteur totale de la page. Vous pouvez ou ne pas considérer cela souhaitable.

Voici un JSFiddle démontrant cela en action.

2
Mark Amery

Cela a été inspiré par la réponse de Shouvik - même concept que le sien, seule la taille de l'en-tête fixe n'est pas codée en dur. Tant que votre en-tête fixe est dans le premier noeud d'en-tête, cela devrait "fonctionner"

/*jslint browser: true, plusplus: true, regexp: true */

function anchorScroll(fragment) {
    "use strict";
    var amount, ttarget;
    amount = $('header').height();
    ttarget = $('#' + fragment);
    $('html,body').animate({ scrollTop: ttarget.offset().top - amount }, 250);
    return false;
}

function outsideToHash() {
    "use strict";
    var fragment;
    if (window.location.hash) {
        fragment = window.location.hash.substring(1);
        anchorScroll(fragment);
    }
}

function insideToHash(nnode) {
    "use strict";
    var fragment;
    fragment = $(nnode).attr('href').substring(1);
    anchorScroll(fragment);
}

$(document).ready(function () {
    "use strict";
    $("a[href^='#']").bind('click',  function () {insideToHash(this); });
    outsideToHash();
});
2
Alice Wonder

Vous pouvez y parvenir sans identifiant à l'aide du sélecteur a[name]:not([href]) css. Cela cherche simplement des liens avec un nom et sans href, par exemple. <a name="anc1"></a>

Un exemple de règle pourrait être:

a[name]:not([href]){
    display: block;    
    position: relative;     
    top: -100px;
    visibility: hidden;
}
1
Chris GW Green

Je suis confronté à ce problème sur un site Web TYPO3, où tous les "éléments de contenu" sont entourés de quelque chose comme:

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

et j'ai changé le rendu de sorte qu'il se présente comme ceci:

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

Et ce CSS:

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

La barre supérieure fixe ayant une hauteur de 40px, les ancres fonctionnent à nouveau et démarrent 10px sous la barre supérieure.

Le seul inconvénient de cette technique est que vous ne pouvez plus utiliser :target.

1
lipsumar

Une autre astuce de l'excellent réponse de @Jan est de l'incorporer dans l'en-tête fixe #uberbar, qui utilise jQuery (ou MooTools). ( http://davidwalsh.name/persistent-header-opacity )

J'ai modifié le code pour que le haut du contenu se trouve toujours en dessous, pas sous l'en-tête fixe, mais j'ai également ajouté à nouveau les ancres de @Jan en s'assurant que les ancres sont toujours positionnées sous l'en-tête fixe.

Le 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;
}

Le jQuery (y compris des ajustements à la fois sur les approches #uberbar et anchor:

<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>

Et enfin le 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>

C’est peut-être utile pour ceux qui aiment l’en-tête à atténuation progressive #uberbar!

0
Dzseti

En ajoutant à la réponse de Ziav (et merci à Alexander Savin), je dois utiliser l’ancienne école <a name="...">...</a> comme nous utilisons <div id="...">...</div> à une autre fin dans notre code. J'ai eu quelques problèmes d'affichage avec display: inline-block - la première ligne de chaque élément <p> s'avérait légèrement en retrait (sur les navigateurs Webkit et Firefox). J'ai fini par essayer d'autres valeurs display et display: table-caption fonctionne parfaitement pour moi.

.anchor {
  padding-top: 60px;
  margin-top: -60px;
  display: table-caption;
}
0
Prashant Tiwari

J'ai ajouté un élément .vspace de 40px-height tenant l'ancre avant chacun de mes éléments h1.

<div class="vspace" id="gherkin"></div>
<div class="page-header">
  <h1>Gherkin</h1>
</div>

En CSS:

.vspace { height: 40px;}

Cela fonctionne très bien et l'espace n'est pas choquant.

0
Quentin

que diriez-vous des balises span cachées avec des identifiants pouvant être liés qui fournissent la hauteur de la barre de navigation:

#head1 {
  padding-top: 60px;
  height: 0px;
  visibility: hidden;
}


<span class="head1">somecontent</span>
<h5 id="headline1">This Headline is not obscured</h5>

voici le violon: http://jsfiddle.net/N6f2f/7

0
Pete

Vous pouvez également ajouter une ancre avec suivez attr:

(text-indent:-99999px;)
visibility: hidden;
position:absolute;
top:-80px;    

et donnez au conteneur parent une position relative.

Fonctionne parfaitement pour moi.

0
Sanjo Elstak