Scroll naar sectie kleur aanpassen

Status
Niet open voor verdere reacties.

damnsharp

Terugkerende gebruiker
Lid geworden
6 jan 2012
Berichten
1.385
Hoi, ik gebruik soms bij een site een scroll-naar-sectie menu.
Dat menu maak ik ook soms sticky.

Nu wil ik graag dat als er naar een bepaalde sectie gescrollt wordt dit menu item een kleurtje krijgt.

Dus;
Code:
<div id="stickymenu">
<a href="#sectie01">Sectie 1</a>
<a href="#sectie02">Sectie 2</a>
<a href="#sectie03">Sectie 3</a>
</div>

<div id="sectie01">
Morbi ullamcorper ultrices ligula sed venenatis. Proin consectetur id elit vitae venenatis. Curabitur quis elit nisi. Praesent commodo ipsum nec pretium condimentum. 
</div>

Op zich, het lukt met a:focus maar als ik een link vanaf een andere pagina plaats die wijst naar bijv #sectie01 dan is die kleur niet zichtbaar.
Is dat op te lossen?
 
Als er een linkje in <div id="sectie__"> staat dan ben je de focus op je menu item kwijt. Een css oplossing is misschien wel te vinden maar die lijkt mij niet handig omdat je niet van tevoren weet welke content er in de secties komt te staan. De oplossing is Javascript, ergens onderaan in je html.
Code:
<div id="stickymenu">
    <a href="#sectie01">Sectie 1</a>
    <a href="#sectie02">Sectie 2</a>
    <a href="#sectie03">Sectie 3</a>
    <a href="#sectie04">Sectie 4</a>
    <a href="#sectie05">Sectie 5</a>
</div>

Code:
#stickymenu a,
#stickymenu a:focus {
    color: blue;
    background-color: yellow;
}

/* class `active-section` wordt via javascript op actieve menu item gezet */
#stickymenu a:hover,
#stickymenu a:active,
#stickymenu a.active-section {
    color: yellow;
    background-color: blue;
}

Code:
// invullen: classname (zonder punt) van het actieve menu item
var currentClass = "active-section";

// invullen: selecteer alle <a> menu items voor de secties (css selector)
var menuItems = document.querySelectorAll("#stickymenu a");

// invullen: één item is standaard `currentClass` (css selector)
var defaultItem = document.querySelector("#stickymenu a:first-child");

// hash gedeelte in de url, bijv. #sectie02
var hash = window.location.hash;
if (hash != '' && hash != '#') {
    // doorloop alle menu items
    menuItems.forEach(function(item, index) {
        // waar staat de # in de href
        var n = item.href.indexOf('#');
        // geef class `currentClass` als hash hetzelfde is
        if (item.href.substr(n) == hash) {
            item.classList.add(currentClass);
        }
    });
} else {
    // geen hash in de url, zet dan default item actief.
    defaultItem.classList.add(currentClass);
}

// doorloop alle menu items
menuItems.forEach(function(item, index) {
    // zet een `click` event op elke menu item
    item.addEventListener("click", function(event) {
        // verwijder bij alle menu items de `currentClass`
        menuItems.forEach(function(item, index) {
            item.classList.remove(currentClass);
        });
        // zet een `currentClass` op het geklikte item
        this.classList.add(currentClass);
    });
});

edit: javascript aangepast voor url#hash naar deze pagina vanaf een andere pagina
 
Laatst bewerkt:
@bron dank je wel voor de code! Ik ga dat direct uitproberen, ik kom erop terug.
Kom wel eens vaker dit soort zaken tegen, fijn dat het dan met JS code is op te lossen.
 
@bron het werk als een speer!

Als ik van een andere pagina link naar sectie03 heeft menu item geen kleur. Niet erg maar zou ook dat anders kunnen.
 
Als ik van een andere pagina link naar sectie03 heeft menu item geen kleur. Niet erg maar zou ook dat anders kunnen.
Berichtje #2 is aangepast. Als er een # in de url staat wordt de juiste knop actief.
En je kan een default pagina (sectie01 lijkt logisch) opgeven voor als er geen # in de url staat.

Je kan het stukje JS over de hash in het onderste stuk JS schuiven om de code korter te maken.
 
Laatst bewerkt:
Met deze css heb ik getest.
Code:
<div id="stickymenu">
    <a href="#sectie01">Sectie 1</a>
    <a href="#sectie02">Sectie 2</a>
    <a href="#sectie03">Sectie 3</a>
