Detecting if section is currently visible on scroll - javascript

I am trying to do a sort of navigation based on current section.
My code is as follows:
$(function() {
'use strict';
function setTitle(title) {
$('.overlay').text(title);
}
function removeTitle() {
$('.overlay').text('');
}
$(window).on('scroll', function() {
let windowScroll = $(window).scrollTop(),
sections = $('section[data-title]');
sections.each(function() {
let thisStart = $(this).offset().top,
thisHeight = $(this).outerHeight(true),
thisTitle = $(this).attr('data-title'),
thisEnd = thisHeight + thisStart;
console.log(`start: ${thisStart}, end: ${thisEnd}, scroll: ${windowScroll}`);
if(windowScroll >= thisStart && windowScroll < thisEnd) {
setTitle(thisTitle);
} else {
removeTitle();
}
});
});
});
HTML
<section class="section section-first"></section>
<section class="section section-what" data-title="First">
</section>
<section class="section section-cv" data-title="Secound">
</section>
<div class="overlay"></div>
Unfortunately, it works only with last .section. What can I do?
Please refer to my CodePen to see what I mean exactly: http://codepen.io/tomekbuszewski/pen/Xmovwq

Add return false; here:
if(windowScroll >= thisStart && windowScroll < thisEnd) {
setTitle(thisTitle);
return false; // <- add this
} else {
removeTitle();
}
That will break out of the each method and prevent the title from being removed once it's been set.
CodePen

Related

How to scroll to the next section/div (of choice) when a button is clicked using only Vanilla JavaScript?

I have been learning Js for the last few months and I took it upon myself to not learn any Jquery until I'm very comfortable with pure js.
I want something that has the exact same effect of This code here without using Jquery. Please include comments for explanation if possible
// --------- Jquery ---------
$(function(){
var pagePositon = 0,
sectionsSeclector = 'section',
$scrollItems = $(sectionsSeclector),
offsetTolorence = 30,
pageMaxPosition = $scrollItems.length - 1;
//Map the sections:
$scrollItems.each(function(index,ele) { $(ele).attr("debog",index).data("pos",index); });
// Bind to scroll
$(window).bind('scroll',upPos);
//Move on click:
$('#arrow a').click(function(e){
if ($(this).hasClass('next') && pagePositon+1 <= pageMaxPosition) {
pagePositon++;
$('html, body').stop().animate({
scrollTop: $scrollItems.eq(pagePositon).offset().top
}, 300);
}
if ($(this).hasClass('previous') && pagePositon-1 >= 0) {
pagePositon--;
$('html, body').stop().animate({
scrollTop: $scrollItems.eq(pagePositon).offset().top
}, 300);
return false;
}
});
//Update position func:
function upPos(){
var fromTop = $(this).scrollTop();
var $cur = null;
$scrollItems.each(function(index,ele){
if ($(ele).offset().top < fromTop + offsetTolorence) $cur = $(ele);
});
if ($cur != null && pagePositon != $cur.data('pos')) {
pagePositon = $cur.data('pos');
}
}
});
Updated
"This works great unless I manually scroll to a different div using the mouse then try to hit the next/prev button. any solution for that? Thank you"
Each <section> is dynamically given a data-id attribute with a value corresponding to its index. If a <section> is clicked, then it becomes the current active <section> so when the arrows are clicked, the scrolling will start from there.
Specific parts of Demo has been commented as "UPDATED"
There's a perfect method called scrollIntoView()
x.scrollIntoView({
behavior: 'smooth'
});
It has jQuery-like options built in.
Details commented in Demo
Demo
// Reference counter outside of function
var idx = 0;
// Collect all sections in a NodeList
var sxn = document.querySelectorAll('section');
// UPDATED
/* Loop through the NodeList sxn
|| Assign a data-id attribute to each section
|| Set data-id value to current index of each
|| section
*/
for (let i = 0; i < sxn.length; i++) {
sxn[i].setAttribute('data-id', i);
}
// Reference nav
var nav = document.querySelector('nav');
// Collect all anchors into a HTMLCollection
var lnx = document.links;
// UPDATED
// Register document on click event callback is move()
document.addEventListener('click', move, false);
// UPDATED
/* move() determines the direction of scroll by idx
|| If a section is clicked instead of the arrows,
|| then the data-id value of said section is now idx.
|| So when a section is clicked, nothing happens until an
|| arrow is clicked. Once that happens, scrolling starts
|| from the last section clicked.
*/
function move(e) {
if (e.target == lnx[0]) {
idx--;
if (idx < 0) {
idx = sxn.length - 1;
}
} else if (e.target.tagName === 'SECTION') {
idx = e.target.getAttribute('data-id');
} else {
idx++;
if (idx > sxn.length - 1) {
idx = 0;
}
}
return idxScroll(idx);
}
// Pass idx thru idxScroll
function idxScroll(idx) {
// Reference current active section
var act = document.querySelector('.active');
// Determine which section becomes active
var x = sxn[idx];
// Remove active class from current section
act.classList.remove('active');
// Add active class to new section
x.classList.add('active');
/* scrollIntoView method has a behavior option that animates
|| scrolling
*/
x.scrollIntoView({
behavior: 'smooth'
});
}
main {
width: 100vw;
height: auto;
}
nav {
position: fixed;
z-index: 1;
width: 20%;
right: 0;
top: 0
}
a {
width: 48px;
height: 48px;
font-size: 48px;
text-decoration: none;
}
section {
width: 100vw;
height: 100vh;
}
<main>
<nav>
<a href='#/'>◀</a>
<a href='#/'>▶</a>
</nav>
<div>
<section style='background:red' class='active'></section>
<section style='background:blue'></section>
<section style='background:yellow'></section>
<section style='background:black'></section>
<section style='background:green'></section>
<section style='background:purple'></section>
<section style='background:deeppink'></section>
<section style='background:cyan'></section>
<section style='background:tomato'></section>
<section style='background:brown'></section>
<section style='background:orchid'></section>
</div>
</main>
Here you go:
var secs = document.querySelectorAll('section');
var currentSection = 0;
document.querySelector('#arrow').addEventListener('click', move);
function move(e) {
if (e.target.classList.contains('next') && currentSection < secs.length) {
window.scroll({
top: secs[++currentSection].offsetTop,
left: 0,
behavior: 'smooth'
});
// secs[++currentSection].scrollIntoView({ behavior: 'smooth' });
} else if (currentSection > 0) {
window.scroll({
top: secs[--currentSection].offsetTop,
left: 0,
behavior: 'smooth'
});
}
}
Here is the jsFiddle solution. I used the smoothscroll API polyfill. For the animations incase your browser doesn't support the API (https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior).

