I am planning a two-tier fixed position navigation at the top of my design.
As the user scrolls down the page, I would like the navigation to slide up until the first tier is mostly hidden.
If the user then hovers over the navigation, the container should slide down revealing the first tier again.
This hover effect should not fire if the user is at the very top of the browser window.
Additionally, when the user scrolls back to the very top of the page, the full two-tier navigation should again be fully visible as it is on the initial load.
I'm having trouble chaining these events together using javascript and have had to resort to a combination of CSS3 transitions and jQuery addClass/removeClass calls.
Additionally, I can only get the whole mish-mash to fire once. So once the user has scrolled down and back up there is no more animation.
My current code is viewable at this fiddle
Hopefully this gives an idea of what I'm trying to do.
Can anyone help me bring this monstrosity to life?
Code is as follows:
HTML
<div id="nav_wrap">
<div id="nav_one">
<h2>Nav One</h2>
</div>
<div id="nav_two">
<h3>Nav Two</h3>
</div>
</div>
<p>blah blah blah etc...</p>
CSS
#nav_wrap {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 120px;
z-index: 100;
}
#nav_one,
#nav_two {
float: left;
width: 100%;
height: 48px;
background: #111;
}
#nav_two {
background: #1f4c6b;
height: 72px;
}
h2, h3 {
color: #fff;
}
#nav_wrap.fixed {
margin-top: -42px;
-webkit-transition: margin-top .5s ease-in-out;
box-shadow: 0 0 24px #111;
}
#nav_wrap.down {
margin-top: 0px;
-webkit-transition: margin-top .5s ease-in-out;
}
#nav_wrap.drop {
top: 42px;
-webkit-transition: top .5s ease-in-out;
}
#nav_wrap.up {
top: 0;
-webkit-transition: top .5s ease-in-out;
}
Javascript
var top = $('#nav_wrap').offset()
.top - parseFloat($('#nav_wrap')
.css('marginTop')
.replace(/auto/, 0));
$(window).scroll(function (event) {
var y = $(this).scrollTop();
if (y > top) {
$('#nav_wrap').addClass('fixed');
$("#nav_wrap").hover(
function () {
$(this).addClass('drop');
},
function () {
$(this).addClass('up');
}
);
} else if (y == 0) {
$('#nav_wrap').addClass('down');
}
});
You're overcomplicating your approach. All you actually need to do is toggle one class on scroll in your JavaScript. This CSS will do the rest.
Note: The code will need prefixes added for other browsers (-moz, -o, -ms) and I would look at improving the performance of the addClass part of the call in scroll as the event will be getting called a lot.
An example fiddle can be found here.
CSS
#nav_wrap {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 120px;
z-index: 100;
}
#nav_one,
#nav_two {
width: 100%;
height: 48px;
background: #111;
}
#nav_two {
background: #1f4c6b;
height: 72px;
}
h2, h3 {
color: #fff;
}
#nav_wrap{
-webkit-transition: margin-top .5s ease-in-out;
}
#nav_wrap.scroll {
margin-top: -42px;
box-shadow: 0 0 24px #111;
}
#nav_wrap.scroll:hover{
margin-top: 0px;
}
JS
$(window).scroll(function (event) {
var y = $(this).scrollTop();
if (y > 0) {
$('#nav_wrap').addClass('scroll');
}
else{
$('#nav_wrap').removeClass('scroll');
}
});
Related
In this stackblitz, I am not able to add animation while closing, I tried it using transform, but it didnt seem to work
HTML
Blocker is used to covering the full screen in a half-transparent mode in mobile devices
const sidebar = document.querySelector('.sidebar');
sidebar.querySelector('.blocker').onclick = hide;
function show() { // swipe right
sidebar.classList.add('visible');
document.body.style.overflow = 'hidden';
}
function hide() { // by blocker click, swipe left, or url change
sidebar.classList.remove('visible');
document.body.style.overflow = '';
}
function toggle() {
sidebar.classList.contains('visible') ? hide() : show();
}
.sidebar {
/* it's a mobile sidebar, blocker and content */
position: fixed;
top: 0;
left: 0;
width: 100vw;
/* to cover the whole screen */
height: 100vh;
padding: 0;
/* to override the default padding */
background: rgba(0, 0, 0, .5);
/* half transparent background */
display: none;
z-index: 99999;
/* to be on top of any other elements */
}
.sidebar.visible {
display: block;
}
/*cover the whole screen and to detect user click on background */
.sidebar .blocker {
position: absolute;
width: 100%;
height: 100%;
}
/* user content */
.sidebar .content {
position: absolute;
top: 0;
left: 0;
background: #FFF;
height: 100%;
width: 250px;
left: -50%;
/* will be animated to left: 0, by animation */
animation: slide 0.5s forwards;
}
#keyframes slide {
100% {
left: 0;
}
}
<div class="sidebar">
<div class="blocker"></div>
<div class="content">
Sidebar Content
</div>
</div>
With the above code, you can have a working sidebar.
Check the working code from stackblitz
https://allenhwkim.medium.com/mobile-friendly-sidebar-in-few-minutes-7817b5c5239f
https://stackblitz.com/edit/medium-sidebar-1-eevvax?file=style.css,index.js
You can't animate between display:block (when .sidebar has .visible applied to it) and display:none (when .visible is removed from .sidebar).
display:none turns off the display of an element so that it has no effect on layout (the document is rendered as though the element did not exist). All descendant elements (i.e. .blocker and .content) also have their display turned off.
The reason you get an animation upon adding .visible is that .sidebar now "exists" and so .sidebar-content also exists and as such animates. As soon as you remove .visible, .sidebar ceases to exist again and so it and its descendants disappear instantaneously.
You are along the right lines using transforms but you need to remove display:none as the method for hiding the sidebar. Something like the below is a good starting point. You may need to change some values to get it looking exactly as you wish. I have added a working codepen to show the result.
.sidebar {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
padding: 0;
background: rgba(0, 0, 0, .5);
z-index: 99999;
transform: translateX(-100%); // new property - will move the element off the left hand side of the screen
transition: transform .5s ease-in-out; // new property - will make the sidebar slide in in a similar manner to your animation
}
.sidebar.visible {
transform: translateX(0); // new property - makes sidebar sit in its natural position (i.e. taking up the whole viewport)
}
.sidebar .blocker {
position: absolute;
width: 100%;
height: 100%;
}
.sidebar .content {
position: absolute;
top: 0;
left: 0;
background: #FFF;
height: 100%;
width: 250px;
}
Why my class toggle is strict without any transition effect even though I added transition: 0.5s? It doesn't work on my bootstrap website. Whenever I try to test it on custom div on jsfiddle site then it works fine.
https://jsfiddle.net/dawid1798/5goyjwx0/3/
window.addEventListener('scroll', function() {
var header = document.getElementById("navbar");
header.classList.toggle("fixednav", window.scrollY > 100);
});
.fixednav {
transition: 0.5s;
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 9999;
}
When nav loses class .fixednav it also loses the transition property.
Put it on .nav:
.nav {
width: 100%;
height: 250px;
background: red;
transition: 0.5s;
}
Also check if bootstrap is loaded into the project and that you aren't disabling the transition with your css.
I'm trying to figure out how Medium made their bottom action / menu bar slide up when your mouse enters the bottom of the document. The slide up effect is not triggered by moving the mouse over the invisible div (it slides up & down via transform translateY).
Besides, the menu bar is only 44px in height, but its is-visible class gets triggered way before your mouse is near it — but by what? When using Inspect Element, I can't see any hidden divs that could be triggering it..
I've searched for countless of ways, e.g. "show element when mouse enters specific part of document" but all search results involve when the mouse enters or moves over a div element, which is not the solution I'm looking for.
Obviously, you can solve this problem by putting the slide up menu inside a hidden container like I've done here, and then you get the desired result:
(function() {
var actionBar = document.querySelector('.action-bar');
var actionBarWrapper = document.querySelector('.action-bar-detection');
function showDiv() {
actionBar.classList.add('js-is-visible')
}
function hideDiv() {
actionBar.classList.remove('js-is-visible')
}
actionBarWrapper.onmouseover = showDiv;
actionBarWrapper.onmouseout = hideDiv;
})();
* {
margin: 0;
padding: 0;
box-sizing: border-box;
line-height: 1.5;
}
body {
height: 100%;
}
.wrapper {
width: 90%;
max-width: 600px;
margin: 5% auto;
}
.action-bar {
position: absolute;
left: 0;
bottom: 0;
border: 1px solid #252321;
background: #fff;
padding: 16px;
width: 100%;
min-height: 50px;
opacity: 0;
transform: translateY(100%);
transition: all .5s;
z-index: 99;
}
.action-bar-detection {
height: 150px;
width: 100%;
opacity: 1;
position: fixed;
bottom: 0;
left: 0;
z-index: 1;
}
.js-is-visible {
opacity: 1;
transform: translateY(0%);
}
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<div class="wrapper">
<p>When mouse enters the hidden action bar element, slides up.</p>
<p>But it's only happening because the action-bar is inside an invisible detection layer class (action-bar-detection) with a height of 150px.</p>
</div>
<div class="action-bar-detection">
<div class="action-bar">
Bottom Menu
</div>
</div>
</body>
</html>
However, this doesn't seem to be what Medium have done, and if this can be done without adding more HTML & CSS, I want to learn how! :-)
I think I'm not phrasing the problem correctly, since I can't find any solutions even remotely close (I've searched A LOT).
Any advice? What should I read up on? :-)
Get height of viewport, track onmousemove, and compare clientY from the mouse event to the viewport height:
(function() {
var actionBar = document.querySelector('.action-bar');
var viewHeight = window.innerHeight - 150;
function toggleDiv(e) {
if (e.clientY >= viewHeight) {
actionBar.classList.add('js-is-visible');
} else {
actionBar.classList.remove('js-is-visible');
}
}
window.onmousemove = toggleDiv;
})();
* {
margin: 0;
padding: 0;
box-sizing: border-box;
line-height: 1.5;
}
body {
height: 100%;
}
.wrapper {
width: 90%;
max-width: 600px;
margin: 5% auto;
}
.action-bar {
position: absolute;
left: 0;
bottom: 0;
border: 1px solid #252321;
background: #fff;
padding: 16px;
width: 100%;
min-height: 50px;
opacity: 0;
transform: translateY(100%);
transition: all .5s;
z-index: 99;
}
.action-bar-detection {
height: 150px;
width: 100%;
opacity: 1;
position: fixed;
bottom: 0;
left: 0;
z-index: 1;
}
.js-is-visible {
opacity: 1;
transform: translateY(0%);
}
<div class="wrapper">
<p>When mouse comes within 150px of the bottom part of the screen, the bar slides up.</p>
<p>When the mouse leaves this defined area of the screen, the bar slides down.</p>
</div>
<div class="action-bar-detection">
<div class="action-bar">
Bottom Menu
</div>
</div>
You could do this by listening to the mousemove event on the document, you will want to invest effort into making this performant as it will be triggered frequently. The most common way to regulate events like this is through throttling.
Once you are hooked into the mousemove event you will need to get the Y coordinate of the cursor and compare that to the height of the window, if it is within a threshold then you can reveal your panel, once it moves out you can proceed to hide it again.
Here is an example showing a basic implementation jsFiddle
// Using underscore for the throttle function though you can implement your own if you wish
document.addEventListener('mousemove', _.throttle(mouseMoveEventAction, 200));
function mouseMoveEventAction(e) {
doPanelStuff(isInsideThreshold(e.clientY));
}
function doPanelStuff(isActive) {
var panelElement = document.querySelector('.panel');
if (isActive) {
panelElement.style.background = 'red';
} else {
panelElement.style.removeProperty('background');
}
}
function isInsideThreshold(cursorY) {
var threshold = 200;
var clientHeight = document.documentElement.clientHeight;
return cursorY > (clientHeight - threshold);
}
html, body {
height: 100%;
}
.container, .content {
height: 100%;
width: 100%;
}
.panel {
height: 50px;
position: absolute;
bottom: 0;
width: 100%;
background: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<div class="container">
<div class="content"></div>
<div class="panel"></div>
</div>
I am trying to add transition to a growing div.
Here is a jsfiddle: http://jsfiddle.net/fL5rLr2y/
This jsfiddle represent my real world problem.
I have the following markup:
<div class="container">
<div class="inner">
</div>
</div>
And the following css:
html, body {
height: 100%; } .container {
position: relative;
height: 80%;
background-color: blue; }
.inner {
position: absolute;
top: 30px;
left: 0;
right: 0;
height: 50px;
background-color: red; }
.inner.open {
height: initial;
bottom: 20px; }
Here is my js:
$('.inner').click(function() {
$(this).toggleClass('open');
});
I am trying to add the transition using pure css. How can I do it?
If pure css solution is not possible, how can I use js in order to solve it?
UPDATE
After a lot of investigations, it seems that using calc is the only option to do it in pure css.
Unfortunately I have bed experience with calc, especially with safari and mobile (browser crashes and other surprises). I prefer to avoid using calc for now and use javascript solution to simulate that.
Any idea how?
Edit your .inner and .inner.open classes as demonstrated below ... you need to set a predetermined height to .open
If you're going to use CSS3 transitions you can opt to use calc() to determine your .open height without compromising browser compatibility.
Check demo
.inner {
position: absolute;
top: 30px;
left: 0;
right: 0;
height: 50px;
background-color: red;
transition: height 1s;
-webkit-transition: height 1s;
-moz-transition: height 1s;
-ms-transition: height 1s;
-o-transition: height 1s;
}
.inner.open {
height: calc(100%-50px);
bottom: 20px;
}
You can use the dynamic height by updating the style below. Demo at http://jsfiddle.net/fL5rLr2y/8/
.inner {
position: absolute;
top: 30px;
left: 0;
right: 0;
height: 50px;
background-color: red;
-webkit-transition: height 1s;
transition:height 1s;
}
.inner.open {
height: calc(100% - 50px); /* top 30px + bottom 20px */
}
Or you can use jQuery animation. See the output at http://jsfiddle.net/8mn90ueb/3/ and code below
Remove the open class and the toggle type
$('.inner').click(function() {
var currentHeight = $(this).height();
if(currentHeight > 50){
currentHeight = 50;
}
else{
currentHeight = $('.container').height() - 50;
}
$(this).animate({
height:currentHeight
},1000,function(){});
});
The CSS transition property is what you need. The height calculation of .inner is now made with jQuery.
Demo with jQuery calculation
$('.inner').click(function() {
var parentHeight = $(this).parent().outerHeight() - 50; // Get parent height - 50px
var innerHeight = $(this).outerHeight(); // Get inner height
// if the inner height = 50px then change height to the parent height calculation
// otherwise return to 50 height
if (innerHeight === 50) {
$(this).height(parentHeight);
} else {
$(this).height(50);
}
});
html,
body {
height: 100%;
}
.container {
position: relative;
height: 80%;
background-color: blue;
}
.inner {
position: absolute;
top: 30px;
left: 0;
right: 0;
height: 50px;
background-color: red;
transition: height 0.5s;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script>
<div class="container">
<div class="inner"></div>
</div>
If you change your mind about calc()
The CSS transition property is what you need along with height: calc(100% - 50px) on the open class. The calc gets you a 30px gap at the top and 20px gap at the bottom when open. The bottom property has been removed.
Compatibility:
The transition property is unlikely to need browser prefixes. Have a look here for its browser support.
calc() enjoys widespread support including, importantly, IE9 + support. More information here. To provide a fallback height for IE 8 and below, provide a normal height percentage property before the calc height for older browsers to use. Something like height: 70%
Demo with CSS only
$('.inner').click(function() {
$(this).toggleClass('open');
});
html,
body {
height: 100%;
}
.container {
position: relative;
height: 80%;
background-color: blue;
}
.inner {
position: absolute;
top: 30px;
left: 0;
right: 0;
height: 50px;
background-color: red;
transition: height 0.5s;
}
.inner.open {
height: 70%; /* pick a percentage height for IE 8 and below */
height: calc(100% - 50px); /* 100% height minus 30px at the top + 20px at the bottom */
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script>
<div class="container">
<div class="inner"></div>
</div>
I'm newbe in jQuery, please do not judge strictly. I want header become fixed when I scroll page 300px. And remove fixed if <300px. And I want to animate it, slide down when I scroll down, and slide up when I scroll top. Something like this Some site, scroll down and you'll see what I want.
My html like that
<div class="heading-wrapper">
1
2
3
4
5
</div>
Css
.heading-wrapper {
width: 100%;
height: 65px;
background: #000;
position: relative;
}
.heading-wrapper.fixed {
position: fixed;
top: -80px;
left: 0;
right: 0;
}
and jQuery
$(window).scroll(function() {
if ($(this).scrollTop() > 300){
$('.heading-wrapper').addClass("fixed");
$('.heading-wrapper.fixed').animate({'top' : '0px'}, 800);
}
else{
$('.heading-wrapper.fixed').animate({'top' : '-80px'}, 800);
setTimeout(function(){
$('.heading-wrapper').removeClass("fixed");
},800);
}
});
It dont work like what I want.
If scrolling by pressing down mouse whell - it dont animate..
Animation appears at once only..
Slide up animation never appears..
If I scrolling fast up and down, the whole structure breaks down, no styles are added where necessary))
Please, help me to fix this, and remember, do not judge strictly! :)
JsFiddle link
Demo
js
$(document).ready(function () {
$("header").before($("header").clone().addClass("animateIt"));
$(window).on("scroll", function () {
$("body").toggleClass("down", ($(window).scrollTop() > 100));
});
});
css
body, html {
margin:0;
padding:0;
}
header {
position: relative;
width: 100%;
height: 60px;
line-height: 60px;
background: #000;
color: #fff;
}
header.animateIt {
position:fixed;
top:-60px;
left: 0;
right: 0;
z-index:999;
transition:0.4s top cubic-bezier(.3, .73, .3, .74);
}
body.down header.animateIt {
top:0;
}
.content {
padding: 0 20px 20px;
background: #fff;
line-height: 1.5;
color: #333;
}
html
<header>
1
2
3
4
5
</header>
Here's how I would do it.
First, depending on the browsers you're supporting, you could add this CSS :
.heading-wrapper {
position: fixed;
top: -80px;
transition: top 1s linear; /*as you wish*/
[...]
}
.heading-wrapper.relative {
position: absolute;
top: 0px;
}
.heading-wrapper:not(.relative).fixed {
top: 0px;
}
Then in Javascript :
var $wrapper = $(".heading-wrapper");
var $win = $(window);
var doc = document.documentElement, body = document.body;
var top = 0;
$wrapper.clone().appendTo("body").addClass("relative");
$win.scroll(function () {
top = (doc && doc.scrollTop || body && body.scrollTop || 0);
if( top > 300)
setTimeout(function(){$wrapper.addClass("fixed");},0);
else if( $wrapper.hasClass("fixed") )
setTimeout(function(){$wrapper.removeClass("fixed");},0);
});
I updated your JSFiddle.
EDIT : Added a cloned menu, absolute.