Updating underline-slider position on resize - javascript

I have an underline slider for my navigation links that get the specific width and position based on the clicked element, <li>, using the getBoundingClientRect(). This works as expected, but on resizing the browser window I would like to update the underline-slider's position.
As it is right now I'm only grabbing the width and position of the first <li>. What I need and want to do is to target the specific <li> that currently has the underline-slider and update its width and position on resizing so the slider follows along during resize with the right <li> element.
Any tip how I could grab the currently active <li>??
This is the codepen where I have the example in where I only grab the first <li> everytime on resize:
https://codepen.io/Shenden/pen/PELbOM
the script looks like this:
const lists = document.querySelectorAll('.dropdown > li');
const slider = document.querySelector('.slider');
function handleEnter() {
// from current li
const dropCoords = this.getBoundingClientRect();
//get the current li's width and height
//apply coords of li's to the slider-div elem
slider.style.setProperty('opacity', '1');
slider.style.setProperty('width', `${dropCoords.width}px`);
slider.style.setProperty('transform', `translate(${dropCoords.left}px)`);
}
//for each li-elem clicked trigger handleEvent function
lists.forEach(listLink => listLink.addEventListener('click', handleEnter));
window.addEventListener('resize', function(){
const elem = document.querySelector('.dropdown > li').getBoundingClientRect();
slider.style.setProperty('transform', `translate(${elem.left}px)`);
slider.style.setProperty('width', `${elem.width}px`);
});

You could keep track of which item should be active by flagging it with a class like this :
const lists = document.querySelectorAll('.dropdown > li');
const slider = document.querySelector('.slider');
function handleEnter() {
//remove active item indicator class if it is present
document.querySelector(".activeItem").classList.remove("activeItem");
//add the active item indicator class to the current item
this.classList.add("activeItem");
//perform the move code for the underline
const dropCoords = this.getBoundingClientRect();
//get the current li's width and height
//apply coords of li's to the slider-div elem
slider.style.setProperty('opacity', '1');
slider.style.setProperty('width', `${dropCoords.width}px`);
slider.style.setProperty('transform', `translate(${dropCoords.left}px)`);
}
//for each li-elem clicked trigger handleEvent function
lists.forEach(listLink => listLink.addEventListener('click', handleEnter));
window.addEventListener('resize', function(){
//select the correct item using the activeItem indicator class
const elem = document.querySelector('.dropdown > li.activeItem').getBoundingClientRect();
slider.style.setProperty('transform', `translate(${elem.left}px)`);
slider.style.setProperty('width', `${elem.width}px`);
});
CodePen fork : right here

Related

How to find index of previous active class

I am trying to make a slider. Most of it is working till here, but I am not getting further as I want to find the index of the item which I removed the class as last from.(before the click)
So when I click on a dot, the clicked dot must be enabled and get the active class and the class on the previous active dot needs to be removed.
navDots.addEventListener('click', e => {
// make only dot's clickable
const targetDot = e.target.closest('.dots');
// disable NavDots for clicks
if(!targetDot)return;
//Find the index of the clicked btn
const dotIndex = Array.from(targetDot.parentNode.children).indexOf(targetDot);
console.log(dotIndex);
// Add the active class tot the dot
navDots.children[dotIndex].classList.add('active');
//HOW TO REMOVE PREVIOUS DOT ACTIVE style?
//show image with dotIndex
showImages(dotIndex);
});
Thank you for helping,
r,y.
The most efficient way is to store active element in a variable:
let activeElement = null;
navDots.addEventListener('click', e => {
// make only dot's clickable
const targetDot = e.target.closest('.dots');
// disable NavDots for clicks
if(!targetDot)return;
//Find the index of the clicked btn
const dotIndex = Array.from(targetDot.parentNode.children).indexOf(targetDot);
console.log(dotIndex);
// remove previous active class
activeElement && activeElement.classList.remove("active");
// save new active element
activeElement = navDots.children[dotIndex];
// Add the active class tot the dot
activeElement.classList.add('active');
//HOW TO REMOVE PREVIOUS DOT ACTIVE style?
//show image with dotIndex
showImages(dotIndex);
});