Javascript doesn't work on dynamic content

I'm trying to learn javascript and jquery lately so I'm not so good yet.
The page I'm currently struggling with is a news page. Basically news are article tags and are contained in two different main category divs.
<body>
<div class="a">
<article> ... </article>
<article> ... </article>
<article> ... </article>
...
</div>
<div class="b">
<article> ... </article>
<article> ... </article>
<article> ... </article>
...
</div>
</body>
In each article there's a slideshow with pictures and this is the code in JS:
//dots functionality
dots = document.getElementsByClassName('dot');
for (i = 0; i < dots.length; i++) {
dots[i].onclick = function() {
slides = this.parentNode.parentNode.getElementsByClassName("mySlides");
for (j = 0; j < this.parentNode.children.length; j++) {
this.parentNode.children[j].classList.remove('active');
slides[j].classList.remove('active-slide');
if (this.parentNode.children[j] == this) {
index = j;
}
}
this.classList.add('active');
slides[index].classList.add('active-slide');
}
}
//prev/next functionality
links = document.querySelectorAll('.slideshow-container a');
for (i = 0; i < links.length; i++) {
links[i].onclick = function() {
current = this.parentNode;
var slides = current.getElementsByClassName("mySlides");
var dots = current.getElementsByClassName("dot");
curr_slide = current.getElementsByClassName('active-slide')[0];
curr_dot = current.getElementsByClassName('active')[0];
curr_slide.classList.remove('active-slide');
curr_dot.classList.remove('active');
if (this.className == 'next') {
if (curr_slide.nextElementSibling.classList.contains('mySlides')) {
curr_slide.nextElementSibling.classList.add('active-slide');
curr_dot.nextElementSibling.classList.add('active');
} else {
slides[0].classList.add('active-slide');
dots[0].classList.add('active');
}
}
if (this.className == 'prev') {
if (curr_slide.previousElementSibling) {
curr_slide.previousElementSibling.classList.add('active-slide');
curr_dot.previousElementSibling.classList.add('active');
} else {
slides[slides.length - 1].classList.add('active-slide');
dots[slides.length - 1].classList.add('active');
}
}
}
}
It worked fine until I made the news data load on scroll with ajax. The JS code doesn't work anymore and I don't know how to fix it.
Here is the ajax code:
$(document).ready(function() {
function getDataFor(category) {
var flag = 0;
function getData() {
$.ajax({
type: 'GET',
url: 'get_data.php',
data: {
'offset': flag,
'limit': 3,
'cat': category
},
success: function(data) {
$('.' + category).append(data);
flag += 3;
}
});
}
getData();
var $window = $(window);
var $document = $(document);
$window.on('scroll', function() {
if ($window.scrollTop() >= $document.height() - $window.height()) {
getData();
}
});
}
getDataFor('a');
getDataFor('b');
});