</div>

<div id="content">
    <section id="sectie01" class="section-item">
        <p>veel tekst</p>
    </section>
    <section id="sectie02" class="section-item">
        <p>veel tekst</p>
    </section>
    <section id="sectie03" class="section-item">
        <p>veel tekst</p>
    </section>
</div>

Code:
* {
    box-sizing: border-box;
}
body {
    margin: 0;
    font: 400 16px/1.5 sans-serif;
}
#stickymenu {
    display: flex;
    justify-content: center;
    position: -webkit-sticky; /* Safari */
    position: sticky;
    top: 0;
    background-color: white;
}
#content {
    min-height: 100%;
    width: 50%;
    margin: 0 auto 700px auto;
}
#stickymenu a {
    display: block;
    margin: 5px;
    padding: 4px 12px;
    text-decoration: none;
    border: 1px solid grey;
}
#stickymenu a,
#stickymenu a:focus {
    color: blue;
    background-color: yellow;
}
#stickymenu a:hover,
#stickymenu a:active,
#stickymenu a.active-section {
    color: yellow;
    background-color: blue;
}
.section-item {
    min-height: 100%;
    padding: 40px 15px 0 15px;
}
 
Laatst bewerkt:
@bron het wordt steeds mooier en handiger hiermee!
Heb zelf bij #stickymenu nog z-index: 999 gezet want anders stond het menu niet boven alles en niet meer aanklikbaar na het scrollen.
En een vraag, waarom deze code?
Code:
* {
    box-sizing: border-box;
}

En deze ook nodig? Want die maakt bij mijn site een rommeltje van van alles ;)
Code:
#content {
    min-height: 100%;
    width: 50%;
    margin: 0 auto 700px auto;
}

En wat ik ook merk, dat lijkt mij lastig op te lossen is het volgende.
Als je van een andere pagina naar bijv #sectie02 gaat dan is het juiste menu item geselecteerd maar is het sticky menu iets te veel over de tekst.
Dat is niet als je op de pagina zelf scrollt.
 
Dank je @php4u voor je aanvulling, ik ga het artikel lezen.
 
En een vraag, waarom deze code? * { box-sizing: border-box; }
Voorbeeld: een div van 100% breed met een omlijning.
Code:
<div style="width: 100%; border: 1px solid grey;">
  Hier staat wat tekst
</div>

Zonder de * { box-sizing: border-box; } regel krijg je een horizontale schuifbalk omdat de border buiten de browser view valt.
Meestal wordt deze css regel als eerste regel in een stylesheet gezet, dat scheelt een hoop rekenwerk en/of debuggen ;)

Dat stukje over #content heb ik gebruikt voor een minimale test snippet, dat kan je weglaten in jouw template.
 
Laatst bewerkt:
* { box-sizing: border-box; } zijn dingen die je ook niet zomaar halverwege moet toevoegen.
Ik meen dat dit ook zelfs gebruikt wordt in een Bootstrap en andere frameworks.
 
In principe maakt het niet uit waar de * { ... } wordt ingevoegd omdat de * de meest lage prioriteit heeft in css. Het is wel netjes om hiermee te beginnen in een stylesheet. Je kan de * { ... } niet achteraf toevoegen als een template klaar is want dan ligt de layout overhoop ;) ik denk dat daarom iedereen met de * begint.

Nog over de scroll vraag. Omdat ik niet weet hoe je template eruit ziet zou je dit bovenaan in de JS kunnen zetten.
Hiermee ga je naar het juiste punt op de pagina als je vanaf een andere pagina komt.
Zoek bij berichtje #2 even het juiste punt om dit tussen te voegen.
Code:
// hash gedeelte in de url, bijv. #sectie02
var hash = window.location.hash;

// invullen: scroll naar `0,y` nadat pagina is geladen
if (hash == '#sectie01' || hash == '' || hash == '#') {
    window.scrollTo(0, 40);
}

