Infinite menu animation - javascript

I'm coding an infinite vertical menu but I'm having trouble with the animation. I can't manage to make (without Jquery please) the scroll smoother.
I want to animate the addition and deletion of the 'noheight' class. The aim is that the appearance and disappearance of 'li' should not be abrupt, but should have a translational effect. I'd like to add a duration to the addition/deletion of the class but I don't know how to do it in classic js.
I tried to animate the noheight class with queries on the css side but it doesn't work. I would have to animate the addition of the class on the JS side?
A repeat of my code when clicking on another item in the list (On the functional level, it works perfectly, but on the animation level, it's very austere) :
function menuClick(elementClicked) {
let indexActive = searchActiveElement();
let gap = getIndex(elementClicked) < indexActive ? indexActive - getIndex(elementClicked) : getIndex(elementClicked) - indexActive;
for(let i = 0; i < gap; i++) {
indexActive = searchActiveElement();
if(getIndex(elementClicked) !== indexActive) {
ul.children[indexActive].classList.remove('active');
if(getIndex(elementClicked) < indexActive) {
// If the user clicks above the active element (the menu will go down)
ul.children[indexActive].previousElementSibling.classList.add('active');
ul.lastElementChild.remove();
ul.lastElementChild.classList.add('noHeight');
ul.firstElementChild.classList.remove('noHeight');
let newElement = document.createElement('li');
newElement.classList.add('noHeight');
newElement.innerHTML = ul.lastElementChild.previousElementSibling.innerHTML;
ul.firstElementChild.before(newElement);
ul.firstElementChild.addEventListener("click", function() {
menuClick(this);
});
}
The menu can be tested here : http://jsfiddle.net/3u8c2xb5/
Thanks in advance for your answers :D

Related

Remove class based on elements but not others

I have these sections on this side scrolling site. And want to add a class which will change styling depending if you're on a certain section.
I'm working on this function. The top is what determines the section of the side scroller you are viewing.
The let variables and below is where it stops working. I'm trying to have it so if a nonHome ID section is clicked, for example "slide-1", then add the class 'nav-visibilty'. If they are a match "slide-2" and "slide-2" then remove said class. Am I close?
https://codepen.io/mikayp-the-styleful/pen/NWPxoXR?editors=1111
setTimeout(function(){
for (i=0; i < nonHome.length; i++ ){
if (nonHome[i].id != nonHomeID){
nonHome[i].classList.add("nav-visibility");
console.log('add')
} else{
nonHomeID.classList.remove("nav-visibility");
console.log('rem')
}
}
I am still not totally clear on the behavior that you want, but there are two errors in the code that can be fixed:
It seems like you are always using 'slide-2' instead of the slideId in your event handler.
As mentioned in a comment, nonHomeID is being used incorrectly in your comparison (it is either a string or an element, but you are using it as if it was a string in the if condition, and as the element in the else branch.) Here I have kept it as an element and renamed it for clarity.
Fixing these errors results in code that applies the nav-visibility class to all slides except the one selected by the button. Is that the desired behavior?
let nonHome = document.querySelectorAll(".slide-container section");
let nonHomeSelected = document.getElementById(slideId);
var i;
setTimeout(function() {
for (i = 0; i < nonHome.length; i++) {
if (nonHome[i] != nonHomeSelected) {
nonHome[i].classList.add("nav-visibility");
console.log("add");
} else {
nonHome[i].classList.remove("nav-visibility");
console.log("rem");
}
}
}, 1000);
Edit to add: If the goal is to add nav-visibility to all only the specific slideId, you should not be adding in a loop, i.e. you need to pull your check for whether the slide is Home outside the loop. There are conceptually two steps here: remove the class from all elements that are no longer to have it, then add the class to the element that needs it.
let slideToAddVisibilityTo = document.getElementById(slideId)
let homeSlide = document.getElementById('slide-2')
let allSlides = document.querySelectorAll(".slide-container section")
for (let i = 0; i < allSlides.length; ++i)
allSlides[i].classList.remove('nav-visiblity')
if (slideToAddVisibilityTo != homeSlide)
slideToAddVisibilityTo.classList.add('nav-visibility')
Just hide them all, then show the clicked one:
function showSection(id) {
var sections = document.getElementsByTagName("section");
for(var i=0; i<sections.length; i++) sections[i].classList.remove("nav-visibility");
var current = document.getElementById(id);
current.classList.add("nav-visibility");
}
Example: showSection("foo") will remove nav-visibility from all sections, then add it to the section with id foo.

Maintain drop down menu positions between page loads

I have a drop down menu list of links fixed on the left side of a web page I'm in the process of building. As of now, everything is working fine except I would like to have one more functionality; maintaining the position of my drop down menu after clicking links. The links will have the exact same drop down menu except that the title and content of the new page will change. Here are the core pieces I currently have in terms of code. If you click one of the headers it will drop down with the links available. Note, only one drop down is allowed to be open. If I had .html files for the links and clicked one, the page will "refresh" and the drop down menu will be shown as closed again losing its position before the link was clicked. I don't know how to modify the code to do what I want so I was hoping someone can help me. I would like to stick with just HTML, JS, and CSS if that's possible. Thanks, Here's the JS straight from the link
// Gotta give credit where credit is due
// https://stackoverflow.com/questions/45911424/have-only-one-drop-down-panel-open-on-click-using-html-js-and-css
var acc = document.getElementsByClassName("drop-down");
var i = 0;
for (i = 0; i < acc.length; i++) {
acc[i].onclick = function () {
var panel = this.nextElementSibling;
var maxHeight = panel.style.maxHeight;
//Collapse all divs first
var divs = document.getElementsByClassName("drop-down-panel");
for (i = 0; i < divs.length; i++) {
divs[i].style.maxHeight = null;
divs[i].previousElementSibling.classList.remove("active");
}
this.classList.toggle("active")
if (maxHeight) {
panel.style.maxHeight = null;
this.classList.remove("active");
} else {
panel.style.maxHeight = panel.scrollHeight + "px";
}
};
}

Javascript - Trying to create a grid, but can only get rows. I am not sure what to do next

so I am trying to create a etch-a-sketch and I need to generate multiple divs (i.e. little squares) based on the users input. So for instance the option is how many pixels do you want the grid size to be from 1 - 50 and based on the answer (say 30) I need to make a grid with that many squares on each side. The issue is that I can only get that many squares as a row at the top and not as a grid. Linked is my code and also shown is my javascript code which is the one giving me issues. The HTML/CSS are mostly just styling, but they are shown in the link. I am not sure what I am doing wrong because I am using someone else's code as reference and it seems to work for them, but not for me.
https://codepen.io/faar23/pen/qjZgQY
$(document).ready(function(){
/*for (var i = 0; i < 24; i ++) {
$('#workspace').append('<div class = "row"></div>');
$('.row').height(27);
};*/
/*for now i am going to leave the above code commented out but what it
does is instantly creates 24 boxes on the workspace
and without this, the space is empty. I am commenting it out to see if the
.height function will wor down below...doesnt seem to*/
/*the above loop generates 24 divs dynamically without having to copy paste
in html. the .height sets the height of the divs.
the width is set in the css under myId. this was done using this tutorial
https://www.youtube.com/watch?v=dtgx_twX-a4 + inspecting
the elements on this project https://beachfern.github.io/sketchpad/*/
$('#mainButton').mouseenter(function(){
$('#mainButton').addClass('highlight');
});
$('#mainButton').mouseleave(function(){
$('#mainButton').removeClass('highlight');
});
/*the above code makes the main start button change color when the mouse
enters it*/
$('.buttonhide').hide();
$('#mainButton').click(function(){
$('.buttonhide').show();
});
/*the above code hides the rest of the buttons until the main button is
clicked, then all the buttons show themselves*/
$('#mainButton').on('click', function(){
var workspaceSize = prompt("How many boxes should make up the canvas? (1
to 50)", "24");
/*i am going to use this user's way and see if that works better for me
https://stackoverflow.com/questions/25518243/creating-div-grid-with-
jquery-for-loop*/
if(workspaceSize > 0 && workspaceSize <= 50){
$('#workspace').empty();
for (var i = 0; i < workspaceSize; i++){
$('#workspace').append('<div class = "row"></div>');
};
for (var x = 0; x < workspaceSize; x++){
$('.row').append('<div class = "column"></div>');
};
$('.row').height(700 / workspaceSize);
$('.column').height(700 / workspaceSize);
$('.column').width(700 / workspaceSize);
}; /*very imp closing bracket. shit doesnt work without it*/
});
});
You need to nest the two for loops, so that for each row it creates the columns.
This might work, or lead you in the right direction:
for(var i = 0; i < workspaceSize; i++){
$('#workspace').append('<div class="row" id=row'+i+'></div>');
for(var j = 0; j < workspaceSize; j++){
$('#row'+i).append('<div class="column"></div>')
}
}

Toggle class of parent element onclick without jQuery

I'm trying to toggle the class of a parent li element when the sub ul element is active. I know how to do it in jQuery, but the goal with this project is to not rely on libraries like Bootstrap or jQuery.
I have a demo on CodePen: https://codepen.io/mikejandreau/pen/eRvOBQ
There's also a dev site using the same menu here: http://losaidos.com/dev/baseinstall/.
This is the JavaScript currently controlling the sub-menu toggles:
// Add toggles to menu items that have submenus and bind to click event
var subMenuItems = document.body.querySelectorAll('.page_item_has_children > a');
var index = 0;
for (index = 0; index < subMenuItems.length; index++) {
var dropdownArrow = document.createElement('span');
dropdownArrow.className = 'sub-nav-toggle';
dropdownArrow.innerHTML = 'More';
subMenuItems[index].parentNode.insertBefore(dropdownArrow, subMenuItems[index].nextSibling);
}
// Enables toggling all submenus individually
var subMenuToggle = document.querySelectorAll('.sub-nav-toggle');
for(var i in subMenuToggle) {
if(subMenuToggle.hasOwnProperty(i)) {
subMenuToggle[i].onclick = function() {
this.parentElement.querySelector('.children').classList.toggle("active");
this.parentElement.querySelector('.sub-nav-toggle').classList.toggle("active");
};
}
}
I tried duplicating the logic by adding this to the subMenuToggle[i].onclick function:
this.parentElement.querySelector('.page_item_has_children a').classList.toggle("active");
But no luck so far in getting it working. I get the feeling I'm close but missing something obvious.
Any pointers would be greatly appreciated - thanks in advance!
The answer was staring me in the face.
There was no need to do use .querySelector('.class-of-parent) because the target elements are already within the parent. I added this.parentElement.classList.toggle("active"); and it worked like a charm.
// Enables toggling all submenus individually
var subMenuToggle = document.querySelectorAll('.sub-nav-toggle');
for(var i in subMenuToggle) {
if(subMenuToggle.hasOwnProperty(i)) {
subMenuToggle[i].onclick = function() {
this.parentElement.querySelector('.children').classList.toggle("active");
this.parentElement.querySelector('.sub-nav-toggle').classList.toggle("active");
this.parentElement.classList.toggle("active"); // facepalm of obviousness
};
}
}

Sliding Panels, how to reproduce them?

I'm probably in deep water here, and I'm new to JavaScript and jQuery,
But I would like to attempt to create a panel system sort of like how spotify has it done.
Have a look at this picture:
On the Spotify player website, when you click on something such as an artist or song/album,
It slides in a topmost panel onto the right side of the screen, if you click something else,
A new one will appear in its place, and the previous panel will get added to what I call the panel stack on the left side of the screen.
Here is a look at Spotify:
I'd like to understand how to do this in JavaScript/jquery, does anyone have some input to share?
What I've tried: (http://jsfiddle.net/k0Lh3ama/)
I understand my attempt was pretty poor, this is why I'm here
var idx = 0;
var panels = [];
var numpanels = 0;
function panel () {
this.id = 0;
this.active = false;
this.container = {};
}
var panelmgr = new function () {
this.Create = function (lpPanel) {
//set all current panels to inactive
//and then push them left
for (var i = 0; i < panels.length; i++) {
panels[i].active = false;
panels[i].container.css("left", "10%");
}
//set the newest panel to Active and Top Most
lpPanel.container.css("z-index", 10000);
lpPanel.container.css("left", "25%");
//setup some info for the new panel
lpPanel.id = numpanels++;
lpPanel.active = true;
//add it to array
panels.push(lpPanel);
};
}
$(".box").click(function (e) {
var id = $(this).attr("id");
var selected = -1;
//find the panel we've selected and retrieve the index
for (var i = 0; i < panels.length; i++) {
if (id == panels[i].container.attr("id")) {
selected = I;
break;
}
}
//do we have a valid selected panel?
if (selected >= 0) {
//Make all panels not selected
for (var i = 0; i < panels.length; i++) {
panels[i].active = false;
panels[i].container.css("left", "10%");
}
//now make the panel we selected
//the top most panel
panels[selected].active = true;
panels[selected].container.css("z-index", 10000);
panels[selected].container.css("left", "25%");
}
});
$(document).ready(function () {
var p1 = new panel();
var p2 = new panel();
var p3 = new panel();
var p4 = new panel();
var p5 = new panel();
p1.container = $("#panel1");
p2.container = $("#panel2");
p3.container = $("#panel3");
p4.container = $("#panel4");
p5.container = $("#panel5");
panelmgr.Create(p1);
panelmgr.Create(p2);
panelmgr.Create(p3);
panelmgr.Create(p4);
panelmgr.Create(p5);
});
In short there are several issues with your original code. Below are my attempts at getting the current code set working (with suggestions for improvement at the bottom. Please see my original fiddle for working code without changing to much of your original code. I also modified the original fiddle (with sliding) to use css transitions to give a slide effect)
1) You are constantly writing over your panels because their css top value is set to zero for all. You can get around this by setting the top value for all non-active panels. (I assumed you wanted the item to dissappear from the stack, since you were moving it over to the current when it was selected)
//Increment height
var ii = 0;
for (var i = 0; i < panels.length; i++) {
if(panels[i].active == false){
panels[i].container.css("top", ii*30+"px");
ii++;
}
}
2) Your Z-index and width are causing the panel stack panels and the current panel to overlap, defaulting to whatever dom element was created last.
you can get around this by setting the z-index value to a lower value in the `//make all panels not selected`` loop
panels[i].container.css("z-index", "1");
3) You'd likely also want to hide the current panels contents when it is in the stack, otherwise your stack is going to get rather messy. You can get around this by splitting up your DOM panel's into something like:
<div id="panel5" class="box">
<div class="panelTitle">Panel 4</div>
<div class="contents">Stuff to display</div>
</div>
and adding the following code to the following loops:
//Make all panels not selected
for (var i = 0; i < panels.length; i++) {
//other code emitted for space...
//hide the contents
panels[i].container.find(".contents").css("display","none");
}
//now make the panel we selected
//the top most panel
panels[selected].active = true;
panels[selected].container.css("z-index", 2);
panels[selected].container.css("left", "25%");
panels[selected].container.css("top","0px");
//it's active, so display the contents
panels[selected].container.find(".contents").css("display","block");
4) In the event you want the panels to slide around on the clicks, I added CSS transitions - only in the second fiddle I added a transition rule to your .box selector that will put a second delay on all css changes for elements that have the class box. Please see css3 transitions for more on customizing transitions.
transition: all 1s;
-webkit-transition: all 1s;
-moz-transition: all 1s;
-o-transition: all 1s;
Suggestions for improvement/better development
To be honest I'd probably do a large rewrite. DOM manipulation gets the job done and is a fair start, but if things get complex it'd be much easier to track the data you need for each panel in the javascript and generate the DOM elements fresh when they are needed. I'd probably do something along the lines of the following disclaimer, I didn't test the following code as a whole, meant to be a guideline:
1) Define your data structure containing all relevant information to a panel and implement it in the javascript. This is similar to what you already have, but at a minimum I'd add a panel title and panel contents of sort.
Similar to what you have but I would add to it and do some parameter checking and remove the container
function Panel (jobj) {
if(typeof jobj === "undefined") throw new ReferenceError("Bad value passed to constructor");
if(typeof jobj.id === "undefined") throw new ReferenceError("Missing an id");
this.id = 0;
this.active = false;
if(typeof jobj.title === "undefined") throw new ReferenceError("Missing panel title");
this.title = jobj.title;
//set to passed value, or if doesn't exist, default to empty array;
this.tracks = jobj.tracks || [];
}
as a bonus you could make use of prototyping and construct different types of panels with different forms of parameter checking... I'm pretty sure there are other ways of doing this, but I've found the following works for me.
var DiscoverPanel = function(jsonObj){
if(typeof jsonObj === "undefined")throw new ReferenceError("Expecting a JSON object but received none");
var panel = new Panel(jsonObj);
if(typeof jsonObj.genre === "undefined") throw new ReferenceError("Missing genre!")
panel.genre = jsonObj.genre;
return panel;
}
2) create the HTML for a stack and a current panel, so instead of the 5 div elements you currently have, I'd narrow it down to 2. Don't forget to style it to your liking
<ul id="stack"></ul>
<div id="current"></div>
3) Define your panels in the javascript instead of the html. Once you have working examples of the underlying json objects that you pass to the constructors, you might find it beneficial to write a server side service that you call via AJAX that returns the necessary JSON.
the constructor would be called like so:
//there are plenty of ways to generate the id within the constructor if hard coding
//it bugs you, your post showed a means of doing so.
var p1 = new Panel({title:"Panel 1",id:"p1"});
var p2 = new Panel({title:"Panel 2",id:"p2"});
var dp = new DiscoverPanel({title:"discover",id:"dp",genre:"rock"});
//etc.
var pn = new Panel({title:"Panel n"});
var panels = [p1,p2,dp,...,pn];
4) Onload/domready generate the innerHTML for the inactive panels, from potentially the panel titles.
$(document).ready(function () {
generateList();
}
function generateList(){
var stack = document.getElementById("stack");
stack.innerHTML = "";
panels.forEach(function(panel){
var item = document.createElement("li");
item.setAttribute("id",panel.id)
item.innerHTML = panel.title;
item.onclick = function(){
//load this specific panel to the current
loadCurrent(panel);
//regenerate the stack
generateList();
}
stack.appendChild(item);
});
}
5) Proceed to generate the innerHTML for the current panel. This would be very similar to the above method of creating the element and the generating it's content off of the panel's data.
function loadCurrent(panel){
panel.active = true;
var cur = document.getElementById("current");
//Figured table was a good example, but you could get as creative as you want
//using floats, divs, etc. Could also check the types and handle different types of panels
var table = document.createElement("table");
panel.tracks.forEach(function(track){
var row = document.createElement("tr");
var td = document.createElement("td");
td.innerHTML = track.number;
row.appendChild(td);
//repeat for whatever other values you have for a track
//you'd likely add a play function or something of that sort to each.
table.appendChild(row);
});
table.appendChild(cur);
}
This isn't exactly what you're looking for, nor is it at all related to the code you posted. But heres something I threw together that might be a good resource for you to look at. It doesn't implement the stack, but you could apply the concepts fairly easily. Check it out.
You could get rid of the createPanel function and pre-create/assign all the panels in an init function, and then have controllers that use those panels as a base. Then it's just a matter of controlling whats showing in the DOM
display: none
will effectively remove an element from the DOM remember, and can be used to hide certain panels.
Check this fiddle : http://jsfiddle.net/k0Lh3ama/1/
Do the css stuff. I am not master in it..
Your CSS ::
.sidebar{
width:10%;
background-color:#000;
color:#FFF;
height:800px;
float:left;
}
.box{
width:7%;
float:left;
height:800px;
background-color:red;
border:1px solid #000;
}
.active{
width:64%;
float:right !important;
-webkit-transition: all 1s ease-in-out;
-moz-transition: all 1s ease-in-out;
-o-transition: all 1s ease-in-out;
transition: all 1s ease-in-out;
}
Your Html Code
SideBar
<div id="panel2" class="box active">Panel 1 </div>
<div id="panel3" class="box">Panel 2 </div>
<div id="panel4" class="box">Panel 3</div>
<div id="panel5" class="box">Panel 4 </div>
Jquery
$(function(){
$(".box").click(function(){
$(".box").removeClass("active");
$(this).addClass("active");
});
});
You probably shouldn't be changing the CSS. Basically, what you want are two divs, one that holds a list of "panels" and one that holds a detailed view of a single "panel". I see this problem as breaking down into a number of sub-problems:
1) Get a list of panel objects from the server, and dynamically populate the left div, more than likely with sub-divs that hold the panel info. In other words, at page load some code will need to run that will loop through the list and add divs to the left div, one for each panel, and CSS for those pieces should handle how they get laid out and everything. A simple place to start might be to just add a single button for each item in the list, or a single image, etc.
2) Event handler so that when you click on a panel in the list it loads the detailed view into the right div. This may also remove the panel from the list, or change it visually in some way (gray it out), or whatever.
3) Code to display detailed info in the right div if an item has been loaded into it, and doing some other default if not.
One way to cheat a little to simplify all this might be to have the panels and detailed views be pre-built pages, and load them into the main page in iframes or the like.

Categories

Resources