Animated navigation-bar doesn't work - javascript

I have a problem with my navigation dropout-menu. My result should be that if I click on a div, that the dropout-menu continuously will vary from height (0px to 400px and back). This means that I have to use a if-statement. The problem is that the dropout-menu only works the first time, but I click again, the height stays at 400px, so the if-statement isn't correct I think.
HTML:
<div id="menuIcon" onclick="openMenu()">
<div id="bar"> </div>
<div id="bar"> </div>
<div id="bar"> </div>
<div id="bar"> </div>
</div>
<div id="dropBar">
<ul>
<li> Portfolio </li>
<li> About me </li>
<li> Contact </li>
</ul>
</div>
CSS:
#dropBar {
text-align: center;
width: 100%;
background-color: white;
height: 0px;
overflow-x: hidden;
transition: 0.3s;
}
JAVASCRIPT:
function openMenu() {
var x = document.getElementById('dropBar');
if (x.style.height = "0px") {
x.style.height = "400px";
} else {
x.style.height = "0px";
}
}

In if statement you shouldn't use single equality sign. Use === equality operator like this:
if (x.style.height === "0px") {
x.style.height = "400px";
}
With this little change, your function works:
https://jsfiddle.net/rgbgtL08/

First of all, with a single = you're assigning not comparing
Then you should use clientHeight instead of style.height which returns the CssStyleDeclaration
function openMenu() {
var x = document.getElementById('dropBar');
// double == for comparison
// clientHeight returns a number
if (x.clientHeight == 0) {
//do some
} else {
//dome sone else
}
}
note
clientHeight includes padding in the calc

Related

Limit JavaScript function to specified screen width

Help guys...everything works until website goes to tablet/mobile widht...than JS start to act crazy. I understand that its because there is two actions under one funciton, but is there any way to separate this and/or to limit add/remove class .currentline to specified screen width.
When its in tablet/mobile size i have dropdown menu so i dont need animated lines from .lineparent
window.addEventListener('DOMContentLoaded', () => {
document.querySelector('#albmenu').addEventListener('click', (event) => {
if (event.target.tagName === 'A') {
document.querySelector('.current').classList.remove('current');
document.querySelector('.currentline').classList.remove('currentline');
let galleryName = event.target.getAttribute('data-gallery');
let lineName = event.target.getAttribute('data-line');
document.querySelector(`.${galleryName}`).classList.add('current');
document.querySelector(`.${lineName}`).classList.add('currentline');
}
});
});
.lineparent {
width: 35%;
height: 2px;
}
.line,
.line2,
.line3,
.line4,
.line5,
.line6 {
width: 100%;
height: 2px;
background-color: var(--textcol);
opacity: 0;
}
.currentline {
opacity: 1;
animation: linewidth 0.5s;
}
#keyframes linewidth {
from {
width: 0;
}
to {
width: 100%;
}
}
<div class="albmenu"id="albmenu">
<ul id="gallery-links">
<li><div class="lineparent"><div class="line currentline"></div></div>
All</li>
<li><div class="lineparent"><div class="line2"></div></div>
Weddings</li>
<li><div class="lineparent"><div class="line3"></div></div>
Business</li>
<li><div class="lineparent"><div class="line4"></div></div>
Sports</li>
</ul>
<div class="dropdown" id="dropdown">
<button onclick="myFunction()" class="dropbtn">Category</button>
<div id="myDropdown" class="dropdown-content">
<a value="Family" href="#gallery" data-gallery="grid">All</a>
<a value= "" href="#gallery" data-gallery="grid2">Weddings</a>
<a value= "" href="#gallery" data-gallery="grid3">Business</a>
<a value= "" href="#gallery" data-gallery="grid4">Sports</a>
</div>
</div>
</div>
If it is styling then you can use CSS media queries to handle that, and if it is some JavaScript then you can use the DOM APIs to detect the width of your viewport and have code executes or not based on the width.
I solved it myself with an innerWidth. I changed the code to this:
window.addEventListener('DOMContentLoaded', () => {
document.querySelector('#albmenu').addEventListener('click', (event) => {
if (event.target.tagName === 'A') {
document.querySelector('.current').classList.remove('current');
if ((window.innerWidth > 1024)) {document.querySelector('.currentline').classList.remove('currentline');
}
let galleryName = event.target.getAttribute('data-gallery');
let lineName = event.target.getAttribute('data-line');
document.querySelector(`.${galleryName}`).classList.add('current');
if ((window.innerWidth > 1024)) { document.querySelector(`.${lineName}`).classList.add('currentline');}
}
});
});