How do you script an addEventListener to toggle back a hamburger menu in javascript

Alright so I'm at the last phase of adding finishing touches to my website in order to publish it over the internet but I can't quite figure out how to add an eventListener that closes the navbar hamburger menu without having to manually scroll back up to the landing page to toggle it back. This is what I have so far.
//Select element function
const selectElement = function (element) {
return document.querySelector(element);
};
let menuToggler = selectElement('.menu-toggle');
let body = selectElement('body');
menuToggler.addEventListener('click', function () {
body.classList.toggle('open');
});
// Parallax scrolling effect
window.addEventListener('scroll', function () {
const parallax = document.querySelector('.parallax');
let scrollPosition = window.pageYOffset;
parallax.style.transform = 'translateY(' + scrollPosition * -.10 + 'px)';
});
Here's a ss of the HTML document with the nav tag for context; I'd appreciate a thorough explanation of what the js bit is doing as I'm fairly new to it. : ) Cheers!
Attached is a link to a video demonstrating how the hamburger menu responds
To close navbar when click a nav-link you can do this:
const menuToggler = document.querySelector('.menu-toggle');
const body = document.body;
const navItems = document.querySelectorAll(".nav-item");
Array.from(navItems).map(el => {
el.addEventListener('click', function () {
body.classList.remove('open');
});
});
menuToggler.addEventListener('click', function () {
body.classList.toggle('open');
});
// Parallax scrolling effect
window.addEventListener('scroll', function () {
const parallax = document.querySelector('.parallax');
let scrollPosition = window.pageYOffset;
parallax.style.transform = 'translateY(' + scrollPosition * -.10 + 'px)';
});
You want to add an event listener on the entire document, and check if the clicked element is your menu button, your nav element, or a child of your nav element. If it is not then remove the class.
document.addEventListener('click',(e)=>{
//the element clicked
let target = e.target;
//your nav element
let nav = document.querySelector('nav');
//if it's not the menu button, or nav, or a child of nav
if(target != menuToggler && target != nav && target.closest('nav') != nav){
body.classList.remove('open');
}
});
You could also use a key event like the user hitting ESC, in which you would just remove the class
document.addEventListener('keydown',(e)=>{
if(e.key=="Escape"){
body.classList.remove('open');
}
});
If you need to close it after a link in your nav has been clicked then add the remove code to your existing link listeners or add a new one to your links (or a delegated listener on your nav) and remove it there
document.querySelector('nav').addEventListener('click',(e)=>{
if(e.target.classList.contains('nav-link')){
body.classList.remove('open');
}
});

jQuery offset not working as expected

I am getting the value of an data-anchor for each "li" I click on and I use it to match the id value of list of divs
<div id="rateCardsContainer">
<div id="one"><div>
<div id="two"><div>
</div>
I am than using the following function so that when I click on li that has the same id as div than the div will scroll to the top:
AIG.RC.setModalEventsListener = function() {
$(AIG.RC.LIST_NODE).on('click', 'li', function() {
var currentID = $(this).attr('data-anchor');
var currentOffset = $('#' + currentID).offset().top;
console.log(currentOffset);
$('#rateCardsContainer').animate({scrollTop:currentOffset}, 500);
});
};
This "console.log(currentOffset);" returns very odd values as you can see.I click on the list on the left and the corresponding div on the right with same id should scroll to the top, however it has very irregular scrolls

Animating the active item background in a nav fails

