If element is scrolled on data-attribute then - javascript

I have two headers (menu1 - default, menu2 - display:none).
In sections of website I added special attribute (data-ix="change-header").
I want to have the effect.. that if I will scroll site and if we scrolled on section where data-ix="change-header" then header will be other - so menu1 will be display:none and menu2 will be display:block;
I have something like that, but I don't know how I can use scroll.
if ($(this).attr("data-ix") == "change-header"){
$("‪#‎menu1‬").css("display","none");
$("‪#‎menu2‬").css("display","block");
} else {
$("#menu1").css("display","block");
$("#menu2").css("display","none");
}
My html looks like that:
<header id="menu1"></header>
<header id="menu2"></header>
<div class="test" data-ix="change-header"></div>
<div class="test"></div>
<div class="test" data-ix="change-header"></div>
<div class="test" data-ix="change-header"></div>
<div class="test" data-ix="change-header"></div>
<div class="test"></div>
<footer></footer>
Help :)

You can take a look at this: http://janpaepke.github.io/ScrollMagic/
It's only 6Kb gzipped, and it lets you animate elements or toggle CSS classes based on scroll position :)

You can compute the threshold values at which you will change header (or not). Something like this
var thresholds = [];
$('.test').each(function(i, e) {
// after we scroll past the top coordinate of this element,
// either show or hide the second header, based on the presence
// of the data-ix attribute
thresholds.push([e.offsetTop, $(e).data('ix') === 'change-header']);
});
Then, consult these thresholds on every scroll event
// cache menu elements
var $menu1 = $('#menu1'), $menu2 = $('#menu2');
// update header once, and listen on scroll
update();
$(window).on('scroll', update);
function update() {
// pick first visible threshold
var scrollTop = $(this).scrollTop(), thresh;
for (var i = 0, len = thresholds.length; i < len; ++i) {
thresh = thresholds[i];
if (thresh[0] >= scrollTop) break;
}
// update header as necessary
if (thresh[1]) {
$menu1.hide();
$menu2.show();
} else {
$menu2.hide();
$menu1.show();
}
}
Here is a working Plunker.

Related

Add/remove class to div if section is in viewport (vanilla JS)

I have a onepager with 5 sections, each with a min-height of 100vh and an id.
Then there is a fixed background div that changes its state when a new section comes into the viewport.
<div class="background"></div>
<section id="s1">...</section>
<section id="s2">...</section>
<section id="s3">...</section>
<section id="s4">...</section>
<section id="s5">...</section>
I wanted to update the background div with a class with the name of the current section id when the section enters the viewport and remove it when the section leaves the viewport.
This is what I made:
const sections = document.querySelectorAll('section')
const bg = document.querySelector('div.background')
document.addEventListener('scroll', updateBg)
function updateBg() {
sections.forEach((section) => {
const pixels = window.pageYOffset
const sectionId = section.getAttribute('id')
const offsetBottom = section.offsetTop + section.offsetHeight
if (section.offsetTop <= pixels) {
bg.classList.add(sectionId)
}
else if (offsetBottom >= pixels) {
bg.classList.remove(sectionId)
}
else {
bg.classList.remove(sectionId)
}
})
}
Adding the current class when the section enters the viewport works fine. But it's not removing the classes when the sections have left the viewport like I declared in my else if (offsetBottom >= pixels) statement. When I fully scrolled down the page I have something like this:
<div class="background s1 s2 s3 s4 s5"></div>
but what I want at the end is this:
<div class="background s5"></div>
any help?
Your conditions seem to be out of order. You are setting the class for all sections that are below the scroll threshold. Instead, you should add the class if part of the section is visible and remove it otherwise, like this:
if (section.offsetTop <= pixels && offsetBottom > pixels) {
bg.classList.add(sectionId);
} else {
bg.classList.remove(sectionId)
}
Also, please read the answer to this question: How can I tell if a DOM element is visible in the current viewport?
It is recommended to use getBoundingClientRect API instead.

Making on-scroll style changes using vanilla js