Add class when X class is been removed via JS

I am trying to add a class to a div when another class is been removed with JS.
This is my HTML:
<body class="homepage">
<div id="wrap">
<div id="projects">
<section id="project-0" class="slide active"> Slide 1</section>
<section id="project-1" class="slide active"> Slide 2</section>
<section id="project-2" class="slide active"> Slide 3</section>
</div>
</div>
<div class="content"> Website main content </div>
This is a vertical slide, so when you scroll down, the active class is removed with JS. What I want to achieve is to add a class to body when the active is removed from project-2.
This is what I have so far, but it doesn't recognise the class active because it's been added via JS...
if(!$("#project-2").hasClass("active")){
$("body").addClass("shifted");
}
JS:
var delta = 0;
var currentSlideIndex = 0;
var scrollThreshold = 30;
var slides = $(".slide");
var numSlides = slides.length;
function elementScroll (e) {
console.log (Math.abs(delta));
// --- Scrolling up ---
if (e.originalEvent.detail < 0 || e.originalEvent.wheelDelta > 0) {
delta--;
if ( Math.abs(delta) >= scrollThreshold) {
prevSlide();
}
}
// --- Scrolling down ---
else {
delta++;
if (delta >= scrollThreshold) {
nextSlide();
}
}
// Prevent page from scrolling
return false;
}
function showSlide() {
// reset
delta = 0;
slides.each(function(i, slide) {
$(slide).toggleClass('active', (i >= currentSlideIndex));
});
}
function prevSlide() {
currentSlideIndex--;
if (currentSlideIndex < 0) {
currentSlideIndex = 0;
}
showSlide();
}
function nextSlide() {
currentSlideIndex++;
if (currentSlideIndex > numSlides) {
currentSlideIndex = numSlides;
}
showSlide();
}
$(window).on({
'DOMMouseScroll mousewheel': elementScroll
});
You can see here how it works
Thanks
By looking at your JS code I believe you want to add class to body while scrolling down. You may try below code:
function prevSlide() {
currentSlideIndex--;
if(currentSlideIndex == (numSlides-1))
{
$("body").removeClass("shifted"); // remove the class from body
}
if (currentSlideIndex < 0) {
currentSlideIndex = 0;
}
showSlide();
}
function nextSlide() {
currentSlideIndex++;
if (currentSlideIndex > numSlides) {
currentSlideIndex = numSlides;
$("body").addClass("shifted"); // add the class to body
}
showSlide();
}
Your check for the absence of the class is only started once. You had to do this with an interval from 100 ms or whatever you want:
setInterval(function()
{
if (!$("#project-2").hasClass("active")){
$("body").addClass("shifted");
}
}, 100);

Not directing to div id target when scroll

