I'm finding myself in a problem which I think has a easy solution but I cannot see... I have two functions to add a class called selected which highlights the buttons.
One function removes the class selected if present and adds the class to the button clicked:
var sidebarLinks = $('.js-sidebar a');
// About section: Remove and add class on click
sidebarLinks.on('click', function() {
sidebarLinks.removeClass('selected');
$(this).addClass('selected');
});
The other function adds the class selected if the user decides to scroll instead of clicking on the buttons. These buttons would highlight in a determined offset of the page:
var buttonOne = $('.buttonOne');
var buttonTwo = $('.buttonTwo');
var buttonThree = $('.buttonThree');
// Select nav buttons if scrolling and not clicking the button
$(window).scroll(function () {
// About nav sidebar links
var buttonOne = $('.js-polspotten-sidebar');
var buttonTwo = $('.js-product-sidebar');
var buttonThree = $('.js-designers-sidebar');
// About sections top scroll position
var currentTopPosition = $(this).scrollTop();
var productsTopPosition = $('#products').offset().top;
var designersTopPosition = $('#designers').offset().top;
// polspotten selected depending on scroll position
if (currentTopPosition >= currentTopPosition && currentTopPosition < productsTopPosition) {
buttonOne.addClass('selected');
}
else{
buttonOne.removeClass('selected');
}
// products selected depending on scroll position
if (currentTopPosition >= productsTopPosition &&
currentTopPosition < designersTopPosition) {
buttonTwo.addClass('selected');
}
else{
buttonTwo.removeClass('selected');
}
// designers selected depending on scroll position
if (currentTopPosition >= designersTopPosition) {
buttonThree.addClass('selected');
}
else{
buttonThree.removeClass('selected');
}
});
The problem is that both interact at the same time as the click event creates scroll.
What I want is that if somebody clicks on the button the scroll function will not work. It will only work if the user scrolls himself.
Is this possible?
Related
I have an event listener that is listening for a click event inside of a media query, if when the window is under 500px. Likewise I have one that is listening for a click event if the window is wider than 500px.
Once the buttons have been clicked within both states, the code works as I would like. However I have the following issue I would like to solve:
When the window is smaller than 500px and the button is clicked it displays the content as required, though when the window is dragged to be wider than 500px, the button then needs to be clicked again to show the content.
As I say this works fine once buttons have been clicked when the window is wider than 500px and then again under 500px, the correct content shows when the window is dragged back and forth.
What I would like to know is how to do the following:
When the button is clicked when the window is under 500px and then dragged out, the corresponding content is displayed without having to press the button again. I also want this to be the case when the window is wider than 500px and dragged to below this value.
Please see the JSFiddle here
Is there a way to somehow make an eventlistener for the element that has already been clicked and do it this way?
//Start listing variables for use in array.
var art1 = document.getElementById("article1");
var button1 = document.getElementById("btn1");
var art2 = document.getElementById("article2");
var button2 = document.getElementById("btn2");
var art3 = document.getElementById("article3");
var button3 = document.getElementById("btn3");
var articleArray = [art1, art2, art3];
var buttonArray = [button1, button2, button3];
function mediaQuery(x) {
//If window is under 500px in width.
if (x.matches) {
//Begin accordion code
var initAccordion = function(accordionElement) {
function handlePanelClick(event) {
showPanel(event.currentTarget);
}
function showPanel(panel) {
var expandedPanel = accordionElement.querySelector(".active");
if (expandedPanel) {
expandedPanel.classList.remove("active");
}
panel.classList.add("active");
}
var allPanelElements = accordionElement.querySelectorAll(".panel");
for (var y = 0; y < allPanelElements.length; y++) {
allPanelElements[y].addEventListener("click", handlePanelClick);
}
//showPanel(allPanelElements[0]);
}
initAccordion(document.getElementById("contentWrapper"));
} else { // If window if is over 500px in width.
//Begin button code.
var createfunc = function(i) {
return function() {
document.getElementById("fillMe").innerHTML = articleArray[i].innerHTML;
};
}
for (let i = 0; i < articleArray.length; i++) {
let button = buttonArray[i];
button.addEventListener("click", createfunc(i));
}
}
}
//Declare media query and add listener.
var x = window.matchMedia("(max-width: 500px)")
mediaQuery(x) // Call listener function at run time
x.addListener(mediaQuery) // Attach listener function on state changes
I have written a custom slider with object-oriented javascript as seen below. I have included the module in a fiddle here https://jsfiddle.net/5z29xhzg/7/. After sliding left or right, the slides are cloned and appended accordingly so that the user can loop through the slider as much as wanted. There is a separate function for controlling the active tab. When used separately, the tabs and slider work perfectly, but I am having an issue when the two are used in conjuction. For instance, clicking 'blue apron' and then clicking the left slide button (which should take you to the 'dave & busters' slide) takes you to the bliss slide. Or clicking the last slide using the tabs and then clicking the next button does not show anything. Can someone point out the flaw in the object that I have written. Any help is much appreciated!
GiftSlider = {
prev: '.slider-container .prev',
next: '.slider-container .next',
slide: '.slide',
slidesContainer: '#slides',
navLink: '.gift-nav li a',
init: function() {
// Initialize nextSlide function when clicking right arrow
$(this.next).click(this.nextSlide.bind(this));
$(this.next).click(this.activeTabs.bind(this));
// Initialize prevSlide function when clicking left arrow
$(this.prev).click(this.prevSlide.bind(this));
$(this.prev).click(this.activeTabs.bind(this));
// Initialize toggleSlides and activeTab functions when clicking nav links
$(this.navLink).click(this.toggleSlides.bind(this));
$(this.navLink).click(this.activeTabs.bind(this));
},
nextSlide: function(e) {
// Prevent default anchor click
e.preventDefault();
// Set the current slide
var currentSlide = $('.slide.current');
// Set the next slide
var nextSlide = $('.slide.current').next();
// remove the current class from the current slide
currentSlide.removeClass("current");
// Move the current slide to the end so we can cycle through
currentSlide.clone().appendTo(this.slidesContainer);
// remove the last slide so there is not two instances
currentSlide.remove();
// Add current class to next slide to display it
nextSlide.addClass("current");
},
prevSlide: function(e) {
// Prevent defaulct anchor click
e.preventDefault();
// Set the current slide
var currentSlide = $('.slide.current');
// Set the last slide
var lastSlide = $('.slide').last();
// remove the current class from the current slide
currentSlide.removeClass("current");
// Move the last slide to the beginning so we can cycle through
lastSlide.clone().prependTo(this.slidesContainer);
// remove the last slide so there is not two instances
lastSlide.remove();
// Add current class to new first slide
$('.slide').first().addClass("current");
},
toggleSlides: function(e) {
// Prevent defaulct anchor click
e.preventDefault();
var itemData = e.currentTarget.dataset.index;
var currentSlide = $('.slide.current');
currentSlide.removeClass("current");
newSlide = $('#slide-' + itemData);
// currentSlide.clone().appendTo(this.slidesContainer);
newSlide.addClass("current");
// console.log(newSlide);
},
activeTabs: function() {
// *** This could be much simpler if we didnt need to match the slider buttons
// *** up with nav tabs. Because I need to track the slider as well, I have
// *** made this its own function to be used in both instances
// get the active slide
var activeSlide = $('.slide').filter(':visible');
// get the active slide's id
var currentId = activeSlide.attr('id');
// grab just the number from the active slide's id
var currentNum = currentId.slice(-1);
// remove any active gift-nav links
$(this.navLink).removeClass("active");
// get the current tab by matching it to the current slide's id
var currentTab = $('.gift-nav li a[data-index="' + currentNum + '"]');
// make that active
currentTab.addClass("active");
}
}
$(document).ready(function(){
// Init our objects
GiftSlider.init();
});
The bug seems to be in toggleSlides.
EDIT: The following doesn't work
When the page loads, currentSlide is slide 1. Now say you click the 3rd tab. At this point, you need to move the slide before the 3rd tab i.e. the 2nd tab to the end. When you say currentSlide.clone().appendTo(this.slidesContainer);, you are instead moving the first slide to the end. Therefore no matter which tab you clicked, when you click the previous button it was going to the first slide.
If instead you do newSlide.prev().clone().appendTo(this.slidesContainer); the code seems to work fine.
toggleSlides: function(e) {
// Prevent defaulct anchor click
e.preventDefault();
var itemData = e.currentTarget.dataset.index;
var currentSlide = $('.slide.current');
currentSlide.removeClass("current");
newSlide = $('#slide-' + itemData);
//currentSlide.clone().appendTo(this.slidesContainer);
newSlide.prev().clone().appendTo(this.slidesContainer);
newSlide.addClass("current");
//console.log("In toggle slide: "+newSlide.next().attr("id"));
//console.log("In toggle slide: "+newSlide.prev().attr("id"));
//console.log("In toggle slide: "+$('.slide.current').next().attr("id"));
},
This seems to work. Check it out at https://jsfiddle.net/rfgnm992/1/. Your nextSlide and previousSlide seems to keep the current slide at the beginning. toggleSlides wasn't doing that.
toggleSlides: function(e) {
// Prevent defaulct anchor click
e.preventDefault();
var itemData = e.currentTarget.dataset.index;
var currentSlide = $('.slide.current');
currentSlide.removeClass("current");
newSlide = $('#slide-' + itemData);
//keep new slide at the beginning and move the preceding slides to the end
newSlide.nextAll().addBack().prependTo(this.slidesContainer);
//console.log("NewSlide.next: "+newSlide.next().attr('id') + "NewSlide.next.next: "+newSlide.next().next().attr('id')+"newSlide.next.next.next: "+newSlide.next().next().next().attr('id'));
//currentSlide.clone().appendTo(this.slidesContainer);
newSlide.addClass("current");
// console.log(newSlide);
},
sorry I got back a bit later than expected. Took a look there. Think you're over-complicating things with the whole append/prepend/cloning thing.
I got it working, but there's a minor bug in it still. It cycles grand, forward and back and the correct links highlight, however when you click on a random link it doesn't immediately highlight, but when you click the next/prev buttons, the relevant links from the chosen image highlight. It's certainly an advance!!! I'm sure I could roll it out with another look, but its 2am here, and I've already been looking at it for an hour and a half!
Here's a fiddle and a snippet (just js cos my msg was too long - I just took out the paragraph at the end of the content, no css changes)
GiftSlider = {
prev: '.slider-container .prev',
next: '.slider-container .next',
slide: '.slide',
slidesContainer: '#slides',
navLink: '.gift-nav li a',
init: function() {
// Initialize nextSlide function when clicking right arrow
$(this.next).click(this.nextSlide.bind(this));
$(this.next).click(this.activeTabs.bind(this));
// Initialize prevSlide function when clicking left arrow
$(this.prev).click(this.prevSlide.bind(this));
$(this.prev).click(this.activeTabs.bind(this));
// Initialize toggleSlides and activeTab functions when clicking nav links
$(this.navLink).click(this.activeTabs.bind(this));
$(this.navLink).click(this.toggleSlides.bind(this));
},
nextSlide: function(e) {
// Prevent default anchor click
e.preventDefault();
// Set the current slide
var currentSlide = $('.slide.current');
// Set the next slide
var currentId = currentSlide.attr('id');
var currNum = (currentId.slice(-1));
var nextNum;
var increment = 1;
if (currNum == 4){
nextNum = 1;
}
else
{
nextNum = parseInt(currNum) + parseInt(increment) ;
}
var nextSlide = $('#slide-' + nextNum);
// remove the current class from the current slide
currentSlide.removeClass("current");
// Add current class to next slide to display it
nextSlide.addClass("current");
// remove the last slide so there is not two instances
},
prevSlide: function(e) {
// Prevent default anchor click
e.preventDefault();
// Set the current slide
var currentSlide = $('.slide.current');
// Set the last slide
var currentId = currentSlide.attr('id');
var currNum = (currentId.slice(-1));
var prevNum;
var decrement =1;
if (currNum == 1){
prevNum = 4;
}
else
{
prevNum = parseInt(currNum) - parseInt(decrement) ;
}
var prevSlide = $('#slide-' + prevNum);
// Move the last slide to the beginning so we can cycle through
currentSlide.removeClass("current");
// Add current class to new first slide
prevSlide.addClass("current");
},
toggleSlides: function(e) {
// Prevent defaulct anchor click
e.preventDefault();
var itemData = e.currentTarget.dataset.index;
var currentSlide = $('.slide.current');
currentSlide.removeClass("current");
newSlide = $('#slide-' + itemData);
newSlide.addClass("current");
},
activeTabs: function() {
var activeSlide = $('.slide').filter('.current');
// get the active slide's id
var currentId = activeSlide.attr('id');
// grab just the number from the active slide's id
var currentNum = currentId.slice(-1);
// remove any active gift-nav links
$(this.navLink).removeClass("active");
// get the current tab by matching it to the current slide's id
var currentTab = $('.gift-nav li a[data-index="'+ currentNum + '"]');
// make that active
currentTab.addClass("active");
}
}
$(document).ready(function(){
// Init our objects
GiftSlider.init();
});
I'm trying to pin some divs in place and fade them in and out as a user scrolls down. My code looks like this so far:
$(window).on("load",function() {
var fadeDuration = 500;
function fade() {
// compute current window boundaries
var windowTop = $(window).scrollTop(),
windowBottom = windowTop + $(window).innerHeight(),
focusElt = null;
// find our focus element, the first visible .copy element,
// with a short-circuiting loop
$('.imgdiv').toArray().some(function(e, i) {
var objectTop = $(e).offset().top;
if ((objectTop >= windowTop) && (objectTop <= windowBottom)) {
focusElt = e;
return true;
}
console.log(focusElt);
});
// obscure all others
$('.focus').not(focusElt)
.removeClass('focus')
.fadeTo(fadeDuration, 0);
// focus on our focus element; if was the previous focus, nothing
// to do; but if it wasn't focus / wasn't showing before, make
// it visible and have class focus
$(focusElt).not('.focus')
.addClass('focus')
.fadeTo(fadeDuration, 1);
}
fade(); //Fade in completely visible elements during page-load
$(window).scroll(function() {fade();}); //Fade in elements during scroll
});
Here's the corresponding fiddle that almost does what I'm looking for, but instead of the green "Fade In" blocks moving upward and fading, I want them pined in place near the top of the window. As the "IMG DIVs" move past them they will fade and reappear with each new "IMG DIV". Here, I'm focusing on the particular green block and fading it when it becomes the focus element. Instead, what I need to do is, focus on the IMG DIV blocks, add a "pinned" class to the green blocks when they reach the top of the page, and fade the green blocks in and out.
Does anyone have any advice?
Part 2 of my question is how to do this with native JavaScript, and not rely on jQuery's dependency.
Ok, so lets split your first issue into two issues :)
First of all, you want to (in general) do something when some element becomes visible in the viewport and when it becomes invisible. So, basically, all you need is function like that:
watchElementIsInViewport(
$('.imgdiv'),
doSomethingWhenElementAppearedInViewport,
doSomethingWhenElementOutOfViewport
);
You know, that when element becomes visible, you want to show some other element. When element becomes invisible, you want to hide that related element. So now, define those two functions:
function doSomethingWhenElementAppearedInViewport(element) {
// retrieve text related with the element
var $copy = $(element).next('.copy');
// fade it in
$copy.fadeTo(500, 1);
}
function doSomethingWhenElementGotOutOfViewport(element) {
// retrieve text related with the element
var $copy = $(element).next('.copy');
// fade it out
$copy.fadeTo(500, 0);
}
What about watchElementIsInViewport? There is no magic inside, only logic you already created, but decoupled from showing of finding elements.
function watchElementIsInViewport($elements, elementAppearedInViewport, elementGotOutOfViewport) {
var currentlyVisible = [ ];
// retrieve positions once, assume it won't change during script is working
var positions = getVerticalBoundaries($elements);
function _scrollHandler() {
var viewportTop = window.scrollY;
var viewportBottom = viewportTop + window.innerHeight;
$elements.each(function(index, element) {
var elementPosition = positions[index];
/* if you wish to check if WHOLE element is in viewport
* var elementIsInViewport = (elementPosition.top >= viewportTop) &&
* (elementPosition.bottom <= viewportBottom);
*/
var elementIsInViewport = (elementPosition.top < viewportBottom) &&
(elementPosition.bottom > viewportTop);
var elementIndexInCurrentlyVisible = currentlyVisible.indexOf(element);
// if element is visible but was not visible before
if(elementIsInViewport && (elementIndexInCurrentlyVisible === -1)) {
elementAppearedInViewport(element);
currentlyVisible.push(element);
// if element is not visible but was visible before
} else if(!elementIsInViewport && (elementIndexInCurrentlyVisible !== -1)) {
elementGotOutOfViewport(element);
currentlyVisible.splice(elementIndexInCurrentlyVisible, 1);
}
});
}
// initial check & update
_scrollHandler();
// check & update on every scroll
$(window).on('scroll', _scrollHandler);
}
And that's all. Working example.
I am making a page in which you select sandwich toppings with checkboxes, and after you click submit those sandwich option divs containing images will show. By default they are all hidden. I want the first div that is shows to be fixed while the rest of the toppings layer on top of the first one. I'll eventually set it up so that while you scroll down, the toppings appear to move up and over the previous topping. So, if you select bagel-bottom and bacon, you scroll down to see a bagel bottom and keep scrolling for the bacon to move up on top of the bagel bottom.
The first topping div in the HTML order is bagel-bottom, but even though I select "bacon" the only div that shows is bagel-bottom.
I have the jsfiddle here: http://jsfiddle.net/3kgnkh4w/
$(document).ready(function() {
$('#sandwich-option-submit').click(function() {
var checkedTopping = $('.sandwich-option').val();
var divCheckedTopping = $('#'+ checkedTopping);
var optionsContainer = $('#sandwich-selection-container');
if($('.sandwich-option').is(':checked')) {
$(divCheckedTopping).show().addClass('display');
$(divCheckedTopping).eq(0).addClass('first-checked-topping');
} else {
$(divCheckedTopping).hide();
};
});
});
The jQuery class selector returns an array of all objects within that class, so you need to check all of them. Here is the fixed jQuery:
$(document).ready(function() {
$('#sandwich-option-submit').on("click", function() {
var checkedToppings = $('.sandwich-option');
for(var i = 0; i < checkedToppings.length; i++){
var checkedTopping = $(checkedToppings[i]);
var checkedToppingVal = $(checkedTopping).val();
var divCheckedTopping = $('#' + checkedToppingVal);
var optionsContainer = $('#sandwich-selection-container');
if($(checkedTopping).is(':checked')) {
$(divCheckedTopping).show().addClass('display');
$(divCheckedTopping).eq(0).addClass('first-checked-topping');
} else {
$(divCheckedTopping).hide();
}
}
});
});
Alternatively, you can have each topping shown on check, using the toggle() jQuery function which toggles between the hide() and show() method. Here is the code for that:
$(document).ready(function() {
$('.sandwich-option').on("click", function() {
var checkedToppingVal = $(this).val();
var divCheckedTopping = $('#' + checkedToppingVal);
$(divCheckedTopping).toggle();
});
});
You can then remove the submit buttton.
I want to add a class active to the current menu item on scroll and click. It’s a single (long) page with different sections. When click on an item, the active state should switch to the current one.
I came up with the following code, but doesn’t now how I can integrate the above:
// Click event
$('#primary-navwrapper li a[href^="#"]').click(function(event) {
// Prevent from default action to intitiate
event.preventDefault();
// The id of the section we want to go to
var anchorId = $(this).attr('href');
// Our scroll target : the top position of the section that has the id referenced by our href
var target = $(anchorId).offset().top - offset;
console.log(target);
$('html, body').stop().animate({ scrollTop: target }, 500, function () {
window.location.hash = anchorId;
});
return false;
});
To add active class on click is simple using jQuery. You just need this code in the click handler
//remove active from all anchor and add it to the clicked anchor
$('#primary-navwrapper li a[href^="#"]').removeClass("active")
$(this).addClass('active');
And for the scroll part you need to monitor the position of the scroll bar and compare it with every page offset like so
//check the pages when scroll event occurs
$(window).scroll(function(){
// Get the current vertical position of the scroll bar
position = $(this).scrollTop();
$('#primary-navwrapper li a[href^="#"]').each(function(){
var anchorId = $(this).attr('href');
var target = $(anchorId).offset().top - offset;
// check if the document has crossed the page
console.log(position,target);
if(position>=target){
//remove active from all anchor and add it to the clicked anchor
$('#primary-navwrapper li a[href^="#"]').removeClass("active")
$(this).addClass('active');
}
})
Here is a demo http://jsfiddle.net/k5afwfva/
<nav>
item
item
item
item
</nav>
var el = $(".item"),
yPos = 0;
el.click(function(event){
event.preventDefault();
$(this).addClass("active").siblings().removeClass("active");
});
$(window).scroll(function(){
yPos = $(this).scrollTop();
//i'm almost sure that you will need to calculate offset of your section to know when to switch classes
if(yPos > 100){
el.removeClass("active").eq(1).addClass("active");
}
if(yPos > 200){
el.removeClass("active").eq(2).addClass("active");
}
//etc....
});