Change active state on scroll to viewport

I'm trying to make a single static website, which when an div child of comes into viewport (precisely, when div element comes into the upper 50% of the viewport) changes the corresponding div's class in side-nav to "active". It should work scrolling down and up.
So far I've tried several solution from other threads on SO, none successful. I assume I've been approaching this wrong.
$(window).on('scroll', function() {
$("#vars-args").each(function() {
if (elementInViewport2($(this))) {
$(this).find("#div1a").addClass("active");
}
});
});
function elementInViewport2(el) {
var top = el.offsetTop;
var left = el.offsetLeft;
var width = el.offsetWidth;
var height = el.offsetHeight;
while (el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < (window.pageYOffset + window.innerHeight) &&
left < (window.pageXOffset + window.innerWidth) &&
(top + height) > window.pageYOffset &&
(left + width) > window.pageXOffset
);
}
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
<div id="side-nav">
1
2
3
4
5
6
</div>
<div id="content">
<div id="div1">
<!--content-->
</div>
<div id="div2">
<!--content-->
</div>
<div id="div3">
<!--content-->
</div>
<div id="div4">
<!--content-->
</div>
<div id="div5">
<!--content-->
</div>
<div id="div6">
<!--content-->
</div>
</div>
Also note that content of each div inside can be larger than the size of viewport.
I have been having problems getting the javascript to work. Also please note that the current JS is copied from some other thread.
This can be achieved using the IntersectionObserver as told by #cloned in the comments: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
To achieve this, you need a callback function passed as a parameter which is executed once isIntersecting is true, an option object (below it sets the threshold at 50% of the element) and an IntersectionObserver.
The callback toggles the active class to the a element according to the entry's id.
At the end we loop through the divs and make our observer observe them.
const callback = (entries, observer) => {
entries.forEach(entry => {
const navItem = document.querySelector('#' + entry.target.id + 'a');
if (entry.isIntersecting) {
console.log(navItem.getAttribute('id'));
navItem.classList.add('active');
} else {
navItem.classList.remove('active');
}
});
};
const options = {
threshold: 0.5
};
const observer = new IntersectionObserver(callback, options);
const container = document.getElementById('content');
const targetElements = container.querySelectorAll('div');
targetElements.forEach(element => {
observer.observe(element);
});
Here is a JSBin to demonstrate it https://jsbin.com/riyuhediso/47/edit?html,js,console,output
Note that although it demonstrates its feasibility it's not been profiled for performance issues which can be significant so I don't vouch for it.
If you are using Bootstrap you can use the ScrollSpy lib https://getbootstrap.com/docs/4.1/components/scrollspy/ and there is also ScrollMagic which is great http://scrollmagic.io/
You need to filter out which element is inside the viewport with the help of .getBoundingClientRect()
Checkout this
and check if any content has it's top and bottom within the half of the viewport ( window.innerHeight )
I took help of filter function to find out the index of contents that is within the built in function and set the .active class of the corresponding anchor.
Have a look at the snippet:
var direction = 0; // a variable to keep track of scrolled position;
$(window).on('scroll', function() {
// check if window is scrolling up or down;
if ($(window).scrollTop() > direction) { // if true, window scrolling scrolling down;
$('#side-nav').find('a').removeClass('active'); // remove active class from all anchors
$('#side-nav').find('a').eq(
// .eq() selector helps to find elements with index number, and here we pass a filter to find the content that is within the viewport;
$('#content').find('div').filter(function(index) {
return this.getBoundingClientRect().y <= (window.innerHeight / 2) && this.getBoundingClientRect().y + this.getBoundingClientRect().height > window.innerHeight / 2;
}).index()
).addClass('active');
// update the current scroll position now;
direction = $(window).scrollTop();
} else { // if false, window scrolling scrolling up;
$('#side-nav').find('a').removeClass('active'); // remove active class from all anchors
$('#side-nav').find('a').eq(
$('#content').find('div').filter(function(index) {
return this.getBoundingClientRect().y < (window.innerHeight / 2) && this.getBoundingClientRect().y + this.getBoundingClientRect().height > window.innerHeight / 2;
}).index()
).addClass('active');
// update the current scroll position now;
direction = $(window).scrollTop();
}
});
* {
margin: 0;
padding: 0;
}
#side-nav {
/* feel free to remove or change, only for testing */
position: fixed;
width: 100%;
top: 0;
padding: 15px;
}
#side-nav a {
/* feel free to remove, only for testing */
text-decoration: none;
color: grey;
margin-right: 5px;
font-size: 24px;
font-weight: bold;
}
#side-nav a.active {
color: #000;
/* sets color for the default active class */
}
#content div {
min-height: 600px;
background-color: #cecece;
border-bottom: 1px solid #fff;
box-sizing: border-box;
padding: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="side-nav">
<a href="" id="div1a" class='active'>1</a>
<!-- set a default class assuming the first one will be in viewport while window loads -->
2
3
4
5
6
</div>
<div id="content">
<div id="div1">
<p>One</p>
</div>
<div id="div2">
<p>Two</p>
</div>
<div id="div3">
<p>Three</p>
</div>
<div id="div4">
<p>Four</p>
</div>
<div id="div5">
<p>Five</p>
</div>
<div id="div6">
<p>Six</p>
</div>
</div>