Have problems with firefox 33.1 my scroll to div is not directing to my target div id im using polymer core-scaffold as my navigation but its working fine using chrome
and i don't have any errors on my console
here's my script
<script>
var scaffold = document.getElementById('scaffold');
var menu = document.getElementById('menu');
menu.addEventListener('core-select', function(e) {
if (e.detail.isSelected) {
scrollToSection(e.detail.item.getAttribute('name'));
}
});
function scrollToSection(id) {
var section = document.getElementById(id);
if (section) {
scaffold.$.headerPanel.scroller.scrollTop = section.offsetTop;
}
}
</script>
here's my navigation code
<core-scaffold id="scaffold" responsiveWidth="640px">
<core-header-panel navigation flex mode="seamed">
<core-toolbar>Navigation</core-toolbar>
<core-menu id="menu">
<core-item name="drawerPanel" icon="home" label="Home"></core-item>
<core-item name="about" icon="account-circle" label="Who"></core-item>
<core-item name="works" icon="work" label="Works"></core-item>
<core-item name="skills-cont" icon="gesture" label="Skills" name="skills"></core-item>
<core-item name="contacts-cont" icon="settings-phone" label="Contacts"></core-item>
<core-item name="cart" icon="shopping-cart" label="D Shop"></core-item>
<core-item name="v8" icon="link" label="v8"></core-item>
<core-item name="v7" icon="link" label="v7"></core-item>
<core-item name="v6" icon="link" label="v6"></core-item>
</core-menu>
<div id="about">
<about-koh></about-koh>
</div>
<div id="works">
<works></works>
</div>
....
is it possible to change the offsetTop depends on browser like if in firefox i want it to offsetTop -75 then in chrome it will be offsetTop -10 ?
ok found out that the reason was my jquery script i had this script and it has a duplicate script with different class so to fix it i remove the duplicate script.
my old script reason i got problem
jQuery.noConflict();
jQuery(document).ready(function(){
var i = 0;
var posts = jQuery('.ab-effect').children();
function animateCircle() {
if (i % 2 === 0) {
jQuery(posts[i]).addClass('visible animated fadeInUp');
} else {
jQuery(posts[i]).addClass('visible animated fadeInDown');
}
i++;
if (i <= posts.length) {
startAnimation();
}
}
function startAnimation() {
setTimeout(function () {
animateCircle();}, 1000);
}
posts.addClass('hidden');
animateCircle(posts);
});
jQuery.noConflict();
jQuery(document).ready(function(){
var i = 0;
var posts = jQuery('.sk-effect').children();
function animateCircle() {
if (i % 2 === 0) {
jQuery(posts[i]).addClass('visible animated fadeInUp');
} else {
jQuery(posts[i]).addClass('visible animated fadeInDown');
}
i++;
if (i <= posts.length) {
startAnimation();
}
}
function startAnimation() {
setTimeout(function () {
animateCircle();}, 1000);
}
posts.addClass('hidden');
animateCircle(posts);
});
my new script that fix my problem - i just remove the duplicate script
jQuery.noConflict();
jQuery(document).ready(function(){
var i = 0;
var posts = jQuery('.ab-effect').children();
function animateCircle() {
if (i % 2 === 0) {
jQuery(posts[i]).addClass('visible animated fadeInUp');
} else {
jQuery(posts[i]).addClass('visible animated fadeInDown');
}
i++;
if (i <= posts.length) {
startAnimation();
}
}
function startAnimation() {
setTimeout(function () {
animateCircle();}, 1000);
}
posts.addClass('hidden');
animateCircle(posts);
});

Javascript, Jquery - I am getting two values on index when element is clicked

HTML:
<div class="promo_tumbs col_12">
<div data-dir="prev" class="prev"></div>
<div data-dir="next" class="next"></div>
<div class="promo_tumbs_centar">
<div class="promo_tumb promo_tumb_current">Test</div>
<div class="promo_tumb">Test</div>
<div class="promo_tumb">Test</div>
<div class="promo_tumb">Test</div>
<div class="promo_tumb">Test</div>
<div class="promo_tumb">Test</div>
<div class="promo_tumb">Test</div>
</div>
</div>
JavaScript:
function Slider(container, nav) {
this.container = container;
this.nav = nav;
this.li = this.container.find('li');
this.li_width = this.li.first().width();
this.li_len = this.li.length;
this.thumbs = this.nav.find('a');
this.current = 0;
}
Slider.prototype.transition = function(coords) {
this.container.stop().animate({
'margin-left': coords || -(this.current * this.li_width)
})
}
Slider.prototype.set_current = function(dir) {
var pos = this.current;
if (dir === 'next') {
pos++
}
else if (dir === 'prev') {
pos--
}
this.current = (pos < 0) ? this.li_len - 1 : pos % this.li_len;
return pos;
}
var slider = new Slider($('div.promo_inner ul'), $('div.promo_tumbs'));
slider.nav.find('div').on('click', function() {
if ($(this).attr("data-dir") === undefined) {
var index = slider.thumbs.index($(this).parent('a'));
console.log(index)
} else {
slider.set_current($(this).data('dir'));
}
slider.transition();
})​
​
Fiddle link
When I click on element I am getting two values - index of clicked element and -1. What is going on here? How can I loose -1 and get only index value?
Call event.stopPropagation(); in order to stop the propagation of event Demo on JsFiddle
This will give you more idea what elements causing double event Reason for multiple events JsFiddle
nav.find() also matches <div class="promo_tumbs_centar">. Try find(".promo_tumb")
When clicking on a .promo_tumb div, you're also clicking on .promo_tumbs_centar.
You should use this :
slider.nav.find('.promo_tumb')
instead of
slider.nav.find('div')

Categories