I want remove .bg-light from nav element at 400px and more scrolls
<nav id="my-nav" class="bg-light navbar text-info"> change my background color</nav>
I know it's an easy task with jQuery but is it possible to do it with vanilla js?
Thanks for spending time on my question I will be glad to see opinion
First, we start by grabbing the "nav" element using the ID.
Then setting our Y-axis's offset.
Attach a listener to the window object.
On scroll, compare the current position to our desired offset.
const navBar = document.getElementById("my-nav");
const offset = 400;
window.addEventListener("scroll", () => {
if (window.scrollY >= offset){
navBar.classList.remove("bg-light")
} else {
navBar.classList.add("bg-light")
}
})
Yes, its possible with Vanilla JavaScript, use the "scroll" event handler to get the info. As the users moves through the site it will return the Y axis position in pixels. By using an if statement remove or add the bg-light class, like so:
let nav = document.getElementById("my-nav");
window.addEventListener("scroll", (e) => {
if(this.scrollY > 400){ nav.classList.remove('bg-light') }
else{
nav.classList.add('bg-light')
}
});
*{padding:5px}
html{height:3000px;font-size:20px}
.bg-light{background-color: #F8F8F8!important; color:black!important;}
.navbar{position:fixed;background-color:blue; color:white;}
<nav id="my-nav" class="bg-light navbar text-info"> Change my background color at scrollY 400px</nav>

On scroll, logo color changes, but scrolling back up it stays the same

Im creating a fixed header where on load, the logo is flat white. On scroll, it changes to the full color logo.
However, when scrolling back to the top, it stays the same colored logo instead of going back to white.
Here's the code (and a pen)
$(function() {
$(window).scroll(function() {
var navlogo = $('.nav-logo-before');
var scroll = $(window).scrollTop();
if (scroll >= 1) {
navlogo.removeClass('.nav-logo-before').addClass('nav-logo-after');
} else {
navlogo.removeClass('.nav-logo-after').addClass('nav-logo-before');
}
});
});
http://codepen.io/bradpaulp/pen/gmXOjG
There's a couple of things here:
1) You start with a .nav-logo-before class but when the logo becomes black you remove that class and then try to get the same element using a class selector that doesn't exist anymore
2) removeClass('.nav-logo-before') is different than removeClass('nev-logo-before), notice the "." in the first selector.
3) You get the element using the $('.selector')in every scroll event, this can be a performance issue, it's better to cache them on page load and then use the element stored in memory
4) It's not a good practice to listen to scroll events as this can be too performance demanding, it's usually better to use the requestAnimationFrame and then check if the scroll position has changed. Using the scroll event it could happen that you scroll up really fast and the scroll event doesn't happen at 0, so your logo won't change. With requestAnimationFrame this can't happen
$(function() {
var navlogo = $('.nav-logo');
var $window = $(window);
var oldScroll = 0;
function loop() {
var scroll = $window.scrollTop();
if (oldScroll != scroll) {
oldScroll = scroll;
if (scroll >= 1) {
navlogo.removeClass('nav-logo-before').addClass('nav-logo-after');
} else {
navlogo.removeClass('nav-logo-after').addClass('nav-logo-before');
}
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
});
body {
background-color: rgba(0,0,0,.2);
}
.space {
padding: 300px;
}
.nav-logo-before {
content: url(https://image.ibb.co/kYANyv/logo_test_before.png)
}
.nav-logo-after {
content: url(https://image.ibb.co/jYzFJv/logo_test_after.png)
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<img class="nav-logo nav-logo-before">
</div>
<div class="space">
</div>
Dont need to add the dot . in front of the class name in removeClass and addClass:
Use this:
navlogo.removeClass('nav-logo-before')
Secondly, you are removing the class that you are using to get the element in the first place.
I have an updated codepen, see if this suits your needs: http://codepen.io/anon/pen/ZeaYRO
You are removing the class nav-logo-before, so the second time the function runs, it can't find any element with nav-logo-before.
Just give a second class to your navlogo element and use that on line 3.
Like this:
var navlogo = $('.second-class');
working example:
http://codepen.io/anon/pen/ryYajx
You are getting the navlogo variable using
var navlogo = $('.nav-logo-before');
but then you change the class to be 'nav-logo-after', so next time the function gets called you won't be able to select the logo using jquery as it won't have the '.nav-logo-before'class anymore.
You could add an id to the logo and use that to select it, for example.
Apart from that, removeClass('.nav-logo-before') should be removeClass('nav-logo-before') without the dot before the class name.
The problem is that you removes nav-logo-before and then you want to select element with such class but it doesn't exist.
I've rafactored you code to avert it.
Another problem is that you uses dot in removeClass('.before') while it should be removeClass('before') - without dot
$(function() {
var navlogo = $('.nav-logo');
$(window).scroll(function() {
var scroll = $(window).scrollTop();
if (scroll >= 1) {
navlogo.removeClass('before').addClass('after');
} else {
navlogo.removeClass('after').addClass('before');
}
});
});
body {
background-color: rgba(0,0,0,.2);
}
.space {
padding: 300px;
}
.before {
content: url(https://image.ibb.co/kYANyv/logo_test_before.png)
}
.after {
content: url(https://image.ibb.co/jYzFJv/logo_test_after.png)
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<img class="nav-logo before">
</div>
<div class="space">
</div>

Changing html tag structure on window resizing

I would like to rebuild an html tag structure to a new one on resizing the browser window. Have anyone an idea how can I get from the first structure to the second structure. I need this for an responsive personal project. Maybe with an JavaScript resize Event, I don't know...
<div class="wrapper">
<div class="slide">
<div class="item"></div>
<div class="item"></div>
</div>
<div class="slide">
<div class="item"></div>
<div class="item"></div>
</div>
</div>
<div class="wrapper">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
You could listen to the resize event on the window and then restructure your HTML when the window is below a certain size. Moving elements from the first structure to the second isn't a big problem, however the challenge lies in reverting that again. How will you know which of the .item's belonged to which .slide?
One way to do this is to keep track of the parent for each .item in a separate data- attribute when you make the list:
function makeList() {
var $slides = $('.slide'),
$items = $();
// For each slide set a data-attribute on all its child .item's
$slides.each(function(i) {
var $item = $(this).children('.item');
$item.attr('data-slide', i+1);
$items = $items.add($item);
});
// Append all items directly to the wrapper
$('.wrapper').html($items).attr('data-structure', 'list');
}
I set a data attribute to the .wrapper so we know that it's already converted to a list. Otherwise on every resize this would be fired, you only want it once (until it resizes back to where you want the slides).
When you want the slide again, loop through all the .items and keep a list of slide number's. For each new data-slide number you encounter make a new slide and add that to the total list;
function makeSlides() {
var $slides = $(),
slideNumbers = [],
$currentSlide = $();
$('.item[data-slide]').each(function() {
var $item = $(this),
slideNumber = $item.attr('data-slide');
// if the slide number wasn't in the array yet push the current slide into $slides and create a new one
if(slideNumbers.indexOf(slideNumber) < 0) {
$slides = $slides.add($currentSlide);
$currentSlide = $('<div class="slide" />');
slideNumbers.push(slideNumber);
}
$currentSlide.append($item);
});
// add the last currentSlide
$slides = $slides.add($currentSlide);
// place all slides in the wrapper
$('.wrapper').html($slides).attr('data-structure', 'slides');
}
Then finally you bind the resize event and fire these functions when needed:
$(window).resize(function(e) {
var currentStructure = $('.wrapper').attr('data-structure');
if(window.innerWidth < 600) {
if( currentStructure !== 'list') {
makeList();
}
} else {
if( currentStructure !== 'slides') {
makeSlides();
}
}
});
jsFiddle demo: http://jsfiddle.net/gktLnw31/3/
I do think this could be a bit more efficient, but this is just a proof of concept. Hopefully it'll give you some insights.
I would better take a look at foundation since it already includes the responsive design by default instead of trying to change it dynamically by code

Multiple ID in getElementById

I've found a great tutorial to detach a navigation from the page to keep it static when you scroll using Javascript (http://code.stephenmorley.org/javascript/detachable-navigation/).
However, I'd like to implement this on more than one nav div.
I assume it's adding another class name to document.getElementById('navigation').className but I can't get the right syntax
Here is the code:
/* Handles the page being scrolled by ensuring the navigation is always in
* view.*/
function handleScroll(){
// check that this is a relatively modern browser
if (window.XMLHttpRequest){
// determine the distance scrolled down the page
var offset = window.pageYOffset
? window.pageYOffset
: document.documentElement.scrollTop;
// set the appropriate class on the navigation
document.getElementById('navigation').className =
(offset > 104 ? 'fixed' : '');
}
}
// add the scroll event listener
if (window.addEventListener){
window.addEventListener('scroll', handleScroll, false);
}else{
window.attachEvent('onscroll', handleScroll);
}
You will have to call getElementById() for each ID. The Method is only designed to get exactly one element (or zero, if the ID isn't found).
Assuming, you have two navigation divs, left and right, like this:
<div id="navigationLeft">
<ul>
<!-- Navigation entries -->
</ul>
</div>
<!-- Maybe some content or whatever? -->
<div id="navigationRight">
<ul>
<!-- Navigation entries -->
</ul>
</div>
Then your Javascript line in question would look like this:
// set the appropriate class on the navigation
document.getElementById('navigationLeft').className = (offset > 104 ? 'fixed' : '');
document.getElementById('navigationRight').className = (offset > 104 ? 'fixed' : '');
// or, shorter but less readable (i think)
document.getElementById('navigationLeft').className
= document.getElementById('navigationRight').className
= (offset > 104 ? 'fixed' : '');
If this does not yet answer your question, please feel free to add some relevant HTML-Code to your question.
[Update: Example]
This is not recommended you should replace id with classes and use that in a loop to set the value:
HTML:
<div class="navigation">
<p>test 1</p>
</div>
<div class="navigation">
<p>test 2</p>
</div>
Javascript:
divs = document.getElementsByClassName('navigation');
for(i = 0; i < divs.length; i++) {
var div = divs[i];
var divClassName = div.className;
if(divClassName.indexOf('fixed') != -1 && offset > 104) {
divClassName.replace(' fixed','');
} else {
divClassName += ' fixed';
}
}
I think that will do the trick :-)
Greetings!
Gonzalo G.
you shouldnt have multiple items on a page with the same ID, ID's are meant to be unique...if you want to capture multiple items you should use:
<div class="navigation"></div>
var nodes = document.getElementsByClassName('navigation')
...if not using jquery, otherwise do something like
var nodes = $('.navigation')
which will get you yor nav bars, then check to see if that node is also "fixed" ( a node can have more than one css class )
(nodes[i].indexOf("navigation") >= 0)
if using jquery, you can use .hasClass('fixed') )
nodes[i].hasClass('fixed')
...your current problem is that it cant add className to navigation because there are two of them and youre not specifying which one you'd like to use.
If you want this to happen in two navigation div's, consider putting them both into one div and call it nav and set a style on it (this depends on your design)
All id's on an element must be unique.
One solution so that you can do a simple change would be to change the CSS file to something like this:
.navigation{
position:absolute;
top:120px;
left:0;
}
.navigationFixed{
position:fixed;
top:16px;
}
And define the Div's vis this:
<div class="navigation">
<!-- your navigation code -->
</div>
And then edit the JavaScript to something along the lines of this:
/* Handles the page being scrolled by ensuring the navigation is always in
* view.
*/
function handleScroll(){
// check that this is a relatively modern browser
if (window.XMLHttpRequest){
divs = document.getElementsByClassName('navigation');
for(i = 0; i < divs.length; i++) {
// determine the distance scrolled down the page
var offset = window.pageYOffset
? window.pageYOffset
: document.documentElement.scrollTop;
divs[i].className =
(offset > 104 ? 'navigationFixed' : 'navigation');
}
}
}
// add the scroll event listener
if (window.addEventListener){
window.addEventListener('scroll', handleScroll, false);
}else{
window.attachEvent('onscroll', handleScroll);
}

Categories