I have the following markup:
<ul>
<li>first</li>
<li>largerWord</li>
<li class="active">third</li>
</ul>
The items have transparent background except the active class, that has a blue color.
The idea is that when user clicks (not the active) instead of switching the backgrounds i want to move the background, you can see the result almost done here, but it fails the first click (see end of question for error log):
http://jsfiddle.net/FeV55/26/
What i do is to dynamically create a lower z-index <li> and animate the left according the (clicked) item offset and the width according the (clicked) item width
The jquery code:
$(document).ready(function(){
$('ul li a').not('.active').click(function(){
/*caching*/
var activa = $('li.active');
var bg = $('li.back');;
var list = $(this).closest('ul');
/*when it's first click the background item doesnt exist*/
if(bg.length>0 == false){
list.append('<li class="back"></li>');
}
var width = $(this).outerWidth(true);
var leftUL = list.offset().left;
var leftThis = $(this).offset().left;
var left = leftThis - leftUL;
/*Remove class to previous active*/
activa.removeClass('active');
/*Cancel background even if parent is active*/
$(this).addClass('noBg');
/*Update active class*/
$(this).parent().addClass('active');
/*Move the background to its offset*/
bg.animate({'left':left,'width':width});
/*logs*/
$('#oUl').text(leftUL);
$('#ocl').text($(this).offset().left);
$('#odf').text(left);
$('#obg').text(bg.offset().left);
});
});
But it fails on first click, firebug logs:
bg.offset() is null
[Parar en este error]
$('#obg').text(bg.offset().left);
Question is why? the item should exist by then in any case..
Be sure to select the element you've assigned to bg after you create it:
var bg = $('li.back');
if (bg.length == 0) {
list.append('<li class="back"></li>');
bg = $('li.back');
}
Even better, create the element into bg:
if (bg.length == 0) {
bg = $('<li class="back"></li>');
list.append(bg);
}

Z-index doesn't seem to change anything

