targeting child of event.currentTarget - javascript

I found a simple tab-menu jQuery plugin that needs some adapting for a project of mine. The tabs in question - being absolutely positioned - are taken out of the flow and as such don’t contribute to the wrapping div’s height, so the background behind them don’t show.
I am trying to force the height of the wrapping div (containing the background image) to match the height of the selected tab (+400px for nav and header) and to achieve that on the fly I am adapting the original jQuery file.
Here is the code (with the few extra lines (commented ‘added!’) of mine).
var cbpHorizontalMenu = (function () {
var $listItems = $('#cbp-hrmenu > ul > li'),
$menuItems = $listItems.children('a'),
$body = $('body'),
current = -1;
function init() {
$menuItems.on('click', open);
$listItems.on('click', function (event) {
event.stopPropagation();
});
}
function open(event) {
if (current !== -1) {
$listItems.eq(current).removeClass('cbp-hropen');
}
var $item = $(event.currentTarget).parent('li'),
idx = $item.index();
if (current === idx) {
$item.removeClass('cbp-hropen');
//added!
current = -1;
} else {
$item.addClass('cbp-hropen');
current = idx;
$body.off('click').on('click', close);
var content2Height = jQuery(".cbp-hrsub").height() + 400;
jQuery('#content2').height(content2Height); //added
}
return false;
}
function close(event) {
$listItems.eq(current).removeClass('cbp-hropen');
//added!
current = -1;
}
return {
init: init
};
})();
It does something, but not what I need. It gets the height of the first div.cbp-hrsub and applies it (+400px) to the div.content2. What I need is to target the current tab (a child of event.currentTarget, me thinks?), to calculate its height and to apply it to the content2 div.
This is a simplified HTML if that helps:
<div class="content2">
<nav id="cbp-hrmenu" class="cbp-hrmenu">
<ul>
<li>
tab 1
<div class="cbp-hrsub">
<div class="cbp-hrsub-inner">
I am 1st tab, 100px height.
</div>
</div>
</li>
<li>
tab 2
<div class="cbp-hrsub">
<div class="cbp-hrsub-inner">
I am 2nd tab, 200px height.
</div>
</div>
</li>
<li>
Nigel's CV
<div class="cbp-hrsub">
<div class="cbp-hrsub-inner">
I am 3rd tab, 300px height.
</div>
</div>
</li>
</ul>
</nav>
Just to clarify, I want to keep the original plugin as it is, just to insert something instead of my 2 lines, towards the end of file. (var content2Height = jQuery(".cbp-hrsub").height() + 400;jQuery('#content2').height(content2Height); //added
Thanks everyone for their time.
Zel

I get inconsistent results when targeting a parent-container with .parent(). In this line:
var $item = $(event.currentTarget).parent('li'),
idx = $item.index();
Try instead to use .closest():
var $item = $(event.currentTarget).closest('li'),
idx = $item.index();
Oh, wait! I see the issue:
var content2Height = jQuery(".cbp-hrsub").height() + 400;
You are retrieving all the .cbp-hrsub classed elements, here. It's going to try to return a height, and I'm not exactly certain how jQuery determines that when it's looking at an array, but I'm guessing it just picks the first element out of the array.
What you're really needing at this point, then, is something like this:
var content2Height = $item.first(".cbp-hrsub").height() + 400;
THAT should give you the height of .cbp-hrsub contained within the current item (found above), and not that of the first .cbp-hrsub in the array.

Related

In pure JS, I am trying to set two similar classes but with different numbers or to set the same class to two different elemnts

I tried to set the same class imagens-giratorias to two elements or to set imagens-giratorias and imagens-giratorias-2. The class worked in first element, and the same class stopped of animating in the second element.
[I provide the JSFiddle at the end.]
Check the #rafaelcastrocouto's original code at https://stackoverflow.com/a/59524483/8041366. If you prefer reading the complete code here, here is the code taken from there, but with a bit modified:
var counter = 1;
var div = document.querySelector('.imagens-giratorias');
var imagens = document.querySelectorAll('.imagens-giratorias img');
var showNext = function () {
counter++;
if (counter > 3) counter = 1;
div.classList.remove('imagem1', 'imagem2', 'imagem3')
div.classList.add('imagem'+counter);
};
for (var img of imagens) {
img.addEventListener('animationend', showNext);
}
And small CSS snippet:
<div class="section-2">
<div class="item-2">
<div class="imagens-giratorias imagem1">
</div>
</div>
</div>
<div class="section-3">
<div class="item-2">
<div class="imagens-giratorias imagem1">
</div>
</div>
Or
<div class="section-3">
<div class="item-2">
<div class="imagens-giratorias-2 imagem1">
</div>
</div>
1st solution, that same original code above I am referring.
2nd solution:
var div = document.querySelector('.imagens-giratorias, .imagens-giratorias-2');
var imagens = document.querySelectorAll('.imagens-giratorias img, .imagens-giratorias-2 img');
3rd solution
var div = document.querySelector('[class^=imagens-giratorias]');
var imagens = document.querySelectorAll('[class^=imagens-giratorias] img');
4th solution
const contador = 1;
const div = document.querySelector('.imagens-giratorias');
const imagens = document.querySelectorAll('.imagens-giratorias img');
I also tried to use from multiple selectors with document.querySelectorAll. No luck.
But all these solutions did not work.
JSFiddle
Please pay attention to two elements. While one element will always animate, another will stop of animating.
https://jsfiddle.net/gusbemacbe/mbp84u6r/2/
If I understand you correctly you're trying to grab elements that have a class name starting with imagens-giratorias. If that's the case, use the ^ attribute selector as shown below:
document.querySelectorAll("[class^="imagens-giratorias"]")
Update:
Based on your update it appears that only one of your two divs' images is animating but in reality they're stacked on top of each of other. Feel free to use whatever layout method you want but for demonstration's sake I floated one left and the other right. Other that it was a matter of looping through your divs and assigning your function to their child images as so:
var divs = document.querySelectorAll('.imagens-giratórias');
var contador = 1;
var mostrarPróximo = function(div) {
contador++;
if (contador > 3) contador = 1;
div.classList.remove('imagem1', 'imagem2', 'imagem3')
div.classList.add('imagem' + contador);
};
Array.from(divs).forEach(function(div, index) {
var images = div.querySelectorAll('img');
Array.from(images).forEach(function(img) {
img.addEventListener('animationend', mostrarPróximo.bind(null, div));
});
});
https://jsfiddle.net/1r6yjf5s/

JavaScript - I condition "if" doesn't respond properly

Here is the idea behind the code: The article has an avatar object in the beginning. e.g.:
<div class="column-right">
<div class="inner">
<h3>Header of article</h3>
<div id="avatar">
<img>
<div>avatar title</div>
</div>
<p>long text</p>
</div>
</div>
When a user scrolls down, the avatar obviously disappears. So I wanted to make a clone of said avatar and put it in the left column using position fixed.
Here is the JavaScript code:
document.body.onscroll = function(e) {
var scrolled = document.documentElement.scrollTop;
var newAv;
if ( (scrolled > avatarCoords.bottom) && !newAv ) {
//check if user scrolled below the avatar. if clone of avatar exists, do anything:
newAv = avatar.cloneNode(true);
//clone actual element, whick user can't see right now
newAv.style.position = "fixed";
newAv.style.left = 2 + "px";
newAv.style.top = 10 + "px";
document.body.appendChild(newAv); //add clone in document
}
if( (scrolled < avatarCoords.bottom) && newAv ) {
newAv.parentNode.removeChild(newAv);
}
}
function getCoords(element) {
var box = element.getBoundingClientRect();
return {
bottom: box.top + avatar.offsetHeight
}
}
I don't understand why the first condition keeps responding even though the variable newAv is assigned. !newAv is supposed to be false after first cloning.
If you can answer the first question, tell me why newAv can't be removed as well.
Thank you!
You keep redefining newAV inside the onscroll function.
Make this a global variable (so this remembers the value from previous call):
var newAv;
document.body.onscroll = function(e) {
var scrolled = document.documentElement.scrollTop; // etc

Resize set of List Elements to width of widest list element?

I'm trying to get my list elements in my pure css dropdown to be uniform size based on the width of the largest element in the ul. This is the current html:
<nav>
<ul id="drop-nav">
<li>Home</li>
<li onmouseover="SizeChildren('DropDown1')">Filler
<ul id="DropDown1">
<li>Filler1</li>
<li>Filler223456</li>
<li>Filler212314235234523</li>
</ul>
</li>
</ul>
<nav>
I run the SizeChildren function passing the parent containers id to the function (note: I realize there is redundant code in here, it's stuff I was messing with):
function SizeChildren(ElementID)
{
var WidthArray = new Array();
UOList = document.getElementById(ElementID);
WidthArray = UOList.getElementsByTagName('li');
var MaxWidth;
for(i = 0; i < WidthArray.length; i++)
{
if(i == 0)
{
MaxWidth = WidthArray[i].offsetWidth;
}
else
{
var curr = WidthArray[i].offsetWidth;
if(curr > MaxWidth)
{
MaxWidth = curr;
}
}
}
var nodes = document.getElementById(ElementID).childNodes;
for(i = 0; i < nodes.length;i++)
{
if(nodes[i].nodeName.toLowerCase() == 'li')
{
nodes[i].style.width = MaxWidth;
}
}
}
There are two problems I am running into with the current code, first off, it is calculating the max width to be 82 (should be closer to 150 or close to it) and it is not actually setting the widths, meaning that nothing is actually changing on the page, and that I have no clue about, even with the max width getting miscalculated this portion of the code should still work, so what am I missing? I tried using .offsetWidth as well and that still produced no fruit.
I can't use JQuery, flex boxes, or anything like that, it has to be done in JavaScript if possible.

Highlight Menu Item when Scrolling Down to Section

I know this question have been asked a million times on this forum, but none of the articles helped me reach a solution.
I made a little piece of jquery code that highlights the hash-link when you scroll down to the section with the same id as in the hash-link.
$(window).scroll(function() {
var position = $(this).scrollTop();
$('.section').each(function() {
var target = $(this).offset().top;
var id = $(this).attr('id');
if (position >= target) {
$('#navigation > ul > li > a').attr('href', id).addClass('active');
}
});
});
The problem now is that it highlights all of the hash-links instead of just the one that the section has a relation to. Can anyone point out the mistake, or is it something that I forgot?
EDIT:
I have modified my answer to talk a little about performance and some particular cases.
If you are here just looking for code, there is a commented snippet at the bottom.
Original answer
Instead of adding the .active class to all the links, you should identify the one which attribute href is the same as the section's id.
Then you can add the .active class to that link and remove it from the rest.
if (position >= target) {
$('#navigation > ul > li > a').removeClass('active');
$('#navigation > ul > li > a[href=#' + id + ']').addClass('active');
}
With the above modification your code will correctly highlight the corresponding link. Hope it helps!
Improving performance
Even when this code will do its job, is far from being optimal. Anyway, remember:
We should forget about small efficiencies, say about 97% of the time:
premature optimization is the root of all evil. Yet we should not pass
up our opportunities in that critical 3%. (Donald Knuth)
So if, event testing in a slow device, you experience no performance issues, the best you can do is to stop reading and to think about the next amazing feature for your project!
There are, basically, three steps to improve the performance:
Make as much previous work as possible:
In order to avoid searching the DOM once and again (each time the event is triggered), you can cache your jQuery objects beforehand (e.g. on document.ready):
var $navigationLinks = $('#navigation > ul > li > a');
var $sections = $(".section");
Then, you can map each section to the corresponding navigation link:
var sectionIdTonavigationLink = {};
$sections.each( function(){
sectionIdTonavigationLink[ $(this).attr('id') ] = $('#navigation > ul > li > a[href=\\#' + $(this).attr('id') + ']');
});
Note the two backslashes in the anchor selector: the hash '#' has a special meaning in CSS so it must be escaped (thanks #Johnnie).
Also, you could cache the position of each section (Bootstrap's Scrollspy does it). But, if you do it, you need to remember to update them every time they change (the user resizes the window, new content is added via ajax, a subsection is expanded, etc).
Optimize the event handler:
Imagine that the user is scrolling inside one section: the active navigation link doesn't need to change. But if you look at the code above you will see that actually it changes several times. Before the correct link get highlighted, all the previous links will do it as well (because their corresponding sections also validate the condition position >= target).
One solution is to iterate the sections for the bottom to the top, the first one whose .offset().top is equal or smaller than $(window).scrollTop is the correct one. And yes, you can rely on jQuery returning the objects in the order of the DOM (since version 1.3.2). To iterate from bottom to top just select them in inverse order:
var $sections = $( $(".section").get().reverse() );
$sections.each( ... );
The double $() is necessary because get() returns DOM elements, not jQuery objects.
Once you have found the correct section, you should return false to exit the loop and avoid to check further sections.
Finally, you shouldn't do anything if the correct navigation link is already highlighted, so check it out:
if ( !$navigationLink.hasClass( 'active' ) ) {
$navigationLinks.removeClass('active');
$navigationLink.addClass('active');
}
Trigger the event as less as possible:
The most definitive way to prevent high-rated events (scroll, resize...) from making your site slow or unresponsive is to control how often the event handler is called: sure you don't need to check which link needs to be highlighted 100 times per second! If, besides the link highlighting, you add some fancy parallax effect you can ran fast intro troubles.
At this point, sure you want to read about throttle, debounce and requestAnimationFrame. This article is a nice lecture and give you a very good overview about three of them. For our case, throttling fits best our needs.
Basically, throttling enforces a minimum time interval between two function executions.
I have implemented a throttle function in the snippet. From there you can get more sophisticated, or even better, use a library like underscore.js or lodash (if you don't need the whole library you can always extract from there the throttle function).
Note: if you look around, you will find more simple throttle functions. Beware of them because they can miss the last event trigger (and that is the most important one!).
Particular cases:
I will not include these cases in the snippet, to not complicate it any further.
In the snippet below, the links will get highlighted when the section reaches the very top of the page. If you want them highlighted before, you can add a small offset in this way:
if (position + offset >= target) {
This is particullary useful when you have a top navigation bar.
And if your last section is too small to reach the top of the page, you can hightlight its corresponding link when the scrollbar is in its bottom-most position:
if ( $(window).scrollTop() >= $(document).height() - $(window).height() ) {
// highlight the last link
There are some browser support issues thought. You can read more about it here and here.
Snippet and test
Finally, here you have a commented snippet. Please note that I have changed the name of some variables to make them more descriptive.
// cache the navigation links
var $navigationLinks = $('#navigation > ul > li > a');
// cache (in reversed order) the sections
var $sections = $($(".section").get().reverse());
// map each section id to their corresponding navigation link
var sectionIdTonavigationLink = {};
$sections.each(function() {
var id = $(this).attr('id');
sectionIdTonavigationLink[id] = $('#navigation > ul > li > a[href=\\#' + id + ']');
});
// throttle function, enforces a minimum time interval
function throttle(fn, interval) {
var lastCall, timeoutId;
return function () {
var now = new Date().getTime();
if (lastCall && now < (lastCall + interval) ) {
// if we are inside the interval we wait
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
lastCall = now;
fn.call();
}, interval - (now - lastCall) );
} else {
// otherwise, we directly call the function
lastCall = now;
fn.call();
}
};
}
function highlightNavigation() {
// get the current vertical position of the scroll bar
var scrollPosition = $(window).scrollTop();
// iterate the sections
$sections.each(function() {
var currentSection = $(this);
// get the position of the section
var sectionTop = currentSection.offset().top;
// if the user has scrolled over the top of the section
if (scrollPosition >= sectionTop) {
// get the section id
var id = currentSection.attr('id');
// get the corresponding navigation link
var $navigationLink = sectionIdTonavigationLink[id];
// if the link is not active
if (!$navigationLink.hasClass('active')) {
// remove .active class from all the links
$navigationLinks.removeClass('active');
// add .active class to the current link
$navigationLink.addClass('active');
}
// we have found our section, so we return false to exit the each loop
return false;
}
});
}
$(window).scroll( throttle(highlightNavigation,100) );
// if you don't want to throttle the function use this instead:
// $(window).scroll( highlightNavigation );
#navigation {
position: fixed;
}
#sections {
position: absolute;
left: 150px;
}
.section {
height: 200px;
margin: 10px;
padding: 10px;
border: 1px dashed black;
}
#section5 {
height: 1000px;
}
.active {
background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="navigation">
<ul>
<li>Section 1</li>
<li>Section 2</li>
<li>Section 3</li>
<li>Section 4</li>
<li>Section 5</li>
</ul>
</div>
<div id="sections">
<div id="section1" class="section">
I'm section 1
</div>
<div id="section2" class="section">
I'm section 2
</div>
<div id="section3" class="section">
I'm section 3
</div>
<div id="section4" class="section">
I'm section 4
</div>
<div id="section5" class="section">
I'm section 5
</div>
</div>
And in case you are interested, this fiddle tests the different improvements we have talked about.
Happy coding!
I've taken David's excellent code and removed all jQuery dependencies from it, in case anyone's interested:
// cache the navigation links
var $navigationLinks = document.querySelectorAll('nav > ul > li > a');
// cache (in reversed order) the sections
var $sections = document.getElementsByTagName('section');
// map each section id to their corresponding navigation link
var sectionIdTonavigationLink = {};
for (var i = $sections.length-1; i >= 0; i--) {
var id = $sections[i].id;
sectionIdTonavigationLink[id] = document.querySelectorAll('nav > ul > li > a[href=\\#' + id + ']') || null;
}
// throttle function, enforces a minimum time interval
function throttle(fn, interval) {
var lastCall, timeoutId;
return function () {
var now = new Date().getTime();
if (lastCall && now < (lastCall + interval) ) {
// if we are inside the interval we wait
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
lastCall = now;
fn.call();
}, interval - (now - lastCall) );
} else {
// otherwise, we directly call the function
lastCall = now;
fn.call();
}
};
}
function getOffset( el ) {
var _x = 0;
var _y = 0;
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
_x += el.offsetLeft - el.scrollLeft;
_y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
return { top: _y, left: _x };
}
function highlightNavigation() {
// get the current vertical position of the scroll bar
var scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
// iterate the sections
for (var i = $sections.length-1; i >= 0; i--) {
var currentSection = $sections[i];
// get the position of the section
var sectionTop = getOffset(currentSection).top;
// if the user has scrolled over the top of the section
if (scrollPosition >= sectionTop - 250) {
// get the section id
var id = currentSection.id;
// get the corresponding navigation link
var $navigationLink = sectionIdTonavigationLink[id];
// if the link is not active
if (typeof $navigationLink[0] !== 'undefined') {
if (!$navigationLink[0].classList.contains('active')) {
// remove .active class from all the links
for (i = 0; i < $navigationLinks.length; i++) {
$navigationLinks[i].className = $navigationLinks[i].className.replace(/ active/, '');
}
// add .active class to the current link
$navigationLink[0].className += (' active');
}
} else {
// remove .active class from all the links
for (i = 0; i < $navigationLinks.length; i++) {
$navigationLinks[i].className = $navigationLinks[i].className.replace(/ active/, '');
}
}
// we have found our section, so we return false to exit the each loop
return false;
}
}
}
window.addEventListener('scroll',throttle(highlightNavigation,150));
For anyone trying to use this solution more recently, I hit a snag trying to get it to work. You may need to escape the href like so:
$('#navigation > ul > li > a[href=\\#' + id + ']');
And now my browser doesn't throw an error on that piece.
function navHighlight() {
var scrollTop = $(document).scrollTop();
$("section").each(function () {
var xPos = $(this).position();
var sectionPos = xPos.top;
var sectionHeight = $(this).height();
var overall = scrollTop + sectionHeight;
if ((scrollTop + 20) >= sectionPos && scrollTop < overall) {
$(this).addClass("SectionActive");
$(this).prevAll().removeClass("SectionActive");
}
else if (scrollTop <= overall) {
$(this).removeClass("SectionActive");
}
var xIndex = $(".SectionActive").index();
var accIndex = xIndex + 1;
$("nav li:nth-child(" + accIndex + ")").addClass("navActivePage").siblings().removeClass("navActivePage");
});
}
.navActivePage {
color: #fdc166;
}
$(document).scroll(function () {
navHighlight();
});
In this line:
$('#navigation > ul > li > a').attr('href', id).addClass('active');
You are actually setting the href attribute of every $('#navigation > ul > li > a') element, and then adding the active class also to all of them. May be what you need to do is something like:
$('#navigation > ul > li > a[href=#' + id + ']')
And select only the a which href match the id. Make sense?

Fill parent div based on child height/width

I'm trying to create a expanding menu that appends the parent div with multiple 400px smaller divs until the full parent div is filled.
In the for loop i'm checking if .background is the same height as .main-nav, if it isn't then continue to add the .slice divs.
The issue I'm having is that once .background is the same height as .main-nav it stops adding the .slices divs which means the full width isn't covered.
P.S First 'real'JS project; sorry if my code is messy.
The following JSFiddle will make more sense:
http://jsfiddle.net/8ryAD/13/
JS:
(function () {
'use strict';
var s = document.getElementsByClassName('slice');
var m = document.getElementById('btn-nav');
var b = document.getElementsByClassName('background')[0];
var a = document.getElementById('main-nav');
var w = document.documentElement.clientHeight;
m.addEventListener('click', function() {
m.classList.add('open');
for(var i = b.clientHeight; i < a.clientHeight; i++) {
var c = document.getElementsByClassName('slice')[0];
var d = c.cloneNode(true);
if(b.clientHeight == a.clientHeight) {
break;
} else {
c.parentNode.appendChild(d);
}
}
for(var i = 0; i < s.length; i++){
s[i].style.opacity = 1;
}
}, false);
}());
HTML
<div id="master">
<a id="btn-nav">
<span></span>
<span>Menu</span>
<span></span>
</a>
<nav id="main-nav">
<div class="background" >
<div class="slice"></div>
</div>
</nav>
</div><!-- end master -->
The main problem in your code is the for loop. For that particular case you should use a while loop. For loop are used when you know precisely the number of iterations you are going to do.
I reworked the loop this way :
// Loop while we don't cover client surface
while(dsWidth < a.clientWidth*(a.clientHeight / c.clientHeight)) {
var d = c.cloneNode(true);
c.parentNode.appendChild(d);
dsWidth = dsWidth + d.clientWidth;
}
Here is a fiddle doing the job. It is not clear if you want to add "slices" vertically or horizontally. I chose horizontally but you should have no difficulties .

Categories

Resources