if (hash != '' && hash != '#') {

Dit is een aardig effect voor moderne browsers.
Zet deze 3 regels bij elkaar, zonder andere css erbij, ergens bovenaan in je stylesheet.
Code:
html {
    scroll-behavior: smooth;
}
 
Laatst bewerkt:
Dank je wel @php4u en @bron. Leer steeds meer bij. JS moet ik me meer in gaan verdiepen want het bied wel handige "truukjes" zeg!

@bron ik heb de code geplaatst maar ik denk toch op de verkeerde plek of manier want het werkt niet.
Het JS script ziet er nu zo uit:
Code:
<script>
// code via helpmij van bron topic CSS vraag blok positie absoluut en media queries 
// invullen: classname (zonder punt) van het actieve menu item
var currentClass = "active-section";

// invullen: selecteer alle <a> menu items voor de secties (css selector)
var menuItems = document.querySelectorAll("#stickymenu a");

// invullen: één item is standaard `currentClass` (css selector)
var defaultItem = document.querySelector("#stickymenu a:first-child");

// hash gedeelte in de url, bijv. #sectie02
var hash = window.location.hash;

// invullen: scroll naar `0,y` nadat pagina is geladen
if (hash == '#sectie01' || hash == '' || hash == '#') {
    window.scrollTo(0, 40);
}
// invullen: scroll naar `0,y` nadat pagina is geladen
if (hash == '#sectie02' || hash == '' || hash == '#') {
    window.scrollTo(0, 50);
}
// invullen: scroll naar `0,y` nadat pagina is geladen
if (hash == '#sectie03' || hash == '' || hash == '#') {
    window.scrollTo(0, 60);
}
	
if (hash != '' && hash != '#') {
    // doorloop alle menu items
    menuItems.forEach(function(item, index) {
        // waar staat de # in de href
        var n = item.href.indexOf('#');
        // geef class `currentClass` als hash hetzelfde is
        if (item.href.substr(n) == hash) {
            item.classList.add(currentClass);
        }
    });
} else {
    // geen hash in de url, zet dan default item actief.
    defaultItem.classList.add(currentClass);
}	

// doorloop alle menu items
menuItems.forEach(function(item, index) {
    // zet een `click` event op elke menu item
    item.addEventListener("click", function(event) {
        // verwijder bij alle menu items de `currentClass`
        menuItems.forEach(function(item, index) {
            item.classList.remove(currentClass);
        });
        // zet een `currentClass` op het geklikte item
        this.classList.add(currentClass);
    });
});

</script>

Expres voor #sectie01, 2 en 3 een ander getal genomen om verschil te zien.
 
Zet (zo mogelijk alle) scripts vlak boven </body> want dan werkt het én de website wordt sneller aan de bezoeker getoond. Niet alleen de bezoeker maar ook Google houdt van goede performance :)
 
@bron ga ik direct proberen, had hem juist in de footer gezet ;-)
 
@bron had hem toch goed staan, boven </body> maar werkt niet. Ik zal een test site opzetten zodat ik linkje kan tonen.
 
Mogelijk heb je een conflict met een een (dubbel gebruikte) variabele-naam in een ander script. Om dit op te lossen wordt de Javascript code in een Self-Invoking functie gezet die ervoor zorgt dat variabelen niet buiten deze naamloze functie zichtbaar zijn.
Code:
(function () {
    // hier komt het hele script
})();

Als je dit hebt gedaan dan kan je het testen door onder deze Self-Invoking functie console.log(hash) te zetten. Je krijgt dan een foutmelding omdat de variabele hash (in de functie) niet buiten de functie zichtbaar is.


edit: in het script zit een bug maar daar zal je in de praktijk niet veel last van hebben.
Als je eerst op knop #sectie3 klikt en dan de adresbalk in path/# verandert dan blijft de knop op sectie 3 staan.
 
Laatst bewerkt:
Hoi @bron ik heb de Self-Invoking functie geplaatst en daaronder het console.log maar krijg geen foutmelding en het werkt niet.
Maar test site (linkje naar de site) opgezet dus daar kan je de code zien in de bron code. Wellicht staat er wat niet goed, ben soms dyslectisch ;-)
 
Het menu werkt bij mij maar het is niet fixed in de css. Test pagina

Net alle browsers getest, het werkt op desktop alleen in IE11 niet.
In IE11 kan het werkend gemaakt worden door de forEach loop te vervangen door een for loop
 
Laatst bewerkt:
@bron, goedemorgen! Dat fixed is opgelost maar als ik vanaf Home naar de test pagina veranderd er niets in de positie waar ik uit kom. Dat zou toch nu moeten met dat aangepast script?
Thanks weer!
 
Status
Niet open voor verdere reacties.
Terug
Bovenaan Onderaan