I am building some custom dropdown controls and the z-index isn't working properly.
// Add the empty class to the container div if no check boxes are checked.
$('.qx-container').each(function ()
{
var container = $(this);
if (!container.find('input[type="checkbox"]').is(':checked'))
{
container.find('.qx-content').text($('.qx-content').attr('empty-message'));
container.find('.qx-content').addClass('qx-empty-content');
}
else
{
handleCheckBoxToggle(container.find('input[type="checkbox"]'));
}
});
// Wire a mouse enter event to the container div. Turns the drop-down list's colors to gray if the slider isn't visible.
$('.qx-container').mouseenter(function ()
{
var container = $(this);
if (!container.find('.qx-slider').is(':visible'))
{
container.find('.qx-container-border-outer').addClass('qx-container-border-outer-hover');
container.find('.qx-container-border-inner').addClass('qx-container-border-inner-hover');
container.find('.qx-container-border-background').addClass('qx-container-border-background-hover');
}
container.data('hoverState', true);
});
// Wire a mouse leave event to the container div. Turns the drop-down list's colors to white if the slider isn't visible and
// sets the container div's empty class if no check boxes are checked.
$('.qx-container').mouseleave(function ()
{
var container = $(this);
if (!container.find('.qx-slider').is(':visible'))
{
container.find('.qx-container-border-outer').removeClass('qx-container-border-outer-hover');
container.find('.qx-container-border-inner').removeClass('qx-container-border-inner-hover');
container.find('.qx-container-border-background').removeClass('qx-container-border-background-hover');
}
if (container.text() == '')
{
container.text($(this).attr('empty-message'));
container.addClass('qx-empty-content');
}
container.data('hoverState', false);
});
// Wire a click event to the content div. Shows or hides the slider and changes the drop-down list's colors based on the slider's visibility.
$('.qx-container-border-outer').click(function ()
{
var outer = $(this);
var inner = $(this).find('.qx-container-border-inner');
var background = $(this).find('.qx-container-border-background');
var container = outer.closest('.qx-container');
var slider = container.find('.qx-slider');
var sliders = $('.qx-container').find('.qx-slider').not(slider);
// Close any other open sliders.
sliders.each(function ()
{
$(this).hide();
var containerDiv = $(this).closest('.qx-container');
var outerBorder = containerDiv.find('.qx-container-border-outer');
var innerBorder = containerDiv.find('.qx-container-border-inner');
var backgroundDiv = containerDiv.find('.qx-container-border-background');
outerBorder.removeClass('qx-container-border-outer-selected');
outerBorder.removeClass('qx-container-border-outer-hover');
innerBorder.removeClass('qx-container-border-inner-selected');
inner.removeClass('qx-container-border-inner-hover');
backgroundDiv.removeClass('qx-container-border-background-selected');
background.removeClass('qx-container-border-background-hover');
});
// Toggle the slider.
slider.slideToggle(50, function ()
{
if (!container.data('hoverState'))
{
outer.removeClass('qx-container-border-outer-hover');
inner.removeClass('qx-container-border-inner-hover');
background.removeClass('qx-container-border-background-hover');
}
if (slider.is(':visible'))
{
outer.addClass('qx-container-border-outer-selected');
inner.addClass('qx-container-border-inner-selected');
background.addClass('qx-container-border-background-selected');
}
else
{
outer.removeClass('qx-container-border-outer-selected');
inner.removeClass('qx-container-border-inner-selected');
background.removeClass('qx-container-border-background-selected');
}
});
});
// Wire a change event to the check boxes. Stores the user's selection in the content element & displays the text of which check box is checked.
$('.qx-slider').find($('input[type="checkbox"]')).click(function (event)
{
event.stopPropagation();
handleCheckBoxToggle($(this));
});
// Wire a mouse enter event to the slider row so the background color changes to gray.
$('.qx-slider-row').mouseenter(function ()
{
$(this).find('td').addClass('qx-slider-cell-hover');
});
// Wire a mouse leave event to the slider row so the background color changes to white.
$('.qx-slider-row').mouseleave(function ()
{
$(this).find('td').removeClass('qx-slider-cell-hover');
});
// Wire a mouse click event to the slider row to toggle the check box's checked attribute.
$('.qx-slider-row').click(function ()
{
var checkBox = $(this).find('input[type="checkbox"]');
checkBox.attr('checked', !checkBox.is(':checked'));
handleCheckBoxToggle(checkBox);
});
// Handles the checked event for each check box.
function handleCheckBoxToggle(checkBox)
{
// Reference to the containing content div.
var content = checkBox.closest('.qx-container').find('.qx-content')
// Holds the checked values (data is associated with the content div).
var checkBoxData = content.data('checkBoxData');
// Reference to all the check boxes in the slider.
var checkBoxes = checkBox.closest('table').find('input[type="checkbox"]');
// Create an array of check box values (associated with the content div) if it doesn't exist.
if (checkBoxData == undefined)
{
checkBoxData = new Array();
checkBoxes.each(function ()
{
checkBoxData[$(this).attr('interest-level-description')] = 0;
});
}
// Store the checked values of each check box.
checkBoxes.each(function ()
{
checkBoxData[$(this).attr('interest-level-description')] = $(this).is(':checked') ? 1 : 0;
});
// Create a commo-delimited string from the checked values.
content.data('checkBoxData', checkBoxData);
var output = '';
for (var property in checkBoxData)
{
if (checkBoxData[property] == 1)
{
output += property + ", ";
}
}
// Remove the trailing comma.
if (output.match(",") != null)
{
output = output.substr(0, output.length - 2);
}
// Set the content text and class based on the checked values.
if (output == '')
{
content.text(content.attr('empty-message'));
content.addClass('qx-empty-content');
}
else
{
content.text(output);
content.removeClass('qx-empty-content');
}
}
http://jsfiddle.net/heray/1/
If you click the items you'll notice the dropdown menu appears behind subsequent dropdowns. I've added z-indexes and position relative to every element I can think of.
Just so you understand how to use z-index, never assign a z-index to something unless you want it to be displayed over top of another element. The best practice is not to define a z-index (especially not assigning a value of 0) until you need to. In your example, the class you have after clicking the button (the actual dropdown) should have a z-index of 1 or greater, and nothing else in your document should have any z-index definition. if you have an element with z-index of 1, and then you put another element in the same physical spot with a z-index of 2 -- the container with the higher z-index will overlap the one's with the lower.
Remove the z-indexes from the dropdowns. Also, what makes you think that setting a z-index of 0 on them will make things better?
Updated fiddle.

Categories

Resources