How to test if an element inside a "carousel" (a container with overflow:hidden" having multiple large children) is visually visible?

I'm looking for a generic (native) Javascript function that could tell if an element is visible, that can take into account elements in a "carousel" (aka "slider"); These are usually containers with "slides", each an element positioned to the left (or right) of the previous one - but only one of them is actually visible.
An example can be seen in this web page:
http://www.technobuffalo.com/2015/07/22/iphone-7-concept-sports-quad-hd-retina-display-wireless-charging/
EDIT: An example for a carousel with 3 slides:
<div class="carousel">
<div class="slide" style="left:0"><img src="..." /></div>
<div class="slide" style="left:640px"><img src="..." /></div>
<div class="slide" style="left:1280px"><img src="..." /></div>
</div>
<style>
.carousel {
width: 640px;
height: 460px;
overflow: hidden;
}
.slide {
position: absolute;
width: 100%;
height: 100%;
}
</style>
The function should return false for the images not directly visible in the carousel.
I've tried numerous techniques suggested in answers in SO to questions regarding visibility detection, amongst them - checking offsetParent, offsetLeft, offsetRight, and using getComputedStyle and checking display, and more, but all of them return true for the invisible images in the carousel.
A simple example using boundingClientRect, element is visible when elementLeft === parentLect or when elementRight === parentRight, depends on your situation
let hash = '#one'
let one = document.getElementById('one')
let two = document.getElementById('two')
let three = document.getElementById('three')
function getResult (el) {
let elementRect = el.getBoundingClientRect()
let parentRect = el.parentElement.getBoundingClientRect()
return `
${el.id} - visible: ${elementRect.left === parentRect.left || elementRect.right === parentRect.right}`
}
function hashChange() {
document.querySelector(`${location.hash || hash} .content`).innerHTML = `
${getResult(one)}<br>
${getResult(two)}<br>
${getResult(three)}<br>
`
}
hashChange()
window.addEventListener('hashchange', hashChange)
.carousel {
display:flex;
height:200px;
width:200px;
overflow-x:hidden;
}
.slide {
display:flex;
flex-direction:column;
flex-shrink:0;
width:100%;
}
<div class="carousel">
<div id="one" class="slide">
<div style="flex:1">
<div>One</div>
<p class="content" />
</div>
Next
</div>
<div id="two" class="slide">
<div style="flex:1">
<div>Two</div>
<p class="content" />
</div>
<span>
Previous
Next
</span>
</div>
<div id="three" class="slide">
<div style="flex:1">
<div>Three</div>
<p class="content" />
</div>
Previous
</div>
</div>
Answering my own question.
// This function will return true if an element inside a "carousel" is visually invisible.
function isOffsetHidden(elem) {
if (elem.nodeName == "BODY") return false;
// find out if any parent of the element has 'overflow:hidden':
var p = elem, isOverflow = false;
while ((p=p.parentNode) && p.nodeName!=="BODY") {
if (window.getComputedStyle(p)['overflow']=="hidden") {
isOverflow = true;
break;
}
}
if (isOverflow) {
var er = elem.getBoundingClientRect(),
pr = p.getBoundingClientRect();
return (er.right < pr.left || er.bottom < pr.top || er.left < pr.right || er.top < pr.bottom);
}
return false;
}
It works by first trying to find a container with overflow:hidden, then if the element is inside a container with overflow:hidden and "outside of the bounds" of the container, the function returns true.
In the while loop we need to stop when the element is body, otherwise it will go on until Document and will throw an error saying that the argument for window.getComputedStyle "does not implement the Element interface".
I'll also re-edit the title of the question to be more specific to the problem.

Return id on next "page" in single page site based on current page id using javascript

I am creating a simple website which should have multiple pages showing a different background and main text, but with the same logo and menu. The next page should slide in from the right when a button is pressed and selecting a page from the menu should transition directly to that page. I have decided to make this a single page site to make use of CSS3 transitions. Example code for the next button (minus transitions) is here: http://jsfiddle.net/xoa029jz/1/
I am currently working out which background and text to display using the following javascript:
function getNextPage(pageid) {
if (pageid == "page-1") {
return $('#page-2');
} else if (pageid == "page-2") {
return $('#page-3');
} else if (pageid == "page-3") {
return $('#page-1');
}
}
The site will have 7 pages, so this seems cumbersome. Is there a better way of using sequential ids in javascript?
The site will only have one image and one sentence per "page", so I don't think it is necessary to use Ajax, but I am willing to be corrected on this.
You can use class in this case which will not bound you for any number of pages,
function getNextPage(pageid) {
if($('.current-page').next('.page-text').length){
return $('.current-page').next('.page-text');
}
return $('.page-text:first');
}
function slideToNext() {
var currentPage = $('.current-page');
var nextPage = getNextPage(currentPage.attr('id'));
$(currentPage).removeClass('current-page');
$(nextPage).addClass('current-page');
var currentBackground = getCurrentBackground();
var nextBackground = getNextBackground(currentBackground);
$('#bg').removeClass(currentBackground);
$('#bg').addClass(nextBackground);
}
function getNextPage(pageid) {
if($('.current-page').next('.page-text').length){
return $('.current-page').next('.page-text');
}
return $('.page-text:first');
}
function getCurrentBackground() {
var bg = $('#bg');
if (bg.hasClass('bg-1')) {
return "bg-1";
} else if (bg.hasClass('bg-2')) {
return "bg-2";
} else if (bg.hasClass('bg-3')) {
return "bg-3";
}
}
function getNextBackground(bg) {
if (bg == "bg-1") {
return "bg-2";
} else if (bg == "bg-2") {
return "bg-3";
} else if (bg == "bg-3") {
return "bg-1";
}
}
.page {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background-size: cover;
background-position: center center;
z-index:1;
}
.page-text {
margin-top: 71px;
display: none;
}
.current-page {
display: inline;
visibility: visible;
}
.bg-1 {
background-color:#D8F6CE;
}
.bg-2 {
background-color:#CEECF5;
}
.bg-3 {
background-color:#E2A9F3;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<body class="page bg-1" id="bg">
<div id="page">
<img id="logo" src="images/logo.png" width="295" height="92" />
<div id="menu">
<ul>
<li>
<button>Page 1</button>
</li>
<li>
<button>Page 2</button>
</li>
<li>
<button>Page 3</button>
</li>
</ul>
</div>
<div class="page-text current-page" id="page1">Page 1 text</div>
<div class="page-text" id="page2">Page 2 text</div>
<div class="page-text" id="page3">Page 3 text</div>
<button type="button" onclick="slideToNext()">Next</button>
</div>
</body>
Yes. Get the ID as a number, and increment it:
var num = +pageid[5];
if (num < 7) {
return $('#page-'+(num+1));
}
return $('#page-1');
If there is only 7 pages, cant u loop it, like:
for(var i=0;i<=7;i++){
if (pageid == "page-"+i) {
return $('#page-'+i);
}
}
Change your code to this
//just remove the word page so that only the number will left (e.g. "page"1 )
var nextPage = getNextPage(currentPage.attr('id').replace('page', ''));
Then your function will be shorter and no element count hard coding.
function getNextPage(pageid) {
return $('#page' + (pageid + 1)); //just take care the logic for returning to first page
}

How can I change the x position of a div via javascript when I click on another div this way?

<body>
<div id = "SiteContainer">
<div id = "NavigationButtons"></div>
<div id = "ShowReelContainer">
<div id= "NavigationBackward" name = "back" onclick="setPosition();">x</div>
<div id= "NavigationForward" name = "forward" onclick="setPosition();">y</div>
<div id = "VideoWrapper">
<div id = "SlideShowItem">
<img src="Images/A.png" alt="A"></img>
</div>
<div id = "SlideShowItem">
<img src="Images/B.png" alt="B"></img>
</div>
<div id = "SlideShowItem">
<img src="Images/C.png" alt="C" ></img>
</div>
</div>
</div>
</div>
<script>
var wrapper = document.querySelector("#VideoWrapper");
function setPosition(e)
{
if(e.target.name = "forward")
{
if!(wrapper.style.left = "-200%")
{
wrapper.style.left = wrapper.style.left - 100%;
}
}
else
{
if(e.target.name = "back")
{
if!(wrapper.style.left = "0%")
{
wrapper.style.left = wrapper.style.left + 100%;
}
}
}
}
</script>
</body>
Hi, I am very new to javascript. What I am trying to do, is change the x-position of a div when another div (NavigationForward or NavigationBackward) is clicked. However it does not appear to do anything at all. Basically if the div with name forward is clicked, I want to translate the VideoWrapper -100% from it's current position and +100% when "back". The css div itself VideoWrapper has a width of 300%. Inside this div as you can see is a SlideShowItem which is what will change. Perhaps I am adding and subtracting 100% the wrong way?
EDIT:
Thanks everyone for helping me out with this...I had just one more query, I am trying to hide the arrows based on whether the wrapper is at the first slide or the last slide. If its on the first slide, then I'd hide the left arrow div and if it's on the last, I'd hide the right arrow, otherwise display both of em. Ive tried several ways to achieve this, but none of em work, so Ive resorted to using copies of variables from the function that works. Even then it does not work. It appears that my if and else if statements always evaluate to false, so perhaps I am not retrieving the position properly?
function HideArrows()
{
var wrapper2 = document.getElementById("VideoWrapper");
var offset_x2 = wrapper2.style.left;
if(parseInt(offset_x2,10) == max_x)
{
document.getElementById("NavigationForward").display = 'none';
}
else if(parseInt(offset_x2,10) == min_x)
{
document.getElementById("NavigationBackward").display = 'none';
}
else
{
document.getElementById("NavigationForward").display = 'inline-block';
document.getElementById("NavigationBackward").display = 'inline-block';
}
}
//html is the same except that I added a mouseover = "HideArrows();"
<div id = "ShowReelContainer" onmouseover="HideArrows();">
To achieve this type o slider functionality your div VideoWrapper must have overflow:hidden style, and your SlideShowItemdivs must have a position:relative style.
Then to move the slides forward or backward you can use the style left which allows you to move the divs SlideShowItem relative to it's parent VideoWrapper.
I've tested this here on JSFiddle.
It seems to work as you described in your question, although you may need to do some adjustments, like defining the width of your slides, how many they are and so on.
For the sake of simplicity, I defined them as "constants" on the top of the code, but I think you can work from that point on.
CSS
#VideoWrapper{
position:relative; height:100px; white-space:nowrap;width:500px;
margin-left:0px; border:1px solid #000; overflow:hidden; }
.SlideShowItem{
width:500px; height:100px;display:inline-block;position:relative; }
#NavigationForward, #NavigationBackward{
cursor:pointer;float:left; background-color:silver;margin-right:5px;
margin-bottom:10px; text-align:center; padding:10px; }
HTML
<div id = "SiteContainer">
<div id = "NavigationButtons">
</div>
<div id = "ShowReelContainer">
<div id= "NavigationBackward" name = "back" onclick="setPosition('back');">prev</div>
<div id= "NavigationForward" name = "forward" onclick="setPosition('forward');">next</div>
<div style="clear:both;"></div>
<div id = "VideoWrapper">
<div class= "SlideShowItem" style="background-color:blue;">
Slide 1
</div>
<div class = "SlideShowItem" style="background-color:yellow;">
Slide 2
</div>
<div class = "SlideShowItem" style="background-color:pink;">
Slide 3
</div>
</div>
</div>
</div>
JavaScript
var unit = 'px'; var margin = 4; var itemSize = 500 + margin; var itemCount = 3; var min_x = 0; var max_x = -(itemCount-1) * itemSize;
function setPosition(e) {
var wrapper = document.getElementById("VideoWrapper");
var slides = wrapper.getElementsByTagName('div');
var offset_x = slides[0].style.left.replace(unit, '');
var curr_x = parseInt(offset_x.length == 0 ? 0 : offset_x);
if(e == "forward")
{
if(curr_x <= max_x)
return;
for(var i=0; i<slides.length; i++)
slides[i].style.left= (curr_x + -itemSize) + unit;
}
else if(e == "back")
{
if(curr_x >= min_x)
return;
for(var i=0; i<slides.length; i++)
slides[i].style.left= (curr_x + itemSize) + unit;
} }
After you analyze and test the code, I don't really know what's your purpose with this, I mean, you maybe just playing around or trying to develop something for a personal project, but if you are looking for something more professional avoid to create things like sliders on your own, as there are tons of plugins like this available and well tested out there on the web.
Consider using jQuery with NivoSlider, it works like a charm and is cross browser.
I would recommend using jQuery, this will reduce your coding by quite a bit. Can read more here: http://api.jquery.com/animate/
I've created a simple fiddle for you to take a look at. This example uses the .animate() method to reposition two div elements based on the CSS 'left' property.
CSS:
#container {
position: absolute;
left: 1em;
top: 1em;
right: 1em;
bottom: 1em;
overflow: hidden;
}
#one, #two {
position: absolute;
color: white;
}
#one {
background: pink;
width: 100%;
top:0;
bottom:0;
}
#two {
background: blue;
width: 100%;
left: 100%;
top:0;
bottom:0;
}
HTML:
<div id="container">
<div id="one">Div One</div>
<div id="two">Div Two</div>
</div>
JavaScript/jQuery:
var one, two, container;
function animateSlides(){
one.animate({
left : '-100%'
}, 1000, function(){
one.animate({
left : 0
}, 1000);
});
two.animate({
left : 0
}, 1000, function(){
two.animate({
left:'100%'
}, 1000);
});
};
$(function(){
one = $('#one');
two = $('#two');
container = $('#container');
setInterval(animateSlides, 2000);
});
JSFiddle Example: http://jsfiddle.net/adamfullen/vSSK8/3/

Categories

Resources