Earlier I asked this question about setting a height back to 0 after toggling a class.
Whilst the answer was correct, it turned out my question was not so I'm back to rephrase and try and solve my problem.
I have a navigation list and some items have a sub navigation list inside them
Each item that has a sub navigation has a toggle arrow which you click
I want the sub navigation <ul> to start with a height of 0, and when you click the toggle arrow I want to use jQuery to set that sub navigation's height to the calculated height of it's items
The HTML is like this:
<li class="slidedown">
Parent Link
<!-- Dropdown arrow -->
<span class="slidedown-toggle">
<span class="caret"></span>
</span>
<!-- Submenu -->
<ul class="nav nav-sub">
<li>
Child Menu Item 1
</li>
</ul>
</li>
The answer to my previous question gave me this script:
$(function navCollapse() {
var slidedownToggle = $('#global-nav .slidedown-toggle');
slidedownToggle.click(function () {
var slidedown = $(this).parent('.slidedown');
var $li = $(this).closest('li');
var subnav = $(this).siblings('.nav-sub');
var subnavHeight = subnav.height();
slidedown.toggleClass('open');
if ($li.hasClass('open')) {
subnav.height(0);
} else {
subnav.height(subnavHeight);
}
});
});
BUT I quickly realised that once the height had been set to 0, the script would continue to calculate it as 0 from then on, no matter how many times you click.
Which leaves me stuck and confused.
The old Bootstrap Collapse is pretty much what I want (only the height change). It sets the height via JavaScript and then uses CSS to do the transition. Although I'd prefer not to have my sub-menu have display: none.
Any ideas?
NOTE: I don't want to use JavaScript animations. I want to use CSS for that.
I also don't want to use the max-height solution to CSS only dropdowns. That is my fallback.
DEMO
There are a few problems with the logic of your code, so I've made some modifications.
$(function navCollapse() {
var slidedownToggle = $('#global-nav .slidedown-toggle');
slidedownToggle.click(function () {
// Get the container item
var $slidedown = $(this).parents('.slidedown');
// Get the ul that needs to slide up/down
var $subnav = $(this).siblings('.nav-sub');
// Calculate the height required (in px) to show all LIs
var totalHeight = 0;
$subnav.find('li').each(function() {
totalHeight += $(this).height();
});
// Set the appropriate height
if ($slidedown.hasClass('open')) {
$subnav.css({height: '0px'});
} else {
$subnav.css({height: totalHeight + 'px'});
}
$slidedown.toggleClass('open');
});
});
Then in your CSS make sure you apply the transition to your .nav-sub:
-webkit-transition: height 0.2s linear;
-moz-transition: height 0.2s linear;
-ms-transition: height 0.2s linear;
-o-transition: height 0.2s linear;
transition: height 0.2s linear;
Working example:
http://jsbin.com/INIwuTA/11/
Here is one way: http://jsfiddle.net/Tes9P/2/
Basically, this applies the transition to a wrapper div that goes around the sub menu. Because CSS transitions require a set height to animate to (you can't animate to auto) this wrapper needs to have a height larger than what your largest sub-menu will have.
In my jsfiddle, I set up the CSS like this:
.nav-sub {
height:auto;
background-color:#eee;
}
.nav-sub-holder {
-webkit-transition: height 2s ease;
-moz-transition: height 2s ease;
-o-transition: height 2s ease;
transition: height 2s ease;
overflow:hidden;
height:100px;
}
.closed {
height:0px;
overflow:hidden;
}
Related
I'm using a bit of jquery to have a Bootstrap 4 navbar fade in when scrolling down past a certain point, by adding and removing a specific class. However, the code I have won't show the navbar if I reload the page having already scrolled down, and when scrolling back up the CSS transition doesn't fade the bar out but simply pops out of view instantly. How can I fix those issues? Would it be better to rely on purely jquery instead of relying on a CSS class? If so, how would that work? Thanks!
JS:
$(window).scroll(function(e) {
if($(window).width() >= 768)
var scroll = $(window).scrollTop();
if (scroll >= 300) {
$('.navbar-home').addClass("navbar-hide");
} else {
$('.navbar-home').removeClass("navbar-hide");
}
});
CSS:
.navbar-home {
opacity: 0;
visibility: hidden;
transition: opacity 0.5s ease-in-out;
-moz-transition: opacity .2s ease-in-out;
-o-transition: opacity .2s ease-in-out;
-webkit-transition: opacity .2s ease-in-out;
}
.navbar-hide {
opacity: 1;
visibility: visible;
}
You also need to call the same code on page load. Change the listener to:
$(window).on("scroll load", function(e) {...})
I have an element I want to "expand" and change the background color for a page background. As the user scrolls, a dot in the center will expand to fill the page with a new background color. I see examples of how to change the background but not how to "expand" it. I have attached a jsfiddle of the CSS animation effect I'm looking for. This example shows how it will look but only works on the hover. You can see what it's supposed to look like if you scroll the example and hover the white dot.1
Preferably I'd like to accomplish this with css animation but I'm not opposed to trying it out with javascript. I've been fiddling around with that here.
Second, I've been using a fake element to get the example but is there a way I can do this effect without needing the element and just using the container's background-color?
Here's the HTML of the example of the effect I'm trying to achieve.
<div class="container">
<span class="white"></span>
</div>
And here's the CSS:
.container {height:500px;width:100%;background:#ed565d;position:relative;}
.container span {display:block;}
.white {background:#ffffff;height:10px;width:10px;margin:auto;border-radius:100%;position:absolute;top:50%;left:50%;}
.container:hover .white {
width:300%;
height:300%;
-moz-transition: all 0.5s ease-out;
-o-transition: all 0.5s ease-out;
-webkit-transition: all 0.5s ease-out;
transition:all 0.5s ease-out;
top:-100%;
left:-100%;
}
If you want the animation to correlate directly to the percentage that the user has scrolled on the page, JavaScript will be needed.
First, get the scroll percentage. Here's a great answer on how to do that: https://stackoverflow.com/a/8028584/2957677
const scrollTop = $(window).scrollTop();
const documentHeight = $(document).height();
const windowHeight = $(window).height();
const scrollPercent = (scrollTop / (documentHeight - windowHeight)) * 100;
Then you can define an animation function that takes in the percent the user has scrolled, and will set the style on the circle to be a percentage between the CSS values at the start of the animation, and the CSS values at the end of the animation.
function growAnimation($element, animationPercentage) {
const animationDecimal = animationPercentage / 100;
// Your existing .grow CSS values
const startPositionPercent = 50; // top/left at start of animation
const finishSizePercent = 300; // width/height at end of animation
const finishPositionPercent = -100; // top/left at end of animation
// The current CSS values, based on how far the user has scrolled
const currentSizePercent = getProgressFromTo(0, finishSizePercent, animationDecimal);
const currentPositionPercent = getProgressFromTo(startPositionPercent, finishPositionPercent, animationDecimal);
$element.css({
width: `${currentSizePercent}%`,
height: `${currentSizePercent}%`,
top: `${currentPositionPercent}%`,
left: `${currentPositionPercent}%`
});
}
// A util function to get the progress between two values
// e.g. 50% between 0 and 10 is 5
function getProgressFromTo(from, to, animationDecimal) {
return from + (to - from) * animationDecimal;
}
Here's a fiddle: https://jsfiddle.net/owazk8y1
Animation Curves
You can look into animation curves to make the animation look a lot smoother. Surround animationDecimal in a bezier curve function. Here's some example functions:
https://gist.github.com/gre/1650294
https://jsfiddle.net/owazk8y1/1
It's a mix of different ideas that I have sinned here and there ...
with a small part JS, to be piloted in CSS
PS :transition command must be set on element
const storeScroll=()=>{
document.documentElement.dataset.scroll = window.scrollY;
}
window.onscroll=e=>{ // called when the window is scrolled.
storeScroll()
}
storeScroll() // first attempt
.container {
position : relative;
height : 500px;
width : 100%;
background : #ed565d;
overflow : hidden; /* added */
}
.white {
display : block;
position : absolute;
background : #fff;
height : 10px;
width : 10px;
margin : auto;
border-radius : 100%;
top : 50%;
left : 50%;
-moz-transition : all 0.5s ease-out;
-o-transition : all 0.5s ease-out;
-webkit-transition : all 0.5s ease-out;
transition : all 0.5s ease-out;
}
html:not([data-scroll='0']) .white {
width : 300%;
height : 300%;
top : -100%;
left : -100%;
}
<div class="container">
<span class="white"></span>
</div>
I have written a snippet of code to create an accordion that you can see the code here.
There is some HTML code:
<div class="accordion_holder">
<div class="accordion_item">
<div class="title" onclick="toggle_accordion(this);">
title 1 here
</div>
<div class="content">
content here
</div>
</div>
<div class="accordion_item">
<div class="title" onclick="toggle_accordion(this);">
title 2 here
</div>
<div class="content">
content 2 here
</div>
</div>
</div>
And I have bellow CSS to simulate accordion transition:
.accordion_holder .content {
height: 0px;
overflow: hidden;
-webkit-transition: height 0.5s linear;
-moz-transition: height 0.5s linear;
-ms-transition: height 0.5s linear;
-o-transition: height 0.5s linear;
transition: height 0.5s linear;
}
And I am setting the height of content to 0px or it's offsetHeight using this snippet of JavaScript code:
function toggle_accordion(clicked_element) {
var father = closest_parent(clicked_element, 'accordion_item');
console.log(father);
var accordion_content = father.getElementsByClassName("content")[0];
var destination = 0;
var current_height = accordion_content.offsetHeight;
if (current_height == 0)
{
accordion_content.style.height = "auto";
destination = accordion_content.offsetHeight;
accordion_content.style.height = "0px";
}
console.log("destination is:", destination);
accordion_content.style.height = destination+"px";
}
/************************************
** Find Closest Parent with Class **
***********************************/
function closest_parent (current_element, class_name) {
var parent = current_element.parentElement;
if (parent) {
if (parent.className.indexOf(class_name) >= 0)
{
//We found requested parent
return parent;
}
else
{
return closest_parent (parent, class_name);
}
}
else
{
return false;
}
}
It's working correctly to set the height of content element. when it is closing (set the height of Content element to 0px), it will move smoothly and transition effect is working. but when it is opening (set the height of Content element to offsetHeight), Transition won't work.
Can you please guide me what is wrong?
It's because you're setting the height to auto and then trying to get its offsetHeight.
Getting the offsetHeight triggers a reflow, so it's already going to layout the page and try to make a transition to height: auto; Since transitions to height: auto don't work, it's going to jump straight to the target height. Then, when you set the element's height to the value you got from offsetHeight, the element already has that height, so there will be no transition.
Two ways you could fix that:
A more hacky way is to turn the reflow triggered by offsetHeight to your favor: just try to get the value again after setting the height back to 0, triggering the reflow again:
if (current_height == 0)
{
accordion_content.style.height = "auto";
destination = accordion_content.offsetHeight;
accordion_content.style.height = "0px";
// Here is the trick:
accordion_content.offsetHeight // triggers a reflow
}
Another way is to store the height of the contents of the accordion elements beforehand, so you just transition to that value. That can be tricky, especially if the contents of the accordion can change, but it's less dependent on esoteric browser behaviour.
basically, how to dynamically perform the following using JS:
element1:target ~ element2 {//does something}
element1.addEventListener(":target", () => {
//something here?
});
Or maybe another instance to make it clearer:
.go:checked ~ .road {
-webkit-animation: move-road 6s 1 1.3s ease-in-out forwards,
rotate-road 1s forwards;
}
(Source) : http://blog.teamtreehouse.com/triggering-css-animations-with-sibling-selectors
Is this JS doable?
The context of this code relates to a carousel without any plugins. The question wasn't about slider but I am just going to explain how I Instead tried to make it simpler with the following as against my requirements above:
e.addEventListener('click', function(evt) {
// e is individual buttons on the slider div
let that = this;
// id = evt.target.hash;
id = that.href;
// console.log(id);
num = parseInt(id[id.length - 1]) * -800;
slider.style.left = `${num}px`;
});
slider is the following class on the css side, where it matters:
.div-img-holder
width: 2400px;
height: 400px;
clear: both;
position: relative;
-webkit-transition: left 1s;
-moz-transition: left 1s;
-o-transition: left 1s;
transition: left 1s;
I calculated the total div frames to show in one frame of the slider and set the presentable width;
Created a slider width as in here i.e. maximum width.
On each btn click , the slider goes left by a certain position. the transition takes care of the smooth left movement.
Put the transition and pseudo-css stuff to css.
i have this following jsfiddle link
where am trying to squeeze the webpage to show an AD towards right
http://jsfiddle.net/5o6ghf9d/1/
Works fine on dekstop browsers
But its not getting squeezed on ipad safari/chrome browsers
Below are functions used to squeeze/unsqueeze the web page
function squeeze_page(){
d.body.style.paddingRight='160px';
d.body.style.paddingLeft='160px';
d.body.style.marginLeft='-160px';
d.body.style.overflowX='hidden !important';
is_page_squeezed=true;
}
function unsqueeze_page(){
d.body.style.paddingRight='';
d.body.style.paddingLeft='';
d.body.style.marginLeft='';
is_page_squeezed=false;
}
Let me know if any other way is there where i can squeeze the webpage
Perhaps this is what you're looking for: JSFIDDLE
If you want to have the AD slide from right when showing, it's better to use the CSS transition like in my example.
First, you need to have a container for the content, which in my example, I add
<div id="container">
...
<!-- Your Content Here -->
...
</div>
to contain all your <p> content, then using the CSS, I set this
#test {
position:fixed;
width:160px;
background:blue;
right:-160px;
top:0px;
bottom:0px;
-webkit-transition: all ease-in-out 1s;
-moz-transition: all ease-in-out 1s;
transition: all ease-in-out 1s;
}
#test.show {
right:0;
}
position:fixed; to make it's position fixed to the viewport whenever you're scrolling it, top and bottom set to 0 to make it's height full, and the transition is to make it looks like slide from right to left when it shows
The same with the div container that's using this style
#container {
margin-right:0;
-webkit-transition: all ease-in-out 1s;
-moz-transition: all ease-in-out 1s;
transition: all ease-in-out 1s;
}
#container.squeezed {
margin-right:160px;
}
so that it looks like it's being squeezed by the AD
then use this script to add or remove the class from #container and #test
window.onscroll = function () {
if (pageYOffset > 100) {
$("#test").addClass("show");
$("#container").addClass("squeezed");
} else if (pageYOffset < 100) {
$("#test").removeClass("show");
$("#container").removeClass("squeezed");
}
}